From 26aeff6a510f64e9c6a26bcc8bfd81dd7574f4b5 Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker Date: Tue, 30 May 2023 09:19:24 -0400 Subject: [PATCH 01/27] feature/FOUR-8705 --- .../Http/Controllers/Api/ScriptExecutorController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Http/Controllers/Api/ScriptExecutorController.php b/ProcessMaker/Http/Controllers/Api/ScriptExecutorController.php index b7b45db400..57ec8edf3a 100644 --- a/ProcessMaker/Http/Controllers/Api/ScriptExecutorController.php +++ b/ProcessMaker/Http/Controllers/Api/ScriptExecutorController.php @@ -175,7 +175,9 @@ public function update(Request $request, ScriptExecutor $scriptExecutor) $request->only($scriptExecutor->getFillable()) ); - ScriptExecutorUpdated::dispatch($scriptExecutor->id, $original_values, $scriptExecutor->getChanges()); + if (!empty($scriptExecutor->getChanges())) { + ScriptExecutorUpdated::dispatch($scriptExecutor->id, $original_values, $scriptExecutor->getChanges()); + } BuildScriptExecutor::dispatch($scriptExecutor->id, $request->user()->id); From 5bb72a8a230f4c56a79ee1078189e8e33b3f97d7 Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker Date: Tue, 30 May 2023 09:25:41 -0400 Subject: [PATCH 02/27] feature/FOUR-8704 --- ProcessMaker/Events/ScriptExecutorCreated.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProcessMaker/Events/ScriptExecutorCreated.php b/ProcessMaker/Events/ScriptExecutorCreated.php index 31fa4a10e4..40e2c9ad70 100644 --- a/ProcessMaker/Events/ScriptExecutorCreated.php +++ b/ProcessMaker/Events/ScriptExecutorCreated.php @@ -23,9 +23,9 @@ public function __construct(array $created_values) $this->data = [ 'script_executor_id' => $created_values['id'], 'title' => $created_values['title'], - 'description' => $created_values['description'], + 'description' => isset($created_values['description']) ? $created_values['description'] : "", 'language' => $created_values['language'], - 'config' => $created_values['config'] + 'config' => isset($created_values['config']) ? $created_values['config'] : "" ]; } From 16002a0a5cbd3b5b0401dcbdaee94c533c138d0d Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Mon, 26 Jun 2023 12:27:33 -0400 Subject: [PATCH 03/27] Qa observations fixed in Create and Delete Scripts --- ProcessMaker/Events/ScriptCreated.php | 8 +++++++- ProcessMaker/Events/ScriptDeleted.php | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Events/ScriptCreated.php b/ProcessMaker/Events/ScriptCreated.php index 2cb128bdd0..2101e6e18c 100644 --- a/ProcessMaker/Events/ScriptCreated.php +++ b/ProcessMaker/Events/ScriptCreated.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Script; +use ProcessMaker\Models\ScriptCategory; use ProcessMaker\Traits\FormatSecurityLogChanges; class ScriptCreated implements SecurityLogEventInterface @@ -15,7 +16,7 @@ class ScriptCreated implements SecurityLogEventInterface private array $changes; private array $original; - + public string $categoryName; private Script $script; /** @@ -28,6 +29,8 @@ public function __construct(Script $script, array $changes) { $this->script = $script; $this->changes = $changes; + $categoryId = $this->script['script_category_id'] ?? ''; + $this->categoryName = ScriptCategory::where('id', $categoryId)->value('name'); } /** @@ -54,6 +57,9 @@ public function getData(): array 'created_at' => $this->script->getAttribute('created_at'), ] : [ 'name' => $this->script->getAttribute('title'), + 'description' => $this->script->getAttribute('description'), + 'category' => $this->categoryName, + 'language' => $this->script->getAttribute('language') ]; unset($this->changes['code']); unset($this->original['code']); diff --git a/ProcessMaker/Events/ScriptDeleted.php b/ProcessMaker/Events/ScriptDeleted.php index ef2fefe100..891b6a514c 100644 --- a/ProcessMaker/Events/ScriptDeleted.php +++ b/ProcessMaker/Events/ScriptDeleted.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Events; +use Carbon\Carbon; use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Script; @@ -13,6 +14,7 @@ class ScriptDeleted implements SecurityLogEventInterface use FormatSecurityLogChanges; private Script $script; + public string $userDelete; /** * Create a new event instance. @@ -45,6 +47,7 @@ public function getData(): array { return [ 'name' => $this->script->getAttribute('title'), + 'deleted_at' => Carbon::now() ]; } From ad02090ce37e0773f11d057bd8bc35e3530fdc2b Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Mon, 26 Jun 2023 12:36:31 -0400 Subject: [PATCH 04/27] Ready to PR --- ProcessMaker/Events/ScriptCreated.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ProcessMaker/Events/ScriptCreated.php b/ProcessMaker/Events/ScriptCreated.php index 2101e6e18c..40cc293a33 100644 --- a/ProcessMaker/Events/ScriptCreated.php +++ b/ProcessMaker/Events/ScriptCreated.php @@ -14,7 +14,6 @@ class ScriptCreated implements SecurityLogEventInterface use FormatSecurityLogChanges; private array $changes; - private array $original; public string $categoryName; private Script $script; From 6454d47f5baafb2266844e2a507b68002bcf2a88 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Mon, 26 Jun 2023 12:52:17 -0400 Subject: [PATCH 05/27] FOUR-8697 --- ProcessMaker/Events/AuthClientDeleted.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ProcessMaker/Events/AuthClientDeleted.php b/ProcessMaker/Events/AuthClientDeleted.php index 1eb9e92dfa..5d1875d6bc 100644 --- a/ProcessMaker/Events/AuthClientDeleted.php +++ b/ProcessMaker/Events/AuthClientDeleted.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Events; +use Carbon\Carbon; use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; @@ -18,10 +19,9 @@ class AuthClientDeleted implements SecurityLogEventInterface * * @return void */ - public function __construct(array $deleted_values) + public function __construct(array $values) { - $this->data = ['auth_client_id' => $deleted_values['id']]; - $this->changes = $deleted_values; + $this->changes = $values; } /** @@ -29,7 +29,11 @@ public function __construct(array $deleted_values) */ public function getData(): array { - return $this->data; + return [ + 'auth_client_id' => $this->changes['id'] ?? 0, + 'name' => $this->changes['name'] ?? 0, + 'deleted_at' => Carbon::now() + ]; } /** From 8282051598abc9124282707a99fab288c9e75954 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Thu, 22 Jun 2023 11:13:54 -0400 Subject: [PATCH 06/27] FOUR-8720: Add S3 Download --- .../SecurityLogDownloadJobCompleted.php | 4 +- .../Controllers/Api/SecurityLogController.php | 16 +- ProcessMaker/Jobs/DownloadSecurityLog.php | 184 ++++++++++++++++-- ProcessMaker/Models/Media.php | 11 ++ .../Feature/Jobs/DownloadSecurityLogTest.php | 105 ++++++++++ 5 files changed, 301 insertions(+), 19 deletions(-) create mode 100644 tests/Feature/Jobs/DownloadSecurityLogTest.php diff --git a/ProcessMaker/Events/SecurityLogDownloadJobCompleted.php b/ProcessMaker/Events/SecurityLogDownloadJobCompleted.php index e8c3a2801b..a4007ff5e0 100644 --- a/ProcessMaker/Events/SecurityLogDownloadJobCompleted.php +++ b/ProcessMaker/Events/SecurityLogDownloadJobCompleted.php @@ -11,7 +11,9 @@ class SecurityLogDownloadJobCompleted implements ShouldBroadcastNow { - use Dispatchable, InteractsWithSockets, SerializesModels; + use Dispatchable; + use InteractsWithSockets; + use SerializesModels; public $user; diff --git a/ProcessMaker/Http/Controllers/Api/SecurityLogController.php b/ProcessMaker/Http/Controllers/Api/SecurityLogController.php index 08843563a9..69b904f809 100644 --- a/ProcessMaker/Http/Controllers/Api/SecurityLogController.php +++ b/ProcessMaker/Http/Controllers/Api/SecurityLogController.php @@ -11,6 +11,7 @@ use ProcessMaker\Http\Resources\ApiResource; use ProcessMaker\Http\Resources\SecurityLogs; use ProcessMaker\Jobs\DownloadSecurityLog; +use ProcessMaker\Models\Media; use ProcessMaker\Models\SecurityLog; use ProcessMaker\Models\User; @@ -129,7 +130,7 @@ public function store(Request $request) { $request->validate(SecurityLog::rules()); - $securityLog = new SecurityLog; + $securityLog = new SecurityLog(); $fields = SensitiveDataHelper::parseArray($request->json()->all()); $securityLog->fill($fields); $securityLog->saveOrFail(); @@ -139,17 +140,24 @@ public function store(Request $request) private function download(Request $request, User $user = null) { + if (!Media::s3IsReady()) { + return response()->json([ + 'success' => false, + 'message' => __('Sorry, this feature requires the configured AWS S3 service. Please contact the administrator.') + ]); + } $request->validate([ 'format' => 'required|string|in:xml,csv', ]); - sleep(1); $sessionUser = Auth::user(); + // Call the Event DownloadSecurityLog::dispatch($sessionUser, $request->input('format'), $user ? $user->id : null) ->delay(now()->addSeconds(5)); return response()->json([ - 'message' => __('The log file is being prepared and will be sent to your email as soon as it is ready.'), - ], 200); + 'success' => true, + 'message' => __('The log file is being prepared and will be sent to your email as soon as it is ready.') + ]); } public function downloadForAllUsers(Request $request) diff --git a/ProcessMaker/Jobs/DownloadSecurityLog.php b/ProcessMaker/Jobs/DownloadSecurityLog.php index 4403455969..51a8019fcc 100644 --- a/ProcessMaker/Jobs/DownloadSecurityLog.php +++ b/ProcessMaker/Jobs/DownloadSecurityLog.php @@ -2,20 +2,28 @@ namespace ProcessMaker\Jobs; +use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; use ProcessMaker\Events\SecurityLogDownloadJobCompleted; +use ProcessMaker\Models\Media; +use ProcessMaker\Models\SecurityLog; use ProcessMaker\Models\User; +use Ramsey\Uuid\Uuid; class DownloadSecurityLog implements ShouldQueue { - use Dispatchable, - InteractsWithQueue, - Queueable, - SerializesModels; + use Dispatchable; + use InteractsWithQueue; + use Queueable; + use SerializesModels; private User $user; @@ -23,9 +31,14 @@ class DownloadSecurityLog implements ShouldQueue private ?int $userId; + public const CSV_SEPARATOR = '|'; + + public const FORMAT_CSV = 'csv'; + public const FORMAT_XML = 'xml'; + /** * @param User $user - * @param string $format + * @param string $format xml|csv * @param int|null $userId */ public function __construct(User $user, string $format, int $userId = null) @@ -39,18 +52,161 @@ public function __construct(User $user, string $format, int $userId = null) * Execute the job. * * @return void + * @throw Exception */ public function handle() { - //1. Get all data from security_logs table - //2. Create a file in specified format: csv or xml - //3. Zip this file - //4. Store in S3, with private visibility, with 24h of lifecycle - //5. Send email to user with the link or error message - if (mt_rand(1, 10) <= 8) { - event(new SecurityLogDownloadJobCompleted($this->user, true, __('Click on the link and download the file. This link will be available until midnight tonight.'), 'http://processmaker.com/?download=true')); - } else { - event(new SecurityLogDownloadJobCompleted($this->user, false, __('Sorry, it was not possible to generate the log file. Please contact the administrator.'))); + if (!Media::s3IsReady()) { + event(new SecurityLogDownloadJobCompleted($this->user, false, __('Sorry, this feature requires the configured AWS S3 service.. Please contact the administrator.'))); + return; + } + try { + $collection = $this->getCollection(); + $contents = $this->generateContent($collection); + $expires = $this->getExpires(); + $filename = $this->createTemporaryFilename(); + $url = $this->createTemporaryUrl($filename, $contents, $expires); + $message = __('Click on the link and download the file. This link will be available until midnight tonight.'); + event(new SecurityLogDownloadJobCompleted($this->user, true, $message, $url)); + } catch (Exception $e) { + $message = __('Sorry, it was not possible to generate the log file. Please contact the administrator.'); + event(new SecurityLogDownloadJobCompleted($this->user, false, $message)); + } + } + + /** + * Generate the content with the corresponding format + */ + protected function generateContent(array $collection) + { + return $this->format === static::FORMAT_CSV ? $this->toCSV($collection) : $this->toXML($collection); + } + + /** + * To CSV + */ + protected function toCSV(array $collection) + { + $first = current($collection); + $headers = array_keys($first ?: []); + $content = ''; + $line = []; + foreach ($headers as $key) { + $line[] = $key; + } + $content .= implode(static::CSV_SEPARATOR, $line); + foreach ($collection as $item) { + $line = []; + foreach ($headers as $key) { + if (is_object($item[$key])) { + $line[] = json_encode($item[$key]); + } else { + $line[] = isset($item[$key]) ? $item[$key] : null; + } + } + $content .= PHP_EOL . implode(static::CSV_SEPARATOR, $line); } + return $content; + } + + /** + * To XML + */ + protected function toXML(array $collection) + { + $content = '' . PHP_EOL; + $content .= ''; + $tab = "\t"; + foreach ($collection as $item) { + $content .= PHP_EOL . $tab . ''; + foreach ($item as $key => $value) { + if (is_object($value)) { + $value = json_encode($value); + } + $content .= sprintf( + '%s<%s>%s', + PHP_EOL . $tab, + $key, + $value, + $key + ); + } + $content .= PHP_EOL . $tab . ''; + } + $content .= PHP_EOL . ''; + + return $content; + } + + /** + * Get collection + * + * @return array + */ + protected function getCollection() + { + $query = SecurityLog::query(); + if ($this->userId) { + $query->where('user_id', $this->userId); + } + $results = []; + $query->chunk(1000, function (Collection $collection) use (&$results) { + foreach ($collection as $item) { + $results[] = $item->toArray(); + } + unset($item); + }); + + return $results; + } + + /** + * Create temp url + * + * @param string $filename + * @param string $content + * @param Carbon $expires + * + * @return string + */ + protected function createTemporaryUrl(string $filename, string $content, Carbon $expires) + { + $disk = Storage::disk('s3'); + $disk->put($filename, $content, [ + 'ACL' => 'private', // private|public-read, + 'Expires' => $expires->toString() + ]); + $url = $disk->temporaryUrl( + $filename, + $expires, + [ + 'ResponseContentType' => 'application/octet-stream', + 'ResponseContentDisposition' => 'attachment; filename=' . $filename, + ] + ); + + return $url; + } + + /** + * Get expires time + * + * @return Carbon time + */ + protected function getExpires() + { + return now()->addHours(24); + } + + /** + * Create a temp file + * + * @return string + */ + protected function createTemporaryFilename() + { + $uuid = Uuid::uuid4()->toString() . Str::random(8); + + return 'security-logs/' . $uuid . '.' . $this->format; } } diff --git a/ProcessMaker/Models/Media.php b/ProcessMaker/Models/Media.php index e2a55c831a..75ad48d282 100644 --- a/ProcessMaker/Models/Media.php +++ b/ProcessMaker/Models/Media.php @@ -191,4 +191,15 @@ public static function getFilesRequest(ProcessRequest $request) // Get all files for process and all subprocesses .. return self::whereIn('model_id', $requestTokenIds)->get(); } + + /** + * Check if the S3 is ready to use + */ + public static function s3IsReady() + { + return config('filesystems.disks.s3.key') + && config('filesystems.disks.s3.secret') + && config('filesystems.disks.s3.region') + && config('filesystems.disks.s3.bucket'); + } } diff --git a/tests/Feature/Jobs/DownloadSecurityLogTest.php b/tests/Feature/Jobs/DownloadSecurityLogTest.php new file mode 100644 index 0000000000..076ef35089 --- /dev/null +++ b/tests/Feature/Jobs/DownloadSecurityLogTest.php @@ -0,0 +1,105 @@ +delete(); + + $this->simpleCollection = [ + ['id' => 1, 'event' => 'login'], + ['id' => 2, 'event' => 'logout'], + ]; + + SecurityLog::factory()->create(['event' => 'login', 'user_id' => $this->user->id]); + SecurityLog::factory()->create(['event' => 'logout', 'user_id' => $this->user->id]); + SecurityLog::factory()->create(['event' => 'attempt']); + SecurityLog::factory()->create(['event' => 'attempt']); + } + + public function testCreateTemporaryFilename() + { + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); + $method = new ReflectionMethod($job, 'createTemporaryFilename'); + $filename = $method->invoke($job); + $this->assertStringContainsString('.csv', $filename); + + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_XML); + $method = new ReflectionMethod($job, 'createTemporaryFilename'); + $filename = $method->invoke($job); + $this->assertStringContainsString('.xml', $filename); + } + + public function testGetCollection() + { + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); + $results = (new ReflectionMethod($job, 'getCollection'))->invoke($job); + $this->assertCount(4, $results); + + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV, $this->user->id); + $results = (new ReflectionMethod($job, 'getCollection'))->invoke($job); + $this->assertCount(2, $results); + } + + public function testToCSV() + { + $collection = $this->simpleCollection; + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); + $csv = (new ReflectionMethod($job, 'toCSV'))->invoke($job, $collection); + $this->assertEquals( + 'id|event' . PHP_EOL . + '1|login' . PHP_EOL . + '2|logout', + $csv + ); + } + + public function testToXML() + { + $collection = $this->simpleCollection; + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); + $xml = (new ReflectionMethod($job, 'toXML'))->invoke($job, $collection); + $this->assertStringContainsString('', $xml); + $this->assertStringContainsString('2', $xml); + } + + public function testCreateTemporaryUrl() + { + //The test should not be run as it requires an AWS key + $this->assertTrue(true, 'ignoring'); + return; + $collection = $this->simpleCollection; + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); + $filename = (new ReflectionMethod($job, 'createTemporaryFilename'))->invoke($job); + $expires = (new ReflectionMethod($job, 'getExpires'))->invoke($job); + $csv = (new ReflectionMethod($job, 'toCSV'))->invoke($job, $collection); + $url = (new ReflectionMethod($job, 'createTemporaryUrl'))->invoke($job, $filename, $csv, $expires); + $this->assertStringContainsString('s3.amazonaws.com/security-logs', $url); + $disk = Storage::disk('s3'); + $this->assertTrue($disk->exists($filename)); + } + + public function testHandleWithSuccess() + { + //The test should not be run as it requires an AWS key + $this->assertTrue(true, 'ignoring'); + return; + $this->expectsEvents(SecurityLogDownloadJobCompleted::class); + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); + (new ReflectionMethod($job, 'handle'))->invoke($job); + } +} From 85d2616dd73253cea49001be4c9e9cea16e0bd22 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Tue, 27 Jun 2023 08:55:02 -0400 Subject: [PATCH 07/27] Observations fixed --- ProcessMaker/Events/ScriptCreated.php | 6 ++++-- ProcessMaker/Events/ScriptDeleted.php | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ProcessMaker/Events/ScriptCreated.php b/ProcessMaker/Events/ScriptCreated.php index 40cc293a33..3df5d591ac 100644 --- a/ProcessMaker/Events/ScriptCreated.php +++ b/ProcessMaker/Events/ScriptCreated.php @@ -15,7 +15,7 @@ class ScriptCreated implements SecurityLogEventInterface private array $changes; private array $original; - public string $categoryName; + public string $categoryName = ''; private Script $script; /** @@ -29,7 +29,9 @@ public function __construct(Script $script, array $changes) $this->script = $script; $this->changes = $changes; $categoryId = $this->script['script_category_id'] ?? ''; - $this->categoryName = ScriptCategory::where('id', $categoryId)->value('name'); + if (!empty($categoryId)) { + $this->categoryName = ScriptCategory::where('id', $categoryId)->value('name'); + } } /** diff --git a/ProcessMaker/Events/ScriptDeleted.php b/ProcessMaker/Events/ScriptDeleted.php index 891b6a514c..a4a526486c 100644 --- a/ProcessMaker/Events/ScriptDeleted.php +++ b/ProcessMaker/Events/ScriptDeleted.php @@ -14,7 +14,6 @@ class ScriptDeleted implements SecurityLogEventInterface use FormatSecurityLogChanges; private Script $script; - public string $userDelete; /** * Create a new event instance. From 343c66b257310f965da5b8a47f03027f5b14b165 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Tue, 27 Jun 2023 16:17:17 -0400 Subject: [PATCH 08/27] Adding changes --- .../Events/SecurityLogDownloadFailed.php | 75 ++++++ .../Controllers/Api/SecurityLogController.php | 1 + ProcessMaker/Jobs/DownloadSecurityLog.php | 248 +++++++++++------- .../Feature/Jobs/DownloadSecurityLogTest.php | 68 ++--- 4 files changed, 263 insertions(+), 129 deletions(-) create mode 100644 ProcessMaker/Events/SecurityLogDownloadFailed.php diff --git a/ProcessMaker/Events/SecurityLogDownloadFailed.php b/ProcessMaker/Events/SecurityLogDownloadFailed.php new file mode 100644 index 0000000000..69a4611e6c --- /dev/null +++ b/ProcessMaker/Events/SecurityLogDownloadFailed.php @@ -0,0 +1,75 @@ +user = $user; + $this->success = $success; + $this->message = $message; + $this->link = $link; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel("ProcessMaker.Models.User.{$this->user->id}"); + } + + /** + * Set the event name + * + * @return string + */ + public function broadcastAs() + { + return 'SecurityLogDownloadFailed'; + } + + /** + * Set the data to broadcast with this event + * + * @return array + */ + public function broadcastWith() + { + return [ + 'success' => $this->success, + 'message' => $this->message, + 'link' => $this->link, + ]; + } +} diff --git a/ProcessMaker/Http/Controllers/Api/SecurityLogController.php b/ProcessMaker/Http/Controllers/Api/SecurityLogController.php index 69b904f809..f1da2d24fe 100644 --- a/ProcessMaker/Http/Controllers/Api/SecurityLogController.php +++ b/ProcessMaker/Http/Controllers/Api/SecurityLogController.php @@ -150,6 +150,7 @@ private function download(Request $request, User $user = null) 'format' => 'required|string|in:xml,csv', ]); $sessionUser = Auth::user(); + // Call the Event DownloadSecurityLog::dispatch($sessionUser, $request->input('format'), $user ? $user->id : null) ->delay(now()->addSeconds(5)); diff --git a/ProcessMaker/Jobs/DownloadSecurityLog.php b/ProcessMaker/Jobs/DownloadSecurityLog.php index 51a8019fcc..8684546972 100644 --- a/ProcessMaker/Jobs/DownloadSecurityLog.php +++ b/ProcessMaker/Jobs/DownloadSecurityLog.php @@ -5,13 +5,14 @@ use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; +use ProcessMaker\Events\SecurityLogDownloadFailed; use ProcessMaker\Events\SecurityLogDownloadJobCompleted; use ProcessMaker\Models\Media; use ProcessMaker\Models\SecurityLog; @@ -31,8 +32,7 @@ class DownloadSecurityLog implements ShouldQueue private ?int $userId; - public const CSV_SEPARATOR = '|'; - + public const CSV_SEPARATOR = ','; public const FORMAT_CSV = 'csv'; public const FORMAT_XML = 'xml'; @@ -56,157 +56,211 @@ public function __construct(User $user, string $format, int $userId = null) */ public function handle() { + // Check if the S3 is ready to use if (!Media::s3IsReady()) { - event(new SecurityLogDownloadJobCompleted($this->user, false, __('Sorry, this feature requires the configured AWS S3 service.. Please contact the administrator.'))); + event(new SecurityLogDownloadFailed($this->user, false, __('Sorry, this feature requires the configured AWS S3 service. Please contact the administrator.'))); return; } try { - $collection = $this->getCollection(); - $contents = $this->generateContent($collection); - $expires = $this->getExpires(); + // Get the temp filename $filename = $this->createTemporaryFilename(); - $url = $this->createTemporaryUrl($filename, $contents, $expires); - $message = __('Click on the link and download the file. This link will be available until midnight tonight.'); + // Get the date of expiration + $expires = $this->getExpires(); + // Export the file and get the URL + $url = $this->export($filename, $expires); + $message = __('Click on the link and download the file. This link will be available until '. $expires->toString()); + // Call the event event(new SecurityLogDownloadJobCompleted($this->user, true, $message, $url)); } catch (Exception $e) { - $message = __('Sorry, it was not possible to generate the log file. Please contact the administrator.'); - event(new SecurityLogDownloadJobCompleted($this->user, false, $message)); + $message = __('Sorry, it was not possible to connect AWS S3 service. Please contact the administrator.'); + event(new SecurityLogDownloadFailed($this->user, false, $e->getMessage())); } } /** - * Generate the content with the corresponding format + * Get expires time + * + * @return Carbon time */ - protected function generateContent(array $collection) + protected function getExpires() { - return $this->format === static::FORMAT_CSV ? $this->toCSV($collection) : $this->toXML($collection); + return now()->addHours(24); } /** - * To CSV + * Create a temp file + * + * @return string */ - protected function toCSV(array $collection) + protected function createTemporaryFilename() { - $first = current($collection); - $headers = array_keys($first ?: []); - $content = ''; - $line = []; - foreach ($headers as $key) { - $line[] = $key; - } - $content .= implode(static::CSV_SEPARATOR, $line); - foreach ($collection as $item) { - $line = []; - foreach ($headers as $key) { - if (is_object($item[$key])) { - $line[] = json_encode($item[$key]); - } else { - $line[] = isset($item[$key]) ? $item[$key] : null; - } - } - $content .= PHP_EOL . implode(static::CSV_SEPARATOR, $line); - } - return $content; + $uuid = Uuid::uuid4()->toString() . Str::random(8); + + return 'security-logs/' . $uuid . '.' . $this->format; } /** - * To XML + * Export the file and get the URL + * + * @param string $filename + * @param Carbon $expires + * + * @return URL */ - protected function toXML(array $collection) + protected function export(string $filename, Carbon $expires) { - $content = '' . PHP_EOL; - $content .= ''; - $tab = "\t"; - foreach ($collection as $item) { - $content .= PHP_EOL . $tab . ''; - foreach ($item as $key => $value) { - if (is_object($value)) { - $value = json_encode($value); - } - $content .= sprintf( - '%s<%s>%s', - PHP_EOL . $tab, - $key, - $value, - $key - ); - } - $content .= PHP_EOL . $tab . ''; - } - $content .= PHP_EOL . ''; + // Get a disk manager for S3 + $disk = Storage::disk('s3'); - return $content; + // Create a stream + $stream = fopen('php://temp', 'w+'); + + // Write the content + $stream = $this->writeContent($stream); + + // Rewind the stream + rewind($stream); + + // Save the stream to S3 + $disk->put($filename, stream_get_contents($stream), [ + 'ACL' => 'private', // private|public-read, + 'Expires' => $expires->toString() + ]); + + // Close the stream + fclose($stream); + + // Save temporary Url + $url = $disk->temporaryUrl( + $filename, + $expires, + [ + 'ResponseContentType' => 'application/octet-stream', + 'ResponseContentDisposition' => 'attachment; filename=' . $filename, + ] + ); + + return $url; } /** - * Get collection + * Generate the content according to the format * - * @return array + * @return string */ - protected function getCollection() + protected function writeContent($stream) { - $query = SecurityLog::query(); + $query = DB::table('security_logs'); + + // Check the filter per user if ($this->userId) { $query->where('user_id', $this->userId); } - $results = []; - $query->chunk(1000, function (Collection $collection) use (&$results) { - foreach ($collection as $item) { - $results[] = $item->toArray(); - } - unset($item); + + // Initial tags for XML + $this->initialTagsXML($this->format === static::FORMAT_XML, $stream); + + // Use a cursor to iterate over the table data + $query->orderBy('id')->cursor()->each(function ($record) use ($stream) { + // Convert each record to an array and write it to the stream + $stream = $this->format === static::FORMAT_CSV ? $this->toCSV($stream, (array) $record) : $this->toXML($stream, (array) $record); }); - return $results; + // End tags for XML + $this->endTagsXML($this->format === static::FORMAT_XML, $stream); + + return $stream; } /** - * Create temp url + * Write the CSV line * - * @param string $filename - * @param string $content - * @param Carbon $expires + * @param string $stream + * @param array $record * * @return string */ - protected function createTemporaryUrl(string $filename, string $content, Carbon $expires) + protected function toCSV($stream, array $record) { - $disk = Storage::disk('s3'); - $disk->put($filename, $content, [ - 'ACL' => 'private', // private|public-read, - 'Expires' => $expires->toString() - ]); - $url = $disk->temporaryUrl( - $filename, - $expires, - [ - 'ResponseContentType' => 'application/octet-stream', - 'ResponseContentDisposition' => 'attachment; filename=' . $filename, - ] - ); + fputcsv($stream, (array) $record, static::CSV_SEPARATOR); - return $url; + return $stream; } /** - * Get expires time + * Write the XML node * - * @return Carbon time + * @param string $stream + * @param array $record + * + * @return string */ - protected function getExpires() + protected function toXML($stream, array $record) { - return now()->addHours(24); + $content = $this->getXmlNode((array) $record); + fwrite($stream, $content); + + return $stream; } /** - * Create a temp file + * Write the initial tags to XML + * + * @param bool $write + * @param string $stream + * + * @return void + */ + protected function initialTagsXML($write = false, $stream) + { + if ($write) { + $contentXml = '' . PHP_EOL; + $contentXml .= ''; + fwrite($stream, $contentXml); + } + } + + /** + * Write the end tags to XML + * + * @param bool $write + * @param string $stream + * + * @return void + */ + protected function endTagsXML($write = false, $stream) + { + if ($write) { + $contentXml = PHP_EOL . ''; + fwrite($stream, $contentXml); + } + } + + /** + * Get XML node + * + * @param array $item * * @return string */ - protected function createTemporaryFilename() + protected function getXmlNode(array $item) { - $uuid = Uuid::uuid4()->toString() . Str::random(8); + $tab = "\t"; + $content = PHP_EOL . $tab . ''; + foreach ($item as $key => $value) { + if (is_object($value)) { + $value = json_encode($value); + } + $content .= sprintf( + '%s<%s>%s', + PHP_EOL . $tab, + $key, + $value, + $key + ); + } + $content .= PHP_EOL . $tab . ''; - return 'security-logs/' . $uuid . '.' . $this->format; + return $content; } } diff --git a/tests/Feature/Jobs/DownloadSecurityLogTest.php b/tests/Feature/Jobs/DownloadSecurityLogTest.php index 076ef35089..6e4c508f56 100644 --- a/tests/Feature/Jobs/DownloadSecurityLogTest.php +++ b/tests/Feature/Jobs/DownloadSecurityLogTest.php @@ -44,62 +44,66 @@ public function testCreateTemporaryFilename() $this->assertStringContainsString('.xml', $filename); } - public function testGetCollection() + public function testExpires() { $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); - $results = (new ReflectionMethod($job, 'getCollection'))->invoke($job); - $this->assertCount(4, $results); + $method = new ReflectionMethod($job, 'getExpires'); + $expires = $method->invoke($job); + $this->assertLessThan($expires, now()); - $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV, $this->user->id); - $results = (new ReflectionMethod($job, 'getCollection'))->invoke($job); - $this->assertCount(2, $results); + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_XML); + $method = new ReflectionMethod($job, 'getExpires'); + $expires = $method->invoke($job); + $this->assertLessThan($expires, now()); } - public function testToCSV() + /** + * @covers DownloadSecurityLog::toCSV + */ + public function testWriteContentCSV() { - $collection = $this->simpleCollection; + $stream = fopen('php://temp', 'w+'); $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); - $csv = (new ReflectionMethod($job, 'toCSV'))->invoke($job, $collection); - $this->assertEquals( - 'id|event' . PHP_EOL . - '1|login' . PHP_EOL . - '2|logout', - $csv - ); + $csv = (new ReflectionMethod($job, 'writeContent'))->invoke($job, $stream); + $this->assertNotEmpty($csv); + $this->assertTrue(rewind($stream)); + $this->assertTrue(fclose($stream)); } - public function testToXML() + /** + * @covers DownloadSecurityLog::initialTagsXML + * @covers DownloadSecurityLog::toXML + * @covers DownloadSecurityLog::endTagsXML + */ + public function testWriteContentXML() { - $collection = $this->simpleCollection; - $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); - $xml = (new ReflectionMethod($job, 'toXML'))->invoke($job, $collection); - $this->assertStringContainsString('', $xml); - $this->assertStringContainsString('2', $xml); + $stream = fopen('php://temp', 'w+'); + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_XML); + $xml = (new ReflectionMethod($job, 'writeContent'))->invoke($job, $stream); + $this->assertNotEmpty($xml); + $this->assertTrue(rewind($stream)); + $this->assertTrue(fclose($stream)); } - public function testCreateTemporaryUrl() + public function testHandleWithSuccess() { //The test should not be run as it requires an AWS key $this->assertTrue(true, 'ignoring'); return; - $collection = $this->simpleCollection; + $this->expectsEvents(SecurityLogDownloadJobCompleted::class); $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); - $filename = (new ReflectionMethod($job, 'createTemporaryFilename'))->invoke($job); - $expires = (new ReflectionMethod($job, 'getExpires'))->invoke($job); - $csv = (new ReflectionMethod($job, 'toCSV'))->invoke($job, $collection); - $url = (new ReflectionMethod($job, 'createTemporaryUrl'))->invoke($job, $filename, $csv, $expires); - $this->assertStringContainsString('s3.amazonaws.com/security-logs', $url); - $disk = Storage::disk('s3'); - $this->assertTrue($disk->exists($filename)); + (new ReflectionMethod($job, 'handle'))->invoke($job); } - public function testHandleWithSuccess() + public function testExport() { //The test should not be run as it requires an AWS key $this->assertTrue(true, 'ignoring'); return; $this->expectsEvents(SecurityLogDownloadJobCompleted::class); $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); - (new ReflectionMethod($job, 'handle'))->invoke($job); + $filename = (new ReflectionMethod($job, 'createTemporaryFilename'))->invoke($job); + $expires = (new ReflectionMethod($job, 'getExpires'))->invoke($job); + (new ReflectionMethod($job, 'export'))->invoke($job, $filename, $expires); } } From 7d19a0cfdeb896ac73aff11cb7a31cd5888e73c2 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Tue, 27 Jun 2023 16:43:13 -0400 Subject: [PATCH 09/27] Adding league/flysystem-aws-s3-v3 --- composer.json | 1 + composer.lock | 277 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 277 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ff92f28e57..766ab60d3f 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "laravelcollective/html": "^6.3", "lavary/laravel-menu": "^1.8", "lcobucci/jwt": "^4.2", + "league/flysystem-aws-s3-v3": "^1.0", "mateusjunges/laravel-kafka": "^1.9", "microsoft/microsoft-graph": "^1.77", "moontoast/math": "^1.2", diff --git a/composer.lock b/composer.lock index bc2e41b51b..1b09d893c6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "28c38d38f6d6836d788869f1b1b19435", + "content-hash": "ecf10e5a16c7ac3a853670610c981858", "packages": [ { "name": "asm89/stack-cors", @@ -62,6 +62,155 @@ }, "time": "2022-01-18T09:12:03+00:00" }, + { + "name": "aws/aws-crt-php", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "1926277fc71d253dfa820271ac5987bdb193ccf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/1926277fc71d253dfa820271ac5987bdb193ccf5", + "reference": "1926277fc71d253dfa820271ac5987bdb193ccf5", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.1" + }, + "time": "2023-03-24T20:22:19+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.274.0", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "b2f37a49ca40bce633323a6988477c22be562c37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b2f37a49ca40bce633323a6988477c22be562c37", + "reference": "b2f37a49ca40bce633323a6988477c22be562c37", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.0.4", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "mtdowling/jmespath.php": "^2.6", + "php": ">=5.5", + "psr/http-message": "^1.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.274.0" + }, + "time": "2023-06-27T18:32:17+00:00" + }, { "name": "babenkoivan/elastic-adapter", "version": "v3.5.0", @@ -3924,6 +4073,71 @@ ], "time": "2022-10-04T09:16:37+00:00" }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.30", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "af286f291ebab6877bac0c359c6c2cb017eb061d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/af286f291ebab6877bac0c359c6c2cb017eb061d", + "reference": "af286f291ebab6877bac0c359c6c2cb017eb061d", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.20.0", + "league/flysystem": "^1.0.40", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "support": { + "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.30" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-07-02T13:51:38+00:00" + }, { "name": "league/fractal", "version": "0.20.1", @@ -4732,6 +4946,67 @@ "abandoned": "brick/math", "time": "2020-01-05T04:49:34+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + }, + "time": "2021-06-14T00:11:39+00:00" + }, { "name": "mustache/mustache", "version": "v2.14.2", From 40d3131293fdbf7c48252de64f0cf5f9ca10e86c Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Tue, 27 Jun 2023 17:29:10 -0400 Subject: [PATCH 10/27] test --- .../Controllers/Api/SecurityLogController.php | 2 +- ProcessMaker/Jobs/DownloadSecurityLog.php | 3 +- .../Feature/Jobs/DownloadSecurityLogTest.php | 50 +++++++++++++------ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/ProcessMaker/Http/Controllers/Api/SecurityLogController.php b/ProcessMaker/Http/Controllers/Api/SecurityLogController.php index f1da2d24fe..918e476763 100644 --- a/ProcessMaker/Http/Controllers/Api/SecurityLogController.php +++ b/ProcessMaker/Http/Controllers/Api/SecurityLogController.php @@ -157,7 +157,7 @@ private function download(Request $request, User $user = null) return response()->json([ 'success' => true, - 'message' => __('The log file is being prepared and will be sent to your email as soon as it is ready.') + 'message' => __('The file is processing... Please wait for an alert with the download link.') ]); } diff --git a/ProcessMaker/Jobs/DownloadSecurityLog.php b/ProcessMaker/Jobs/DownloadSecurityLog.php index 8684546972..9d9a56ea9c 100644 --- a/ProcessMaker/Jobs/DownloadSecurityLog.php +++ b/ProcessMaker/Jobs/DownloadSecurityLog.php @@ -33,6 +33,7 @@ class DownloadSecurityLog implements ShouldQueue private ?int $userId; public const CSV_SEPARATOR = ','; + public const EXPIRATION_HOURS = 24; public const FORMAT_CSV = 'csv'; public const FORMAT_XML = 'xml'; @@ -84,7 +85,7 @@ public function handle() */ protected function getExpires() { - return now()->addHours(24); + return now()->addHours(static::EXPIRATION_HOURS); } /** diff --git a/tests/Feature/Jobs/DownloadSecurityLogTest.php b/tests/Feature/Jobs/DownloadSecurityLogTest.php index 6e4c508f56..9011b5a94c 100644 --- a/tests/Feature/Jobs/DownloadSecurityLogTest.php +++ b/tests/Feature/Jobs/DownloadSecurityLogTest.php @@ -87,23 +87,45 @@ public function testWriteContentXML() public function testHandleWithSuccess() { - //The test should not be run as it requires an AWS key - $this->assertTrue(true, 'ignoring'); - return; - $this->expectsEvents(SecurityLogDownloadJobCompleted::class); - $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); - (new ReflectionMethod($job, 'handle'))->invoke($job); + if ( + !config('filesystems.disks.s3.key') + && !config('filesystems.disks.s3.secret') + && !config('filesystems.disks.s3.region') + && !config('filesystems.disks.s3.bucket') + ) { + $this->markTestSkipped( + 'AWS S3 service is not available.' + ); + } else { + $this->expectsEvents(SecurityLogDownloadJobCompleted::class); + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); + $url = (new ReflectionMethod($job, 'handle'))->invoke($job); + $this->assertNotEmpty($url); + $data = file_get_contents($url); + $this->assertNotEmpty($data); + } } public function testExport() { - //The test should not be run as it requires an AWS key - $this->assertTrue(true, 'ignoring'); - return; - $this->expectsEvents(SecurityLogDownloadJobCompleted::class); - $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); - $filename = (new ReflectionMethod($job, 'createTemporaryFilename'))->invoke($job); - $expires = (new ReflectionMethod($job, 'getExpires'))->invoke($job); - (new ReflectionMethod($job, 'export'))->invoke($job, $filename, $expires); + if ( + !config('filesystems.disks.s3.key') + && !config('filesystems.disks.s3.secret') + && !config('filesystems.disks.s3.region') + && !config('filesystems.disks.s3.bucket') + ) { + $this->markTestSkipped( + 'AWS S3 service is not available.' + ); + } else { + $this->expectsEvents(SecurityLogDownloadJobCompleted::class); + $job = new DownloadSecurityLog($this->user, DownloadSecurityLog::FORMAT_CSV); + $filename = (new ReflectionMethod($job, 'createTemporaryFilename'))->invoke($job); + $expires = (new ReflectionMethod($job, 'getExpires'))->invoke($job); + $url = (new ReflectionMethod($job, 'export'))->invoke($job, $filename, $expires); + $this->assertNotEmpty($url); + $data = file_get_contents($url); + $this->assertNotEmpty($data); + } } } From fff22973ca8935cbefdda4be74244e37dabe70ac Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Wed, 28 Jun 2023 16:20:18 -0400 Subject: [PATCH 11/27] Ready to PR: FOUR-9161: Create a Helper to get the all the categories --- ProcessMaker/Helpers/ArrayHelper.php | 32 ++++++++++ .../ProcessMaker/Helpers/ArrayHelperTest.php | 58 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/ProcessMaker/Helpers/ArrayHelper.php b/ProcessMaker/Helpers/ArrayHelper.php index 0eb2417eaf..95e144dc8d 100644 --- a/ProcessMaker/Helpers/ArrayHelper.php +++ b/ProcessMaker/Helpers/ArrayHelper.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Helpers; +use Illuminate\Support\Facades\Schema; use ProcessMaker\Traits\FormatSecurityLogChanges; use stdClass; @@ -39,4 +40,35 @@ public static function getArrayDifferencesWithFormat(array $changedArray, array $arrayDiff = $arrayHelper->formatChanges($displayChanges, $displayOriginal); return $arrayDiff; } + + /** + * This method swaps groups of id numbers "1,2,3" for specific column names values "'A','B','C'" + * using the Tables information through Models + * The method returns a String value + * The packageName parameter is optional, only use with Packages. Default value Null + * @param string $modelName + * @param string $ids + * @param string $columnName + * @param string $packageName + * @return string + */ + public static function getNamesByIds(string $modelName, string $ids, string $columnName, string $packageName = null): string + { + $arrayIds = explode(',', $ids); + $resultString = ''; + if (is_null($packageName)) { + $modelClass = 'ProcessMaker\\Models\\' . $modelName; + } else { + $modelClass = 'ProcessMaker\\Package\\' . $packageName . '\\Models\\' . $modelName; + } + + if (class_exists($modelClass)) { + $tableName = (new $modelClass)->getTable(); + if((Schema::hasColumn($tableName, $columnName))){ + $results = $modelClass::whereIn('id', array_map('intval', $arrayIds))->pluck($columnName); + $resultString = implode(', ', $results->toArray()); + } + } + return $resultString; + } } diff --git a/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php b/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php index 5db0b2fcb1..12ef5ee68b 100644 --- a/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php +++ b/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php @@ -71,4 +71,62 @@ public function testGetArrayDifferencesWithFormat() ] )); } + + public function testGetNamesByIds() + { + //Case 1: Existing Model, Existing Name, More than one ID + $stringModel = 'ProcessCategory'; + $stringIds = '1,3'; + $columnName = 'name'; + $this->assertEquals( + "Default Templates, Uncategorized", + ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) + ); + + //Case 2: Existing Model, Existing Name, one ID + $stringModel = 'ProcessCategory'; + $stringIds = '3'; + $columnName = 'name'; + $this->assertEquals( + "Uncategorized", + ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) + ); + + //Case 3: Not Existing Model, Existing Name, More than one ID + $stringModel = 'ProcessCategoryFake'; + $stringIds = '1,3'; + $columnName = 'name'; + $this->assertEquals( + "", + ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) + ); + + //Case 4: Not Existing Model, Not Existing Name, More than one ID + $stringModel = 'ProcessCategoryFake'; + $stringIds = '1,3'; + $columnName = 'nameFake'; + $this->assertEquals( + "", + ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) + ); + + //Case 5: Not Existing Model, Not Existing Name, Not existing ID + $stringModel = 'ProcessCategoryFake'; + $stringIds = ''; + $columnName = 'nameFake'; + $this->assertEquals( + "", + ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) + ); + + //Case 6: Not Existing Package + $stringModel = 'ProcessCategoryFake'; + $stringIds = ''; + $columnName = 'nameFake'; + $packageName = 'packageFake'; + $this->assertEquals( + "", + ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName, $packageName) + ); + } } From b8bb6aed22961c59c0a4c997b892c6ac65aaa4e0 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Wed, 28 Jun 2023 16:59:07 -0400 Subject: [PATCH 12/27] Fixing assert in Test --- tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php b/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php index 12ef5ee68b..ef46a24db5 100644 --- a/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php +++ b/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php @@ -76,10 +76,10 @@ public function testGetNamesByIds() { //Case 1: Existing Model, Existing Name, More than one ID $stringModel = 'ProcessCategory'; - $stringIds = '1,3'; + $stringIds = '1,2'; $columnName = 'name'; $this->assertEquals( - "Default Templates, Uncategorized", + "Default Templates, System", ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) ); @@ -94,7 +94,7 @@ public function testGetNamesByIds() //Case 3: Not Existing Model, Existing Name, More than one ID $stringModel = 'ProcessCategoryFake'; - $stringIds = '1,3'; + $stringIds = '1,2'; $columnName = 'name'; $this->assertEquals( "", From 6f6e6c37415d9498bac459d982dd31ce2ba8f68b Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Wed, 28 Jun 2023 17:00:14 -0400 Subject: [PATCH 13/27] Fixing assert in Test --- tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php b/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php index ef46a24db5..9e0d489693 100644 --- a/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php +++ b/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php @@ -85,10 +85,10 @@ public function testGetNamesByIds() //Case 2: Existing Model, Existing Name, one ID $stringModel = 'ProcessCategory'; - $stringIds = '3'; + $stringIds = '1'; $columnName = 'name'; $this->assertEquals( - "Uncategorized", + "Default Templates", ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) ); From 6ea2500c2c73ca5442b838f3eccebb4cf891b995 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Thu, 29 Jun 2023 10:02:17 -0400 Subject: [PATCH 14/27] Modified ScreenCategory Model instead of array --- ProcessMaker/Models/ScreenCategory.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ProcessMaker/Models/ScreenCategory.php b/ProcessMaker/Models/ScreenCategory.php index 5e6df9da5e..d2c7bfee37 100644 --- a/ProcessMaker/Models/ScreenCategory.php +++ b/ProcessMaker/Models/ScreenCategory.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Models; +use Illuminate\Support\Facades\Schema; use Illuminate\Validation\Rule; use ProcessMaker\Models\Screen; use ProcessMaker\Traits\Exportable; @@ -67,4 +68,13 @@ public function screens() { return $this->morphedByMany(Screen::class, 'assignable', 'category_assignments', 'category_id'); } + + public function getNamesByIds(string $ids, string $delimiter = ','): string + { + $resultString = ''; + $arrayIds = explode($delimiter, $ids); + $results = ScreenCategory::whereIn('id', array_map('intval', $arrayIds))->pluck('name'); + $resultString = implode(', ', $results->toArray()); + return $resultString; + } } From 3c07d22673fadbd4958d6fa7d3b29ed836a31319 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Thu, 29 Jun 2023 10:08:11 -0400 Subject: [PATCH 15/27] Model modify --- ProcessMaker/Models/ScreenCategory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProcessMaker/Models/ScreenCategory.php b/ProcessMaker/Models/ScreenCategory.php index d2c7bfee37..31dcaa3a94 100644 --- a/ProcessMaker/Models/ScreenCategory.php +++ b/ProcessMaker/Models/ScreenCategory.php @@ -76,5 +76,5 @@ public function getNamesByIds(string $ids, string $delimiter = ','): string $results = ScreenCategory::whereIn('id', array_map('intval', $arrayIds))->pluck('name'); $resultString = implode(', ', $results->toArray()); return $resultString; - } + } } From ab843065445bc4b1da2f0961b2cb5daf18fcf3fa Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Thu, 29 Jun 2023 12:37:39 -0400 Subject: [PATCH 16/27] Refactoring calls to change Category Id's to names --- ProcessMaker/Events/ScreenUpdated.php | 1 + ProcessMaker/Helpers/ArrayHelper.php | 31 ---------- ProcessMaker/Models/ProcessCategory.php | 15 +++++ ProcessMaker/Models/ScreenCategory.php | 8 ++- ProcessMaker/Models/ScriptCategory.php | 15 +++++ .../ProcessMaker/Helpers/ArrayHelperTest.php | 58 ------------------- .../Models/ProcessCategoryTest.php | 45 ++++++++++++++ .../Models/ScreenCategoryTest.php | 45 ++++++++++++++ .../Models/ScriptCategoryTest.php | 45 ++++++++++++++ 9 files changed, 173 insertions(+), 90 deletions(-) create mode 100644 tests/unit/ProcessMaker/Models/ProcessCategoryTest.php create mode 100644 tests/unit/ProcessMaker/Models/ScreenCategoryTest.php create mode 100644 tests/unit/ProcessMaker/Models/ScriptCategoryTest.php diff --git a/ProcessMaker/Events/ScreenUpdated.php b/ProcessMaker/Events/ScreenUpdated.php index a7dfe5e054..54c4be6dee 100644 --- a/ProcessMaker/Events/ScreenUpdated.php +++ b/ProcessMaker/Events/ScreenUpdated.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Screen; +use ProcessMaker\Models\ScreenCategory; use ProcessMaker\Traits\FormatSecurityLogChanges; class ScreenUpdated implements SecurityLogEventInterface diff --git a/ProcessMaker/Helpers/ArrayHelper.php b/ProcessMaker/Helpers/ArrayHelper.php index 95e144dc8d..297b20940d 100644 --- a/ProcessMaker/Helpers/ArrayHelper.php +++ b/ProcessMaker/Helpers/ArrayHelper.php @@ -40,35 +40,4 @@ public static function getArrayDifferencesWithFormat(array $changedArray, array $arrayDiff = $arrayHelper->formatChanges($displayChanges, $displayOriginal); return $arrayDiff; } - - /** - * This method swaps groups of id numbers "1,2,3" for specific column names values "'A','B','C'" - * using the Tables information through Models - * The method returns a String value - * The packageName parameter is optional, only use with Packages. Default value Null - * @param string $modelName - * @param string $ids - * @param string $columnName - * @param string $packageName - * @return string - */ - public static function getNamesByIds(string $modelName, string $ids, string $columnName, string $packageName = null): string - { - $arrayIds = explode(',', $ids); - $resultString = ''; - if (is_null($packageName)) { - $modelClass = 'ProcessMaker\\Models\\' . $modelName; - } else { - $modelClass = 'ProcessMaker\\Package\\' . $packageName . '\\Models\\' . $modelName; - } - - if (class_exists($modelClass)) { - $tableName = (new $modelClass)->getTable(); - if((Schema::hasColumn($tableName, $columnName))){ - $results = $modelClass::whereIn('id', array_map('intval', $arrayIds))->pluck($columnName); - $resultString = implode(', ', $results->toArray()); - } - } - return $resultString; - } } diff --git a/ProcessMaker/Models/ProcessCategory.php b/ProcessMaker/Models/ProcessCategory.php index ab82b452f4..e0d45fbd29 100644 --- a/ProcessMaker/Models/ProcessCategory.php +++ b/ProcessMaker/Models/ProcessCategory.php @@ -68,4 +68,19 @@ public function processes() { return $this->morphedByMany(Process::class, 'assignable', 'category_assignments', 'category_id'); } + + /** + * Get Process Category Names + * @param string String of ids separated by a custom delimiter. + * @param string Delimiter to split ids. By default ',' + * @return string A string separated by commas with Process Category Names + */ + public static function getNamesByIds(string $ids, string $delimiter = ','): string + { + $resultString = ''; + $arrayIds = explode($delimiter, $ids); + $results = ProcessCategory::whereIn('id', array_map('intval', $arrayIds))->pluck('name'); + $resultString = implode(', ', $results->toArray()); + return $resultString; + } } diff --git a/ProcessMaker/Models/ScreenCategory.php b/ProcessMaker/Models/ScreenCategory.php index 31dcaa3a94..80c362f468 100644 --- a/ProcessMaker/Models/ScreenCategory.php +++ b/ProcessMaker/Models/ScreenCategory.php @@ -69,7 +69,13 @@ public function screens() return $this->morphedByMany(Screen::class, 'assignable', 'category_assignments', 'category_id'); } - public function getNamesByIds(string $ids, string $delimiter = ','): string + /** + * Get Screen Category Names + * @param string String of ids separated by a custom delimiter. + * @param string Delimiter to split ids. By default ',' + * @return string A string separated by commas with Screen Category Names + */ + public static function getNamesByIds(string $ids, string $delimiter = ','): string { $resultString = ''; $arrayIds = explode($delimiter, $ids); diff --git a/ProcessMaker/Models/ScriptCategory.php b/ProcessMaker/Models/ScriptCategory.php index 87c1712027..1e2954bab2 100644 --- a/ProcessMaker/Models/ScriptCategory.php +++ b/ProcessMaker/Models/ScriptCategory.php @@ -67,4 +67,19 @@ public function scripts() { return $this->morphedByMany(Script::class, 'assignable', 'category_assignments', 'category_id'); } + + /** + * Get Script Category Names + * @param string String of ids separated by a custom delimiter. + * @param string Delimiter to split ids. By default ',' + * @return string A string separated by commas with Script Category Names + */ + public static function getNamesByIds(string $ids, string $delimiter = ','): string + { + $resultString = ''; + $arrayIds = explode($delimiter, $ids); + $results = ScriptCategory::whereIn('id', array_map('intval', $arrayIds))->pluck('name'); + $resultString = implode(', ', $results->toArray()); + return $resultString; + } } diff --git a/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php b/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php index 9e0d489693..5db0b2fcb1 100644 --- a/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php +++ b/tests/unit/ProcessMaker/Helpers/ArrayHelperTest.php @@ -71,62 +71,4 @@ public function testGetArrayDifferencesWithFormat() ] )); } - - public function testGetNamesByIds() - { - //Case 1: Existing Model, Existing Name, More than one ID - $stringModel = 'ProcessCategory'; - $stringIds = '1,2'; - $columnName = 'name'; - $this->assertEquals( - "Default Templates, System", - ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) - ); - - //Case 2: Existing Model, Existing Name, one ID - $stringModel = 'ProcessCategory'; - $stringIds = '1'; - $columnName = 'name'; - $this->assertEquals( - "Default Templates", - ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) - ); - - //Case 3: Not Existing Model, Existing Name, More than one ID - $stringModel = 'ProcessCategoryFake'; - $stringIds = '1,2'; - $columnName = 'name'; - $this->assertEquals( - "", - ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) - ); - - //Case 4: Not Existing Model, Not Existing Name, More than one ID - $stringModel = 'ProcessCategoryFake'; - $stringIds = '1,3'; - $columnName = 'nameFake'; - $this->assertEquals( - "", - ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) - ); - - //Case 5: Not Existing Model, Not Existing Name, Not existing ID - $stringModel = 'ProcessCategoryFake'; - $stringIds = ''; - $columnName = 'nameFake'; - $this->assertEquals( - "", - ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName) - ); - - //Case 6: Not Existing Package - $stringModel = 'ProcessCategoryFake'; - $stringIds = ''; - $columnName = 'nameFake'; - $packageName = 'packageFake'; - $this->assertEquals( - "", - ArrayHelper::getNamesByIds($stringModel, $stringIds, $columnName, $packageName) - ); - } } diff --git a/tests/unit/ProcessMaker/Models/ProcessCategoryTest.php b/tests/unit/ProcessMaker/Models/ProcessCategoryTest.php new file mode 100644 index 0000000000..8888f22757 --- /dev/null +++ b/tests/unit/ProcessMaker/Models/ProcessCategoryTest.php @@ -0,0 +1,45 @@ +assertEquals( + "Uncategorized", + ProcessCategory::getNamesByIds($stringIds) + ); + + //Case 2: multiple Id + $stringIds = '1,3'; + $this->assertEquals( + "Uncategorized", + ProcessCategory::getNamesByIds($stringIds) + ); + + //Case 3: without Id + $stringIds = ''; + $this->assertEquals( + "Uncategorized", + ProcessCategory::getNamesByIds($stringIds) + ); + + //Case 4: non-existentId + $stringIds = '9452'; + $this->assertEquals( + "Uncategorized", + ProcessCategory::getNamesByIds($stringIds) + ); + } +} diff --git a/tests/unit/ProcessMaker/Models/ScreenCategoryTest.php b/tests/unit/ProcessMaker/Models/ScreenCategoryTest.php new file mode 100644 index 0000000000..953ede2018 --- /dev/null +++ b/tests/unit/ProcessMaker/Models/ScreenCategoryTest.php @@ -0,0 +1,45 @@ +assertEquals( + "Uncategorized", + ScreenCategory::getNamesByIds($stringIds) + ); + + //Case 2: multiple Id + $stringIds = '1,3'; + $this->assertEquals( + "Uncategorized", + ScreenCategory::getNamesByIds($stringIds) + ); + + //Case 3: without Id + $stringIds = ''; + $this->assertEquals( + "Uncategorized", + ScreenCategory::getNamesByIds($stringIds) + ); + + //Case 4: non-existentId + $stringIds = '9452'; + $this->assertEquals( + "Uncategorized", + ScreenCategory::getNamesByIds($stringIds) + ); + } +} diff --git a/tests/unit/ProcessMaker/Models/ScriptCategoryTest.php b/tests/unit/ProcessMaker/Models/ScriptCategoryTest.php new file mode 100644 index 0000000000..d1e4ea7628 --- /dev/null +++ b/tests/unit/ProcessMaker/Models/ScriptCategoryTest.php @@ -0,0 +1,45 @@ +assertEquals( + "Uncategorized", + ScriptCategory::getNamesByIds($stringIds) + ); + + //Case 2: multiple Id + $stringIds = '1,3'; + $this->assertEquals( + "Uncategorized", + ScriptCategory::getNamesByIds($stringIds) + ); + + //Case 3: without Id + $stringIds = ''; + $this->assertEquals( + "Uncategorized", + ScriptCategory::getNamesByIds($stringIds) + ); + + //Case 4: non-existentId + $stringIds = '9452'; + $this->assertEquals( + "Uncategorized", + ScriptCategory::getNamesByIds($stringIds) + ); + } +} From 44b8a12058cbdc3eadbf5bf4af9d878c247a0040 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Thu, 29 Jun 2023 12:59:01 -0400 Subject: [PATCH 17/27] Removing unused namespaces --- ProcessMaker/Events/ScreenUpdated.php | 1 - ProcessMaker/Helpers/ArrayHelper.php | 1 - ProcessMaker/Models/ScreenCategory.php | 1 - 3 files changed, 3 deletions(-) diff --git a/ProcessMaker/Events/ScreenUpdated.php b/ProcessMaker/Events/ScreenUpdated.php index 54c4be6dee..a7dfe5e054 100644 --- a/ProcessMaker/Events/ScreenUpdated.php +++ b/ProcessMaker/Events/ScreenUpdated.php @@ -5,7 +5,6 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Screen; -use ProcessMaker\Models\ScreenCategory; use ProcessMaker\Traits\FormatSecurityLogChanges; class ScreenUpdated implements SecurityLogEventInterface diff --git a/ProcessMaker/Helpers/ArrayHelper.php b/ProcessMaker/Helpers/ArrayHelper.php index 297b20940d..0eb2417eaf 100644 --- a/ProcessMaker/Helpers/ArrayHelper.php +++ b/ProcessMaker/Helpers/ArrayHelper.php @@ -2,7 +2,6 @@ namespace ProcessMaker\Helpers; -use Illuminate\Support\Facades\Schema; use ProcessMaker\Traits\FormatSecurityLogChanges; use stdClass; diff --git a/ProcessMaker/Models/ScreenCategory.php b/ProcessMaker/Models/ScreenCategory.php index 80c362f468..39f7eb5623 100644 --- a/ProcessMaker/Models/ScreenCategory.php +++ b/ProcessMaker/Models/ScreenCategory.php @@ -2,7 +2,6 @@ namespace ProcessMaker\Models; -use Illuminate\Support\Facades\Schema; use Illuminate\Validation\Rule; use ProcessMaker\Models\Screen; use ProcessMaker\Traits\Exportable; From d5514d1fb859355676329a1fa4f801bd7d39ba90 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Wed, 28 Jun 2023 15:14:47 -0400 Subject: [PATCH 18/27] FOUR-9018 --- .../Providers/EventServiceProvider.php | 241 +++++++----------- config/app.php | 14 +- tests/Feature/Api/SecurityLogsTest.php | 16 +- 3 files changed, 120 insertions(+), 151 deletions(-) diff --git a/ProcessMaker/Providers/EventServiceProvider.php b/ProcessMaker/Providers/EventServiceProvider.php index bbdf5fb419..f27d127838 100644 --- a/ProcessMaker/Providers/EventServiceProvider.php +++ b/ProcessMaker/Providers/EventServiceProvider.php @@ -3,6 +3,54 @@ namespace ProcessMaker\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use ProcessMaker\Events\ActivityReassignment; +use ProcessMaker\Events\AuthClientCreated; +use ProcessMaker\Events\AuthClientDeleted; +use ProcessMaker\Events\AuthClientUpdated; +use ProcessMaker\Events\CategoryCreated; +use ProcessMaker\Events\CategoryDeleted; +use ProcessMaker\Events\CategoryUpdated; +use ProcessMaker\Events\CustomizeUiUpdated; +use ProcessMaker\Events\EnvironmentVariablesUpdated; +use ProcessMaker\Events\EnvironmentVariablesCreated; +use ProcessMaker\Events\EnvironmentVariablesDeleted; +use ProcessMaker\Events\FilesCreated; +use ProcessMaker\Events\FilesDeleted; +use ProcessMaker\Events\FilesUpdated; +use ProcessMaker\Events\GroupCreated; +use ProcessMaker\Events\GroupDeleted; +use ProcessMaker\Events\GroupUpdated; +use ProcessMaker\Events\GroupUsersUpdated; +use ProcessMaker\Events\PermissionUpdated; +use ProcessMaker\Events\ProcessArchived; +use ProcessMaker\Events\ProcessCreated; +use ProcessMaker\Events\ProcessPublished; +use ProcessMaker\Events\ProcessRestored; +use ProcessMaker\Events\ProcessUpdated; +use ProcessMaker\Events\RequestAction; +use ProcessMaker\Events\RequestError; +use ProcessMaker\Events\ScreenCreated; +use ProcessMaker\Events\ScreenDeleted; +use ProcessMaker\Events\ScreenUpdated; +use ProcessMaker\Events\ScriptCreated; +use ProcessMaker\Events\ScriptDeleted; +use ProcessMaker\Events\ScriptDuplicated; +use ProcessMaker\Events\ScriptExecutorCreated; +use ProcessMaker\Events\ScriptExecutorDeleted; +use ProcessMaker\Events\ScriptExecutorUpdated; +use ProcessMaker\Events\ScriptUpdated; +use ProcessMaker\Events\SettingsUpdated; +use ProcessMaker\Events\TemplateCreated; +use ProcessMaker\Events\TemplateDeleted; +use ProcessMaker\Events\TemplateUpdated; +use ProcessMaker\Events\TokenCreated; +use ProcessMaker\Events\TokenDeleted; +use ProcessMaker\Events\UnauthorizedAccessAttempt; +use ProcessMaker\Events\UserCreated; +use ProcessMaker\Events\UserDeleted; +use ProcessMaker\Events\UserGroupMembershipUpdated; +use ProcessMaker\Events\UserUpdated; +use ProcessMaker\Listeners\SecurityLogger; /** * Register our Events and their Listeners @@ -27,151 +75,9 @@ class EventServiceProvider extends ServiceProvider 'Illuminate\Database\Events\MigrationsEnded' => [ 'ProcessMaker\Listeners\UpdateDataLakeViews', ], - 'ProcessMaker\Events\ActivityReassignment' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\AuthClientUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\AuthClientCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\AuthClientDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\CategoryCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\CategoryDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\CategoryUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\CustomizeUiUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\EnvironmentVariablesCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\EnvironmentVariablesDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\EnvironmentVariablesUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\FilesCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\FilesDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\FilesUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\GroupCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\GroupDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\GroupUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\GroupUsersUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\PermissionUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ProcessCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ProcessArchived' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ProcessPublished' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ProcessRestored' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ProcessUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\RequestError' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\RequestAction' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScreenCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScreenDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScreenUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScriptUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScriptCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScriptDuplicated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScriptDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScriptExecutorCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScriptExecutorDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\ScriptExecutorUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], 'ProcessMaker\Events\SessionStarted' => [ 'ProcessMaker\Listeners\ActiveUserListener', ], - 'ProcessMaker\Events\SettingsUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\TemplateCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\TemplateDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\TemplateUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\TokenCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\TokenDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\UnauthorizedAccessAttempt' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\UserCreated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\UserDeleted' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\UserGroupMembershipUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - 'ProcessMaker\Events\UserUpdated' => [ - 'ProcessMaker\Listeners\SecurityLogger', - ], - ]; /** @@ -181,5 +87,56 @@ class EventServiceProvider extends ServiceProvider public function boot() { parent::boot(); + + // Check if the variable security_log is enable + if (config('app.security_log')) { + $this->app['events']->listen(ActivityReassignment::class, SecurityLogger::class); + $this->app['events']->listen(AuthClientUpdated::class, SecurityLogger::class); + $this->app['events']->listen(AuthClientCreated::class, SecurityLogger::class); + $this->app['events']->listen(AuthClientDeleted::class, SecurityLogger::class); + $this->app['events']->listen(CategoryCreated::class, SecurityLogger::class); + $this->app['events']->listen(CategoryDeleted::class, SecurityLogger::class); + $this->app['events']->listen(CategoryUpdated::class, SecurityLogger::class); + $this->app['events']->listen(CustomizeUiUpdated::class, SecurityLogger::class); + $this->app['events']->listen(EnvironmentVariablesCreated::class, SecurityLogger::class); + $this->app['events']->listen(EnvironmentVariablesDeleted::class, SecurityLogger::class); + $this->app['events']->listen(EnvironmentVariablesUpdated::class, SecurityLogger::class); + $this->app['events']->listen(FilesCreated::class, SecurityLogger::class); + $this->app['events']->listen(FilesDeleted::class, SecurityLogger::class); + $this->app['events']->listen(FilesUpdated::class, SecurityLogger::class); + $this->app['events']->listen(GroupCreated::class, SecurityLogger::class); + $this->app['events']->listen(GroupDeleted::class, SecurityLogger::class); + $this->app['events']->listen(GroupUpdated::class, SecurityLogger::class); + $this->app['events']->listen(GroupUsersUpdated::class, SecurityLogger::class); + $this->app['events']->listen(PermissionUpdated::class, SecurityLogger::class); + $this->app['events']->listen(ProcessCreated::class, SecurityLogger::class); + $this->app['events']->listen(ProcessArchived::class, SecurityLogger::class); + $this->app['events']->listen(ProcessPublished::class, SecurityLogger::class); + $this->app['events']->listen(ProcessRestored::class, SecurityLogger::class); + $this->app['events']->listen(ProcessUpdated::class, SecurityLogger::class); + $this->app['events']->listen(RequestError::class, SecurityLogger::class); + $this->app['events']->listen(RequestAction::class, SecurityLogger::class); + $this->app['events']->listen(ScreenCreated::class, SecurityLogger::class); + $this->app['events']->listen(ScreenDeleted::class, SecurityLogger::class); + $this->app['events']->listen(ScreenUpdated::class, SecurityLogger::class); + $this->app['events']->listen(ScriptCreated::class, SecurityLogger::class); + $this->app['events']->listen(ScriptDeleted::class, SecurityLogger::class); + $this->app['events']->listen(ScriptDuplicated::class, SecurityLogger::class); + $this->app['events']->listen(ScriptExecutorCreated::class, SecurityLogger::class); + $this->app['events']->listen(ScriptExecutorDeleted::class, SecurityLogger::class); + $this->app['events']->listen(ScriptExecutorUpdated::class, SecurityLogger::class); + $this->app['events']->listen(ScriptUpdated::class, SecurityLogger::class); + $this->app['events']->listen(SettingsUpdated::class, SecurityLogger::class); + $this->app['events']->listen(TemplateCreated::class, SecurityLogger::class); + $this->app['events']->listen(TemplateDeleted::class, SecurityLogger::class); + $this->app['events']->listen(TemplateUpdated::class, SecurityLogger::class); + $this->app['events']->listen(TokenCreated::class, SecurityLogger::class); + $this->app['events']->listen(TokenDeleted::class, SecurityLogger::class); + $this->app['events']->listen(UnauthorizedAccessAttempt::class, SecurityLogger::class); + $this->app['events']->listen(UserCreated::class, SecurityLogger::class); + $this->app['events']->listen(UserDeleted::class, SecurityLogger::class); + $this->app['events']->listen(UserGroupMembershipUpdated::class, SecurityLogger::class); + $this->app['events']->listen(UserUpdated::class, SecurityLogger::class); + } } } diff --git a/config/app.php b/config/app.php index dc5c365549..1ef765490f 100644 --- a/config/app.php +++ b/config/app.php @@ -85,9 +85,14 @@ 'bpmn_actions_lock_check_interval' => (int) env('BPMN_ACTIONS_LOCK_CHECK_INTERVAL', 1000), // The url of our host from inside the docker - 'docker_host_url' => env('DOCKER_HOST_URL', - preg_replace('/(\w+):\/\/([^:\/]+)(\:\d+)?/', '$1://172.17.0.1$3', - env('APP_URL', 'http://localhost'))), + 'docker_host_url' => env( + 'DOCKER_HOST_URL', + preg_replace( + '/(\w+):\/\/([^:\/]+)(\:\d+)?/', + '$1://172.17.0.1$3', + env('APP_URL', 'http://localhost') + ) + ), // Allows our script executors to ignore invalid SSL. This should only be set to false for development. 'api_ssl_verify' => env('API_SSL_VERIFY', 'true'), @@ -101,6 +106,9 @@ // Microservice AI Host 'ai_microservice_host' => env('AI_MICROSERVICE_HOST'), + // Security log + 'security_log' => env('SECURITY_LOG', 'true'), + // Message broker driver to use in Workflow Manager 'message_broker_driver' => env('MESSAGE_BROKER_DRIVER', 'default'), diff --git a/tests/Feature/Api/SecurityLogsTest.php b/tests/Feature/Api/SecurityLogsTest.php index 4afc33a1b3..7201812919 100644 --- a/tests/Feature/Api/SecurityLogsTest.php +++ b/tests/Feature/Api/SecurityLogsTest.php @@ -150,8 +150,8 @@ public function testStore() ]); $response->assertStatus(201); $collection = SecurityLog::where('user_id', $this->user->id)->get(); - $this->assertCount(2, $collection); - $securityLog = $collection->skip(1)->first(); + $this->assertCount(1, $collection); + $securityLog = $collection->first(); $this->assertEquals([ 'fullname' => $this->user->getAttribute('fullname'), ], (array) $securityLog->data); @@ -180,10 +180,14 @@ public function testSettingUpdated() $original = array_intersect_key($setting->getOriginal(), $setting->getDirty()); $setting->save(); SettingsUpdated::dispatch($setting, $setting->getChanges(), $original); - $collection = SecurityLog::get(); - $this->assertCount(1, $collection); - $securityLog = $collection->first(); - $this->assertEquals('SettingsUpdated', $securityLog->getAttribute('event')); + // Check if the variable security_log is enable + if (config('app.security_log')) { + $this->assertCount(1, $collection); + $securityLog = $collection->first(); + $this->assertEquals('SettingsUpdated', $securityLog->getAttribute('event')); + } else { + $this->assertCount(0, $collection); + } } } From d8a6bb62c2e6d3e3c3d5396a8feb07387c09880e Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Thu, 29 Jun 2023 15:20:07 -0400 Subject: [PATCH 19/27] Adding fake test data to Model Tests --- .../Models/ProcessCategoryTest.php | 26 +++++++++++-------- .../Models/ScreenCategoryTest.php | 22 +++++++++------- .../Models/ScriptCategoryTest.php | 24 ++++++++++------- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/tests/unit/ProcessMaker/Models/ProcessCategoryTest.php b/tests/unit/ProcessMaker/Models/ProcessCategoryTest.php index 8888f22757..607e18a17c 100644 --- a/tests/unit/ProcessMaker/Models/ProcessCategoryTest.php +++ b/tests/unit/ProcessMaker/Models/ProcessCategoryTest.php @@ -8,37 +8,41 @@ class ProcessCategoryTest extends TestCase { /** - * Test ScriptCategory Model. + * Test ProcessCategory Model. * * @return void */ public function testGetNamesByIds() { - //Case 1: string id - $stringIds = '1'; + //Case 1: one Id + $category = ProcessCategory::factory()->create([ + 'name' => 'Screen Category 7', + ]); $this->assertEquals( - "Uncategorized", - ProcessCategory::getNamesByIds($stringIds) + $category->name, + ProcessCategory::getNamesByIds($category->id) ); - //Case 2: multiple Id - $stringIds = '1,3'; + //Case 2: more than one Id + $category2 = ProcessCategory::factory()->create([ + 'name' => 'Screen Category 33', + ]); $this->assertEquals( - "Uncategorized", - ProcessCategory::getNamesByIds($stringIds) + $category->name . ', ' . $category2->name, + ProcessCategory::getNamesByIds($category->id . ',' . $category2->id) ); //Case 3: without Id $stringIds = ''; $this->assertEquals( - "Uncategorized", + "", ProcessCategory::getNamesByIds($stringIds) ); //Case 4: non-existentId $stringIds = '9452'; $this->assertEquals( - "Uncategorized", + "", ProcessCategory::getNamesByIds($stringIds) ); } diff --git a/tests/unit/ProcessMaker/Models/ScreenCategoryTest.php b/tests/unit/ProcessMaker/Models/ScreenCategoryTest.php index 953ede2018..e07689c7da 100644 --- a/tests/unit/ProcessMaker/Models/ScreenCategoryTest.php +++ b/tests/unit/ProcessMaker/Models/ScreenCategoryTest.php @@ -15,30 +15,34 @@ class ScreenCategoryTest extends TestCase public function testGetNamesByIds() { //Case 1: one Id - $stringIds = '1'; + $category = ScreenCategory::factory()->create([ + 'name' => 'Screen Category 1', + ]); $this->assertEquals( - "Uncategorized", - ScreenCategory::getNamesByIds($stringIds) + $category->name, + ScreenCategory::getNamesByIds($category->id) ); - //Case 2: multiple Id - $stringIds = '1,3'; + //Case 2: more than one Id + $category2 = ScreenCategory::factory()->create([ + 'name' => 'Screen Category 33', + ]); $this->assertEquals( - "Uncategorized", - ScreenCategory::getNamesByIds($stringIds) + $category->name . ', ' . $category2->name, + ScreenCategory::getNamesByIds($category->id . ',' . $category2->id) ); //Case 3: without Id $stringIds = ''; $this->assertEquals( - "Uncategorized", + "", ScreenCategory::getNamesByIds($stringIds) ); //Case 4: non-existentId $stringIds = '9452'; $this->assertEquals( - "Uncategorized", + "", ScreenCategory::getNamesByIds($stringIds) ); } diff --git a/tests/unit/ProcessMaker/Models/ScriptCategoryTest.php b/tests/unit/ProcessMaker/Models/ScriptCategoryTest.php index d1e4ea7628..b69b336445 100644 --- a/tests/unit/ProcessMaker/Models/ScriptCategoryTest.php +++ b/tests/unit/ProcessMaker/Models/ScriptCategoryTest.php @@ -14,31 +14,35 @@ class ScriptCategoryTest extends TestCase */ public function testGetNamesByIds() { - //Case 1: string id - $stringIds = '1'; + //Case 1: one Id + $category = ScriptCategory::factory()->create([ + 'name' => 'Screen Category 13', + ]); $this->assertEquals( - "Uncategorized", - ScriptCategory::getNamesByIds($stringIds) + $category->name, + ScriptCategory::getNamesByIds($category->id) ); - //Case 2: multiple Id - $stringIds = '1,3'; + //Case 2: more than one Id + $category2 = ScriptCategory::factory()->create([ + 'name' => 'Screen Category 33', + ]); $this->assertEquals( - "Uncategorized", - ScriptCategory::getNamesByIds($stringIds) + $category->name . ', ' . $category2->name, + ScriptCategory::getNamesByIds($category->id . ',' . $category2->id) ); //Case 3: without Id $stringIds = ''; $this->assertEquals( - "Uncategorized", + "", ScriptCategory::getNamesByIds($stringIds) ); //Case 4: non-existentId $stringIds = '9452'; $this->assertEquals( - "Uncategorized", + "", ScriptCategory::getNamesByIds($stringIds) ); } From efa85821ed6d4977ecd5a422448edfac3b4e2240 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Fri, 30 Jun 2023 15:55:51 -0400 Subject: [PATCH 20/27] Adding changes to Categories events --- ProcessMaker/Events/ProcessPublished.php | 8 ++++++++ ProcessMaker/Events/ScreenCreated.php | 6 ++++++ ProcessMaker/Events/ScreenUpdated.php | 8 ++++++++ ProcessMaker/Events/ScriptCreated.php | 10 ++++++++++ ProcessMaker/Events/ScriptUpdated.php | 8 ++++++++ ProcessMaker/Events/TemplateUpdated.php | 11 ++++++++++- 6 files changed, 50 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Events/ProcessPublished.php b/ProcessMaker/Events/ProcessPublished.php index b07b76d8b1..1511ab16ec 100644 --- a/ProcessMaker/Events/ProcessPublished.php +++ b/ProcessMaker/Events/ProcessPublished.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Process; +use ProcessMaker\Models\ProcessCategory; use ProcessMaker\Traits\FormatSecurityLogChanges; class ProcessPublished implements SecurityLogEventInterface @@ -39,6 +40,13 @@ public function __construct(Process $data, array $changes, array $original) $this->process = $data; $this->changes = array_diff_key($changes, array_flip($this::REMOVE_KEYS)); $this->original = array_diff_key($original, array_flip($this::REMOVE_KEYS)); + + if (isset($original['process_category_id']) && isset($changes['process_category_id'])) { + $this->changes['process_category'] = ProcessCategory::getNamesByIds($this->changes['process_category_id']); + $this->original['process_category'] = ProcessCategory::getNamesByIds($this->original['process_category_id']); + unset($this->changes['process_category_id']); + unset($this->original['process_category_id']); + } } /** diff --git a/ProcessMaker/Events/ScreenCreated.php b/ProcessMaker/Events/ScreenCreated.php index 040b651306..90cf0a241a 100644 --- a/ProcessMaker/Events/ScreenCreated.php +++ b/ProcessMaker/Events/ScreenCreated.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; +use ProcessMaker\Models\ScreenCategory; class ScreenCreated implements SecurityLogEventInterface { @@ -19,6 +20,11 @@ class ScreenCreated implements SecurityLogEventInterface public function __construct(array $newScreen) { $this->newScreen = $newScreen; + + if (isset($newScreen['screen_category_id'])) { + $this->newScreen['screen_category'] = ScreenCategory::getNamesByIds($newScreen['screen_category_id']); + unset($this->newScreen['screen_category_id']); + } } /** diff --git a/ProcessMaker/Events/ScreenUpdated.php b/ProcessMaker/Events/ScreenUpdated.php index a7dfe5e054..5c21cf5ead 100644 --- a/ProcessMaker/Events/ScreenUpdated.php +++ b/ProcessMaker/Events/ScreenUpdated.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Screen; +use ProcessMaker\Models\ScreenCategory; use ProcessMaker\Traits\FormatSecurityLogChanges; class ScreenUpdated implements SecurityLogEventInterface @@ -28,6 +29,13 @@ public function __construct(Screen $screen, array $changes, array $original) $this->screen = $screen; $this->changes = $changes; $this->original = $original; + + if (isset($original['screen_category_id']) && isset($changes['screen_category_id'])) { + $this->changes['screen_category'] = ScreenCategory::getNamesByIds($this->changes['screen_category_id']); + $this->original['screen_category'] = ScreenCategory::getNamesByIds($this->original['screen_category_id']); + unset($this->changes['screen_category_id']); + unset($this->original['screen_category_id']); + } } /** diff --git a/ProcessMaker/Events/ScriptCreated.php b/ProcessMaker/Events/ScriptCreated.php index 2cb128bdd0..767632a6d5 100644 --- a/ProcessMaker/Events/ScriptCreated.php +++ b/ProcessMaker/Events/ScriptCreated.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Script; +use ProcessMaker\Models\ScriptCategory; use ProcessMaker\Traits\FormatSecurityLogChanges; class ScriptCreated implements SecurityLogEventInterface @@ -18,6 +19,8 @@ class ScriptCreated implements SecurityLogEventInterface private Script $script; + public string $categoryName = ''; + /** * Create a new event instance. * @@ -28,6 +31,10 @@ public function __construct(Script $script, array $changes) { $this->script = $script; $this->changes = $changes; + $categoryId = $this->script['script_category_id'] ?? ''; + if (!empty($categoryId)) { + $this->categoryName = ScriptCategory::where('id', $categoryId)->value('name'); + } } /** @@ -54,6 +61,9 @@ public function getData(): array 'created_at' => $this->script->getAttribute('created_at'), ] : [ 'name' => $this->script->getAttribute('title'), + 'description' => $this->script->getAttribute('description'), + 'category' => $this->categoryName, + 'language' => $this->script->getAttribute('language') ]; unset($this->changes['code']); unset($this->original['code']); diff --git a/ProcessMaker/Events/ScriptUpdated.php b/ProcessMaker/Events/ScriptUpdated.php index 57b65bf593..a2507d34c7 100644 --- a/ProcessMaker/Events/ScriptUpdated.php +++ b/ProcessMaker/Events/ScriptUpdated.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Script; +use ProcessMaker\Models\ScriptCategory; use ProcessMaker\Traits\FormatSecurityLogChanges; class ScriptUpdated implements SecurityLogEventInterface @@ -30,6 +31,13 @@ public function __construct(Script $script, array $changes, array $original) $this->script = $script; $this->changes = $changes; $this->original = $original; + + if (isset($original['script_category_id']) && isset($changes['script_category_id'])) { + $this->changes['script_category'] = ScriptCategory::getNamesByIds($this->changes['script_category_id']); + $this->original['script_category'] = ScriptCategory::getNamesByIds($this->original['script_category_id']); + unset($this->changes['script_category_id']); + unset($this->original['script_category_id']); + } } /** diff --git a/ProcessMaker/Events/TemplateUpdated.php b/ProcessMaker/Events/TemplateUpdated.php index 8429094d71..8f6eb7a400 100644 --- a/ProcessMaker/Events/TemplateUpdated.php +++ b/ProcessMaker/Events/TemplateUpdated.php @@ -4,7 +4,9 @@ use Carbon\Carbon; use Illuminate\Foundation\Events\Dispatchable; +use ProcessMaker\Helpers\ArrayHelper; use ProcessMaker\Contracts\SecurityLogEventInterface; +use ProcessMaker\Models\ProcessCategory; use ProcessMaker\Traits\FormatSecurityLogChanges; class TemplateUpdated implements SecurityLogEventInterface @@ -28,6 +30,13 @@ public function __construct(array $changes, array $original, bool $processType) $this->changes = $changes; $this->original = $original; $this->processType = $processType; + + if (isset($original['process_category_id']) && isset($changes['process_category_id'])) { + $this->changes['process_category'] = ProcessCategory::getNamesByIds($this->changes['process_category_id']); + $this->original['process_category'] = ProcessCategory::getNamesByIds($this->original['process_category_id']); + unset($this->changes['process_category_id']); + unset($this->original['process_category_id']); + } } /** @@ -53,7 +62,7 @@ public function getData(): array 'label' => $this->processType, ], 'last_modified' => $this->changes['updated_at'] ?? Carbon::now() - ], $this->formatChanges($newData, $oldData)); + ], ArrayHelper::getArrayDifferencesWithFormat($this->changes, $this->original)); } } From abc9b9982ef5f1838f83a10ea1675f0344f2be97 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Fri, 30 Jun 2023 16:56:30 -0400 Subject: [PATCH 21/27] Observations solved --- ProcessMaker/Events/ScriptCreated.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ProcessMaker/Events/ScriptCreated.php b/ProcessMaker/Events/ScriptCreated.php index 767632a6d5..02dfc3683a 100644 --- a/ProcessMaker/Events/ScriptCreated.php +++ b/ProcessMaker/Events/ScriptCreated.php @@ -19,8 +19,6 @@ class ScriptCreated implements SecurityLogEventInterface private Script $script; - public string $categoryName = ''; - /** * Create a new event instance. * @@ -31,10 +29,6 @@ public function __construct(Script $script, array $changes) { $this->script = $script; $this->changes = $changes; - $categoryId = $this->script['script_category_id'] ?? ''; - if (!empty($categoryId)) { - $this->categoryName = ScriptCategory::where('id', $categoryId)->value('name'); - } } /** @@ -56,13 +50,18 @@ public function getChanges(): array */ public function getData(): array { + $categoryId = $this->script['script_category_id'] ?? ''; + if (!empty($categoryId)) { + $categoryName = ScriptCategory::where('id', $categoryId)->value('name'); + } + $basic = isset($this->changes['code']) ? [ 'name' => $this->script->getAttribute('title'), 'created_at' => $this->script->getAttribute('created_at'), ] : [ 'name' => $this->script->getAttribute('title'), 'description' => $this->script->getAttribute('description'), - 'category' => $this->categoryName, + 'category' => $categoryName, 'language' => $this->script->getAttribute('language') ]; unset($this->changes['code']); From 4e71e3eebb3b7b472c5d669f7cf780202af7f15f Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Tue, 4 Jul 2023 08:14:53 -0400 Subject: [PATCH 22/27] Last observations about category_id applied --- ProcessMaker/Events/ProcessPublished.php | 11 +++++------ ProcessMaker/Events/ScreenUpdated.php | 11 +++++------ ProcessMaker/Events/ScriptUpdated.php | 11 +++++------ ProcessMaker/Events/TemplateUpdated.php | 11 +++++------ 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/ProcessMaker/Events/ProcessPublished.php b/ProcessMaker/Events/ProcessPublished.php index 1511ab16ec..1853324758 100644 --- a/ProcessMaker/Events/ProcessPublished.php +++ b/ProcessMaker/Events/ProcessPublished.php @@ -41,12 +41,11 @@ public function __construct(Process $data, array $changes, array $original) $this->changes = array_diff_key($changes, array_flip($this::REMOVE_KEYS)); $this->original = array_diff_key($original, array_flip($this::REMOVE_KEYS)); - if (isset($original['process_category_id']) && isset($changes['process_category_id'])) { - $this->changes['process_category'] = ProcessCategory::getNamesByIds($this->changes['process_category_id']); - $this->original['process_category'] = ProcessCategory::getNamesByIds($this->original['process_category_id']); - unset($this->changes['process_category_id']); - unset($this->original['process_category_id']); - } + // Get category name + $this->original['process_category'] = isset($original['process_category_id']) ? ProcessCategory::getNamesByIds($this->original['process_category_id']) : ''; + unset($this->original['process_category_id']); + $this->changes['process_category'] = isset($changes['process_category_id']) ? ProcessCategory::getNamesByIds($this->changes['process_category_id']) : ''; + unset($this->changes['process_category_id']); } /** diff --git a/ProcessMaker/Events/ScreenUpdated.php b/ProcessMaker/Events/ScreenUpdated.php index 5c21cf5ead..808f530215 100644 --- a/ProcessMaker/Events/ScreenUpdated.php +++ b/ProcessMaker/Events/ScreenUpdated.php @@ -30,12 +30,11 @@ public function __construct(Screen $screen, array $changes, array $original) $this->changes = $changes; $this->original = $original; - if (isset($original['screen_category_id']) && isset($changes['screen_category_id'])) { - $this->changes['screen_category'] = ScreenCategory::getNamesByIds($this->changes['screen_category_id']); - $this->original['screen_category'] = ScreenCategory::getNamesByIds($this->original['screen_category_id']); - unset($this->changes['screen_category_id']); - unset($this->original['screen_category_id']); - } + // Get category name + $this->original['screen_category'] = isset($original['screen_category_id']) ? ScreenCategory::getNamesByIds($this->original['screen_category_id']) : ''; + unset($this->original['screen_category_id']); + $this->changes['screen_category'] = isset($changes['screen_category_id']) ? ScreenCategory::getNamesByIds($this->changes['screen_category_id']) : ''; + unset($this->changes['screen_category_id']); } /** diff --git a/ProcessMaker/Events/ScriptUpdated.php b/ProcessMaker/Events/ScriptUpdated.php index a2507d34c7..17752df939 100644 --- a/ProcessMaker/Events/ScriptUpdated.php +++ b/ProcessMaker/Events/ScriptUpdated.php @@ -32,12 +32,11 @@ public function __construct(Script $script, array $changes, array $original) $this->changes = $changes; $this->original = $original; - if (isset($original['script_category_id']) && isset($changes['script_category_id'])) { - $this->changes['script_category'] = ScriptCategory::getNamesByIds($this->changes['script_category_id']); - $this->original['script_category'] = ScriptCategory::getNamesByIds($this->original['script_category_id']); - unset($this->changes['script_category_id']); - unset($this->original['script_category_id']); - } + // Get category name + $this->original['script_category'] = isset($original['script_category_id']) ? ScriptCategory::getNamesByIds($this->original['script_category_id']) : ''; + unset($this->original['script_category_id']); + $this->changes['script_category'] = isset($changes['script_category_id']) ? ScriptCategory::getNamesByIds($this->changes['script_category_id']) : ''; + unset($this->changes['script_category_id']); } /** diff --git a/ProcessMaker/Events/TemplateUpdated.php b/ProcessMaker/Events/TemplateUpdated.php index 8f6eb7a400..a1e9b2188f 100644 --- a/ProcessMaker/Events/TemplateUpdated.php +++ b/ProcessMaker/Events/TemplateUpdated.php @@ -31,12 +31,11 @@ public function __construct(array $changes, array $original, bool $processType) $this->original = $original; $this->processType = $processType; - if (isset($original['process_category_id']) && isset($changes['process_category_id'])) { - $this->changes['process_category'] = ProcessCategory::getNamesByIds($this->changes['process_category_id']); - $this->original['process_category'] = ProcessCategory::getNamesByIds($this->original['process_category_id']); - unset($this->changes['process_category_id']); - unset($this->original['process_category_id']); - } + // Get category name + $this->original['process_category'] = isset($original['process_category_id']) ? ProcessCategory::getNamesByIds($this->original['process_category_id']) : ''; + unset($this->original['process_category_id']); + $this->changes['process_category'] = isset($changes['process_category_id']) ? ProcessCategory::getNamesByIds($this->changes['process_category_id']) : ''; + unset($this->changes['process_category_id']); } /** From 18f1fba59310dd067d39f8248c32c0d9e6218f19 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Tue, 4 Jul 2023 08:56:46 -0400 Subject: [PATCH 23/27] FOUR-9155 --- ProcessMaker/Events/FilesDownloaded.php | 65 +++++++++++++++++++ .../Http/Controllers/Api/FileController.php | 3 + .../Providers/EventServiceProvider.php | 2 + 3 files changed, 70 insertions(+) create mode 100644 ProcessMaker/Events/FilesDownloaded.php diff --git a/ProcessMaker/Events/FilesDownloaded.php b/ProcessMaker/Events/FilesDownloaded.php new file mode 100644 index 0000000000..30c0a961ad --- /dev/null +++ b/ProcessMaker/Events/FilesDownloaded.php @@ -0,0 +1,65 @@ +media = $data; + } + + /** + * Get specific data related to the event + * + * @return array + */ + public function getData(): array + { + return [ + 'name' => [ + 'label' => $this->media['name'], + 'link' => route('file-manager.index', ['public/'. $this->media['file_name']]) + ], + 'accessed_at' => Carbon::now() + ]; + } + + /** + * Get specific data related to the event + * + * @return array + */ + public function getChanges(): array + { + return [ + 'id' => $this->media['id'] ?? '' + ]; + } + + /** + * Get the Event name + * + * @return string + */ + public function getEventName(): string + { + return 'FilesDownloaded'; + } +} diff --git a/ProcessMaker/Http/Controllers/Api/FileController.php b/ProcessMaker/Http/Controllers/Api/FileController.php index bba394fd49..c3141da3c7 100644 --- a/ProcessMaker/Http/Controllers/Api/FileController.php +++ b/ProcessMaker/Http/Controllers/Api/FileController.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use ProcessMaker\Events\FilesDeleted; +use ProcessMaker\Events\FilesDownloaded; use ProcessMaker\Http\Controllers\Controller; use ProcessMaker\Http\Resources\ApiCollection; use ProcessMaker\Http\Resources\ApiResource; @@ -281,6 +282,8 @@ public function download(Media $file) $path = Storage::disk('public')->getAdapter()->getPathPrefix() . $file->id . '/' . $file->file_name; + // Register the Event + FilesDownloaded::dispatch($file); return response()->download($path); } diff --git a/ProcessMaker/Providers/EventServiceProvider.php b/ProcessMaker/Providers/EventServiceProvider.php index f27d127838..b8e8a6356a 100644 --- a/ProcessMaker/Providers/EventServiceProvider.php +++ b/ProcessMaker/Providers/EventServiceProvider.php @@ -16,6 +16,7 @@ use ProcessMaker\Events\EnvironmentVariablesDeleted; use ProcessMaker\Events\FilesCreated; use ProcessMaker\Events\FilesDeleted; +use ProcessMaker\Events\FilesDownloaded; use ProcessMaker\Events\FilesUpdated; use ProcessMaker\Events\GroupCreated; use ProcessMaker\Events\GroupDeleted; @@ -103,6 +104,7 @@ public function boot() $this->app['events']->listen(EnvironmentVariablesUpdated::class, SecurityLogger::class); $this->app['events']->listen(FilesCreated::class, SecurityLogger::class); $this->app['events']->listen(FilesDeleted::class, SecurityLogger::class); + $this->app['events']->listen(FilesDownloaded::class, SecurityLogger::class); $this->app['events']->listen(FilesUpdated::class, SecurityLogger::class); $this->app['events']->listen(GroupCreated::class, SecurityLogger::class); $this->app['events']->listen(GroupDeleted::class, SecurityLogger::class); From 2d0fc6fc791375bd4015801dcd49ed6bc54a3c4f Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Tue, 4 Jul 2023 13:08:55 -0400 Subject: [PATCH 24/27] FOUR-9174 --- tests/Feature/Api/SecurityLogsTest.php | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/tests/Feature/Api/SecurityLogsTest.php b/tests/Feature/Api/SecurityLogsTest.php index 7201812919..cf8b7e9ffd 100644 --- a/tests/Feature/Api/SecurityLogsTest.php +++ b/tests/Feature/Api/SecurityLogsTest.php @@ -149,28 +149,11 @@ public function testStore() ], ]); $response->assertStatus(201); + $collection = SecurityLog::where('user_id', $this->user->id)->get(); - $this->assertCount(1, $collection); - $securityLog = $collection->first(); - $this->assertEquals([ - 'fullname' => $this->user->getAttribute('fullname'), - ], (array) $securityLog->data); - $this->assertEquals([ - 'event' => 'TestStoreEvent', - 'ip' => '127.0.01', - 'user_id' => $this->user->id, - 'meta' => (object) [ - 'user_agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', - 'browser' => (object) [ - 'name' => 'Chrome', - 'version' => '111', - ], - 'os' => (object) [ - 'name' => 'Linux', - 'version' => null, - ], - ], - ], $securityLog->only('event', 'ip', 'user_id', 'meta')); + $this->assertCount(2, $collection); + $securityLog = $collection->skip(1)->first(); + $this->assertIsObject($securityLog->data); } public function testSettingUpdated() From 5b4f32bc31249b11080a41b5261f97068ae3cb83 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Thu, 6 Jul 2023 08:50:05 -0400 Subject: [PATCH 25/27] Add new Modal Sort Function --- .../users/components/SecurityLogsModal.vue | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/resources/js/admin/users/components/SecurityLogsModal.vue b/resources/js/admin/users/components/SecurityLogsModal.vue index 3410e0b6f1..f472643373 100644 --- a/resources/js/admin/users/components/SecurityLogsModal.vue +++ b/resources/js/admin/users/components/SecurityLogsModal.vue @@ -166,7 +166,8 @@ export default { let key = ""; let value = ""; let auxKey = ""; - const auxArray = {}; + let auxArraySort = {}; + let auxArray = {}; for ([key, value] of Object.entries(data)) { if (key.startsWith("+")) { @@ -191,7 +192,38 @@ export default { auxArray[this.capitalizeKey(key)] = this.booleanToString(value); } } - return auxArray; + + auxArraySort = this.sortModalArray(auxArray); + + return auxArraySort; + }, + /** + * Sort modal Array + */ + sortModalArray(auxArray) { + let sortKey = ["Name"]; + let auxArraySorted = {}; + let specialKey = Object.keys(auxArray).find(key => ["Created_at", "Deleted_at", "Updated_at", "Last_modified", "Accessed_at"].includes(key)); + + if (specialKey) { + sortKey.push(specialKey); + } + + sortKey.push("Description"); + + Object.keys(auxArray).forEach(key => { + if (!sortKey.includes(key)) { + sortKey.push(key); + } + }); + + sortKey.forEach(key => { + if (key in auxArray) { + auxArraySorted[key] = auxArray[key]; + } + }); + + return auxArraySorted; }, /** * Verify if value is a string o null From fcff906908e7ff70f2c44171ae708fde0bbbfc40 Mon Sep 17 00:00:00 2001 From: CarliPinell Date: Thu, 6 Jul 2023 10:46:43 -0400 Subject: [PATCH 26/27] Observations solved --- .../js/admin/users/components/SecurityLogsModal.vue | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/resources/js/admin/users/components/SecurityLogsModal.vue b/resources/js/admin/users/components/SecurityLogsModal.vue index f472643373..fce92b22cc 100644 --- a/resources/js/admin/users/components/SecurityLogsModal.vue +++ b/resources/js/admin/users/components/SecurityLogsModal.vue @@ -166,7 +166,6 @@ export default { let key = ""; let value = ""; let auxKey = ""; - let auxArraySort = {}; let auxArray = {}; for ([key, value] of Object.entries(data)) { @@ -193,9 +192,7 @@ export default { } } - auxArraySort = this.sortModalArray(auxArray); - - return auxArraySort; + return this.sortModalArray(auxArray); }, /** * Sort modal Array @@ -203,10 +200,10 @@ export default { sortModalArray(auxArray) { let sortKey = ["Name"]; let auxArraySorted = {}; - let specialKey = Object.keys(auxArray).find(key => ["Created_at", "Deleted_at", "Updated_at", "Last_modified", "Accessed_at"].includes(key)); + let dateKey = Object.keys(auxArray).find(key => ["Created_at", "Deleted_at", "Updated_at", "Last_modified", "Accessed_at"].includes(key)); - if (specialKey) { - sortKey.push(specialKey); + if (dateKey) { + sortKey.push(dateKey); } sortKey.push("Description"); From 5f6ea02f487a83abdb97665fc5ddaacd605f6c31 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Wed, 5 Jul 2023 15:44:32 -0400 Subject: [PATCH 27/27] FOUR-9176 --- ProcessMaker/Events/FilesAccessed.php | 76 +++++++++++++++++++ ProcessMaker/Events/FilesCreated.php | 30 ++++++-- ProcessMaker/Events/FilesDownloaded.php | 33 +++++--- .../Http/Controllers/Api/FileController.php | 5 +- .../Api/ProcessRequestFileController.php | 14 +++- .../Http/Controllers/RequestController.php | 16 +++- .../Providers/EventServiceProvider.php | 2 + 7 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 ProcessMaker/Events/FilesAccessed.php diff --git a/ProcessMaker/Events/FilesAccessed.php b/ProcessMaker/Events/FilesAccessed.php new file mode 100644 index 0000000000..bffe4b7e47 --- /dev/null +++ b/ProcessMaker/Events/FilesAccessed.php @@ -0,0 +1,76 @@ +processName = $data->getAttribute('name'); + // Link to the request + $this->linkFile = [ + 'label' => $data->getAttribute('id'), + 'link' => route('requests.show', $data) + ]; + } else { + // Link to file in the package + $this->linkFile = [ + 'label' => $name, + 'link' => route('file-manager.index', ['public/' . $name]), + ]; + } + } + + /** + * Get specific data related to the event + * + * @return array + */ + public function getData(): array + { + return [ + 'name' => $this->linkFile, + 'process' => $this->processName, + 'accessed_at' => Carbon::now() + ]; + } + + /** + * Get specific data related to the event + * + * @return array + */ + public function getChanges(): array + { + return []; + } + + /** + * Get the Event name + * + * @return string + */ + public function getEventName(): string + { + return 'FilesAccessed'; + } +} diff --git a/ProcessMaker/Events/FilesCreated.php b/ProcessMaker/Events/FilesCreated.php index 4c9a8b9302..51ba82ce62 100644 --- a/ProcessMaker/Events/FilesCreated.php +++ b/ProcessMaker/Events/FilesCreated.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; use ProcessMaker\Models\Media; +use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Traits\FormatSecurityLogChanges; class FilesCreated implements SecurityLogEventInterface @@ -12,17 +13,38 @@ class FilesCreated implements SecurityLogEventInterface use Dispatchable; use FormatSecurityLogChanges; + public const NAME_PUBLIC_FILES = 'Public Files'; + private array $media; + private array $name = []; + private string $processName = ''; /** * Create a new event instance. * * @return void */ - public function __construct(int $fileId) + public function __construct(int $fileId, ProcessRequest $data) { $this->media = Media::find(['id' => $fileId])->toArray(); $this->media = head($this->media); + + // Check if the request is related to the package files + if (static::NAME_PUBLIC_FILES === $data->getAttribute('name')) { + $this->processName = ''; + // Link to file in the package + $this->name = [ + 'label' => $this->media['name'], + 'link' => route('file-manager.index', ['public/' . $this->media['name']]), + ]; + } else { + $this->processName = $data->getAttribute('name'); + // Link to the request + $this->name = [ + 'label' => $data->getAttribute('id'), + 'link' => route('requests.show', $data) + ]; + } } /** @@ -33,10 +55,8 @@ public function __construct(int $fileId) public function getData(): array { return [ - 'file_name' => [ - 'label' => $this->media['name'], - 'link' => route('file-manager.index', ['public/' . $this->media['file_name']]), - ], + 'name' => $this->name, + 'process' => $this->processName, 'created_at' => $this->media['created_at'], ]; } diff --git a/ProcessMaker/Events/FilesDownloaded.php b/ProcessMaker/Events/FilesDownloaded.php index 30c0a961ad..e6787cc43b 100644 --- a/ProcessMaker/Events/FilesDownloaded.php +++ b/ProcessMaker/Events/FilesDownloaded.php @@ -5,7 +5,7 @@ use Carbon\Carbon; use Illuminate\Foundation\Events\Dispatchable; use ProcessMaker\Contracts\SecurityLogEventInterface; -use ProcessMaker\Models\Media; +use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Traits\FormatSecurityLogChanges; class FilesDownloaded implements SecurityLogEventInterface @@ -13,16 +13,31 @@ class FilesDownloaded implements SecurityLogEventInterface use Dispatchable; use FormatSecurityLogChanges; - private Media $media; + private array $name = []; + private string $processName = ''; /** * Create a new event instance. * * @return void */ - public function __construct(Media $data) + public function __construct(string $file, ProcessRequest $data = null) { - $this->media = $data; + // Check if the file is related to the request + if (!is_null($data)) { + $this->processName = $data->getAttribute('name'); + // Link to the request + $this->name = [ + 'label' => $data->getAttribute('id'), + 'link' => route('requests.show', $data) + ]; + } else { + // Link to file in the package + $this->name = [ + 'label' => $file, + 'link' => route('file-manager.index', ['public/' . $file]), + ]; + } } /** @@ -33,10 +48,8 @@ public function __construct(Media $data) public function getData(): array { return [ - 'name' => [ - 'label' => $this->media['name'], - 'link' => route('file-manager.index', ['public/'. $this->media['file_name']]) - ], + 'name' => $this->name, + 'process' => $this->processName, 'accessed_at' => Carbon::now() ]; } @@ -48,9 +61,7 @@ public function getData(): array */ public function getChanges(): array { - return [ - 'id' => $this->media['id'] ?? '' - ]; + return []; } /** diff --git a/ProcessMaker/Http/Controllers/Api/FileController.php b/ProcessMaker/Http/Controllers/Api/FileController.php index c3141da3c7..c94b553496 100644 --- a/ProcessMaker/Http/Controllers/Api/FileController.php +++ b/ProcessMaker/Http/Controllers/Api/FileController.php @@ -282,8 +282,11 @@ public function download(Media $file) $path = Storage::disk('public')->getAdapter()->getPathPrefix() . $file->id . '/' . $file->file_name; + // Register the Event - FilesDownloaded::dispatch($file); + if (!empty($file->file_name)) { + FilesDownloaded::dispatch($file->file_name); + } return response()->download($path); } diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestFileController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestFileController.php index 74beafa7e6..6755335750 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestFileController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestFileController.php @@ -14,8 +14,10 @@ use Pion\Laravel\ChunkUpload\Handler\AbstractHandler; use Pion\Laravel\ChunkUpload\Handler\HandlerFactory; use Pion\Laravel\ChunkUpload\Receiver\FileReceiver; +use ProcessMaker\Events\FilesAccessed; use ProcessMaker\Events\FilesCreated; use ProcessMaker\Events\FilesDeleted; +use ProcessMaker\Events\FilesDownloaded; use ProcessMaker\Http\Controllers\Controller; use ProcessMaker\Http\Resources\ApiCollection; use ProcessMaker\Http\Resources\ApiResource; @@ -90,6 +92,11 @@ public function index(Request $laravel_request, ProcessRequest $request) $id = $laravel_request->get('id'); $filter = $name ? $name : $id; + // Register the Event + if (!empty($filter)) { + FilesAccessed::dispatch($filter, $request); + } + // If no filter, return entire collection; otherwise, filter collection if (!$filter) { return new ResourceCollection($media); @@ -160,6 +167,11 @@ public function show(Request $laravel_request, ProcessRequest $request, $media) $file = $request->downloadFile($media); if ($file) { + // Register the Event + if (!empty($file['file_name'])) { + FilesDownloaded::dispatch($file['file_name'], $request); + } + return response()->download($file); } @@ -308,7 +320,7 @@ private function saveUploadedFile(UploadedFile $file, ProcessRequest $processReq ->toMediaCollection(); // Register the Event - FilesCreated::dispatch($media->id); + FilesCreated::dispatch($media->id, $processRequest); return new JsonResponse(['message' => 'The file was uploaded.', 'fileUploadId' => $media->id], 200); } diff --git a/ProcessMaker/Http/Controllers/RequestController.php b/ProcessMaker/Http/Controllers/RequestController.php index 7acd268e21..5bdbc2c01e 100644 --- a/ProcessMaker/Http/Controllers/RequestController.php +++ b/ProcessMaker/Http/Controllers/RequestController.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; +use ProcessMaker\Events\FilesDownloaded; use ProcessMaker\Events\ScreenBuilderStarting; use ProcessMaker\Http\Controllers\Controller; use ProcessMaker\Managers\DataManager; @@ -134,7 +135,17 @@ public function show(ProcessRequest $request, Media $mediaItems) $isProcessManager = $request->process?->manager_id === Auth::user()->id; return view('requests.show', compact( - 'request', 'files', 'canCancel', 'canViewComments', 'canManuallyComplete', 'canRetry', 'manager', 'canPrintScreens', 'screenRequested', 'addons', 'isProcessManager' + 'request', + 'files', + 'canCancel', + 'canViewComments', + 'canManuallyComplete', + 'canRetry', + 'manager', + 'canPrintScreens', + 'screenRequested', + 'addons', + 'isProcessManager' )); } @@ -186,6 +197,9 @@ public function downloadFiles(ProcessRequest $request, $media) $file = $request->downloadFile($media); if ($file) { + // Register the Event + FilesDownloaded::dispatch(basename($file), $request); + return response()->download($file); } diff --git a/ProcessMaker/Providers/EventServiceProvider.php b/ProcessMaker/Providers/EventServiceProvider.php index b8e8a6356a..24d422041f 100644 --- a/ProcessMaker/Providers/EventServiceProvider.php +++ b/ProcessMaker/Providers/EventServiceProvider.php @@ -14,6 +14,7 @@ use ProcessMaker\Events\EnvironmentVariablesUpdated; use ProcessMaker\Events\EnvironmentVariablesCreated; use ProcessMaker\Events\EnvironmentVariablesDeleted; +use ProcessMaker\Events\FilesAccessed; use ProcessMaker\Events\FilesCreated; use ProcessMaker\Events\FilesDeleted; use ProcessMaker\Events\FilesDownloaded; @@ -102,6 +103,7 @@ public function boot() $this->app['events']->listen(EnvironmentVariablesCreated::class, SecurityLogger::class); $this->app['events']->listen(EnvironmentVariablesDeleted::class, SecurityLogger::class); $this->app['events']->listen(EnvironmentVariablesUpdated::class, SecurityLogger::class); + $this->app['events']->listen(FilesAccessed::class, SecurityLogger::class); $this->app['events']->listen(FilesCreated::class, SecurityLogger::class); $this->app['events']->listen(FilesDeleted::class, SecurityLogger::class); $this->app['events']->listen(FilesDownloaded::class, SecurityLogger::class);