diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index 030e069bd2..b33d83cdd0 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -223,6 +223,7 @@ class AcceptanceController extends Controller 'item_model' => $display_model, 'item_serial' => $item->serial, 'eula' => $item->getEula(), + 'note' => $request->input('note'), 'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'), 'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'), 'assigned_to' => $assigned_to, @@ -238,7 +239,7 @@ class AcceptanceController extends Controller Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output()); } - $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename); + $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note')); $acceptance->notify(new AcceptanceAssetAcceptedNotification($data)); event(new CheckoutAccepted($acceptance)); @@ -306,10 +307,12 @@ class AcceptanceController extends Controller $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; } + $data = [ 'item_tag' => $item->asset_tag, 'item_model' => $display_model, 'item_serial' => $item->serial, + 'note' => $request->input('note'), 'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'), 'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null, 'assigned_to' => $assigned_to, @@ -323,7 +326,7 @@ class AcceptanceController extends Controller Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output()); } - $acceptance->decline($sig_filename); + $acceptance->decline($sig_filename, $request->input('note')); $acceptance->notify(new AcceptanceAssetDeclinedNotification($data)); event(new CheckoutDeclined($acceptance)); $return_msg = trans('admin/users/message.declined'); diff --git a/app/Http/Controllers/AssetModelsFilesController.php b/app/Http/Controllers/AssetModelsFilesController.php index a4472c3504..2a0a23a9f2 100644 --- a/app/Http/Controllers/AssetModelsFilesController.php +++ b/app/Http/Controllers/AssetModelsFilesController.php @@ -70,8 +70,6 @@ class AssetModelsFilesController extends Controller } $file = 'private_uploads/assetmodels/'.$log->filename; - \Log::debug('Checking for '.$file); - if (! Storage::exists($file)) { return response('File '.$file.' not found on server', 404) diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php index a096f16678..aa282b443d 100644 --- a/app/Http/Controllers/Assets/AssetCheckoutController.php +++ b/app/Http/Controllers/Assets/AssetCheckoutController.php @@ -62,7 +62,7 @@ class AssetCheckoutController extends Controller $this->authorize('checkout', $asset); $admin = Auth::user(); - $target = $this->determineCheckoutTarget($asset); + $target = $this->determineCheckoutTarget(); $asset = $this->updateAssetLocation($asset, $target); diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php index 7debfb479c..ca7a72e3ae 100644 --- a/app/Http/Controllers/Assets/AssetFilesController.php +++ b/app/Http/Controllers/Assets/AssetFilesController.php @@ -70,7 +70,6 @@ class AssetFilesController extends Controller } $file = 'private_uploads/assets/'.$log->filename; - \Log::debug('Checking for '.$file); if ($log->action_type == 'audit') { $file = 'private_uploads/audits/'.$log->filename; diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index ac755fd389..20a5564a44 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -293,7 +293,7 @@ class AssetsController extends Controller * * @param int $assetId * @return \Illuminate\Http\RedirectResponse|Redirect - *@since [v1.0] + * @since [v1.0] * @author [A. Gianotto] [] */ public function update(ImageUploadRequest $request, $assetId = null) diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php index 561e13b200..ffbf81944b 100644 --- a/app/Http/Controllers/Assets/BulkAssetsController.php +++ b/app/Http/Controllers/Assets/BulkAssetsController.php @@ -13,7 +13,9 @@ use App\Models\Setting; use App\View\Label; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Session; use App\Http\Requests\AssetCheckoutRequest; @@ -189,7 +191,6 @@ class BulkAssetsController extends Controller * Save bulk edits * * @author [A. Gianotto] [] - * @return Redirect * @internal param array $assets * @since [v2.0] */ @@ -214,7 +215,7 @@ class BulkAssetsController extends Controller } - $assets = Asset::whereIn('id', array_keys($request->input('ids')))->get(); + $assets = Asset::whereIn('id', $request->input('ids'))->get(); @@ -379,28 +380,30 @@ class BulkAssetsController extends Controller foreach ($asset->model->fieldset->fields as $field) { if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) { - $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); + if (Gate::allows('admin')) { + $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); - /* - * Check if the decrypted existing value is different from one we just submitted - * and if not, pull it out of the object since it shouldn't really be updating at all. - * If we don't do this, it will try to re-encrypt it, and the same value encrypted two - * different times will have different values, so it will *look* like it was updated - * but it wasn't. - */ - if ($decrypted_old != $this->update_array[$field->db_column]) { - $asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]); - } else { /* - * Remove the encrypted custom field from the update_array, since nothing changed + * Check if the decrypted existing value is different from one we just submitted + * and if not, pull it out of the object since it shouldn't really be updating at all. + * If we don't do this, it will try to re-encrypt it, and the same value encrypted two + * different times will have different values, so it will *look* like it was updated + * but it wasn't. */ - unset($this->update_array[$field->db_column]); - unset($asset->{$field->db_column}); - } + if ($decrypted_old != $this->update_array[$field->db_column]) { + $asset->{$field->db_column} = Crypt::encrypt($this->update_array[$field->db_column]); + } else { + /* + * Remove the encrypted custom field from the update_array, since nothing changed + */ + unset($this->update_array[$field->db_column]); + unset($asset->{$field->db_column}); + } - /* - * These custom fields aren't encrypted, just carry on as usual - */ + /* + * These custom fields aren't encrypted, just carry on as usual + */ + } } else { if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) { diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 9316c2dec4..b0874cb569 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -293,8 +293,15 @@ class UsersController extends Controller $user->password = bcrypt($request->input('password')); } + + // Update the location of any assets checked out to this user + Asset::where('assigned_type', User::class) + ->where('assigned_to', $user->id) + ->update(['location_id' => $user->location_id]); + $permissions_array = $request->input('permission'); + // Strip out the superuser permission if the user isn't a superadmin if (! Auth::user()->isSuperUser()) { unset($permissions_array['superuser']); diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index 9677111059..59af9259f4 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -86,12 +86,8 @@ class ImageUploadRequest extends Request if ($this->offsetGet($form_fieldname) instanceof UploadedFile) { $image = $this->offsetGet($form_fieldname); - \Log::debug('Image is an instance of UploadedFile'); } elseif ($this->hasFile($form_fieldname)) { $image = $this->file($form_fieldname); - \Log::debug('Just use regular upload for '.$form_fieldname); - } else { - \Log::debug('No image found for form fieldname: '.$form_fieldname); } if (isset($image)) { diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index d99e570810..ca0f6296ee 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -85,20 +85,23 @@ class ActionlogsTransformer $enc_old = ''; $enc_new = ''; - try { - $enc_old = \Crypt::decryptString($this->clean_field($fieldata->old)); - } catch (\Exception $e) { - \Log::debug('Could not decrypt field - maybe the key changed?'); + if ($this->clean_field($fieldata->old!='')) { + try { + $enc_old = \Crypt::decryptString($this->clean_field($fieldata->old)); + } catch (\Exception $e) { + \Log::debug('Could not decrypt old field value - maybe the key changed?'); + } } - try { - $enc_new = \Crypt::decryptString($this->clean_field($fieldata->new)); - } catch (\Exception $e) { - \Log::debug('Could not decrypt field - maybe the key changed?'); + if ($this->clean_field($fieldata->new!='')) { + try { + $enc_new = \Crypt::decryptString($this->clean_field($fieldata->new)); + } catch (\Exception $e) { + \Log::debug('Could not decrypt new field value - maybe the key changed?'); + } } if ($enc_old != $enc_new) { - \Log::debug('custom fields do not match'); $clean_meta[$fieldname]['old'] = "************"; $clean_meta[$fieldname]['new'] = "************"; diff --git a/app/Listeners/LogListener.php b/app/Listeners/LogListener.php index 4b584c668b..0345ac1341 100644 --- a/app/Listeners/LogListener.php +++ b/app/Listeners/LogListener.php @@ -62,6 +62,7 @@ class LogListener $logaction->target()->associate($event->acceptance->assignedTo); $logaction->accept_signature = $event->acceptance->signature_filename; $logaction->filename = $event->acceptance->stored_eula_file; + $logaction->note = $event->acceptance->note; $logaction->action_type = 'accepted'; // TODO: log the actual license seat that was checked out @@ -78,6 +79,7 @@ class LogListener $logaction->item()->associate($event->acceptance->checkoutable); $logaction->target()->associate($event->acceptance->assignedTo); $logaction->accept_signature = $event->acceptance->signature_filename; + $logaction->note = $event->acceptance->note; $logaction->action_type = 'declined'; // TODO: log the actual license seat that was checked out diff --git a/app/Models/CheckoutAcceptance.php b/app/Models/CheckoutAcceptance.php index 4a4360c40a..9c501aaf63 100644 --- a/app/Models/CheckoutAcceptance.php +++ b/app/Models/CheckoutAcceptance.php @@ -80,12 +80,13 @@ class CheckoutAcceptance extends Model * * @param string $signature_filename */ - public function accept($signature_filename, $eula = null, $filename = null) + public function accept($signature_filename, $eula = null, $filename = null, $note = null) { $this->accepted_at = now(); $this->signature_filename = $signature_filename; $this->stored_eula = $eula; $this->stored_eula_file = $filename; + $this->note = $note; $this->save(); /** @@ -99,9 +100,10 @@ class CheckoutAcceptance extends Model * * @param string $signature_filename */ - public function decline($signature_filename) + public function decline($signature_filename, $note = null) { $this->declined_at = now(); + $this->note = $note; $this->signature_filename = $signature_filename; $this->save(); diff --git a/app/Models/Company.php b/app/Models/Company.php index 4b718b4200..aac002bdcd 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -262,7 +262,6 @@ final class Company extends SnipeModel if (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser()) || (! Auth::check())) { return $query; } else { - \Log::debug('Fire scopeCompanyablesDirectly.'); return static::scopeCompanyablesDirectly($query, $column, $table_name); } } @@ -275,7 +274,6 @@ final class Company extends SnipeModel { // Get the company ID of the logged in user, or set it to null if there is no company assicoated with the user if (Auth::user()) { - \Log::debug('Admin company is: '.Auth::user()->company_id); $company_id = Auth::user()->company_id; } else { $company_id = null; @@ -283,9 +281,6 @@ final class Company extends SnipeModel // Dynamically get the table name if it's not passed in, based on the model we're querying against $table = ($table_name) ? $table_name."." : $query->getModel()->getTable()."."; - \Log::debug('Model is: '.$query->getModel()); - - \Log::debug('Table is: '.$table); // If the column exists in the table, use it to scope the query if (\Schema::hasColumn($query->getModel()->getTable(), $column)) { @@ -307,7 +302,6 @@ final class Company extends SnipeModel */ public static function scopeCompanyableChildren(array $companyable_names, $query) { - \Log::debug('Company Names in scopeCompanyableChildren: '.print_r($companyable_names, true)); if (count($companyable_names) == 0) { throw new Exception('No Companyable Children to scope'); diff --git a/app/Notifications/AcceptanceAssetAcceptedNotification.php b/app/Notifications/AcceptanceAssetAcceptedNotification.php index ca016acd34..db1555b574 100644 --- a/app/Notifications/AcceptanceAssetAcceptedNotification.php +++ b/app/Notifications/AcceptanceAssetAcceptedNotification.php @@ -26,6 +26,7 @@ class AcceptanceAssetAcceptedNotification extends Notification $this->item_serial = $params['item_serial']; $this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false); $this->assigned_to = $params['assigned_to']; + $this->note = $params['note']; $this->company_name = $params['company_name']; $this->settings = Setting::getSettings(); @@ -64,6 +65,7 @@ class AcceptanceAssetAcceptedNotification extends Notification 'item_tag' => $this->item_tag, 'item_model' => $this->item_model, 'item_serial' => $this->item_serial, + 'note' => $this->note, 'accepted_date' => $this->accepted_date, 'assigned_to' => $this->assigned_to, 'company_name' => $this->company_name, diff --git a/app/Notifications/AcceptanceAssetDeclinedNotification.php b/app/Notifications/AcceptanceAssetDeclinedNotification.php index 11b022e095..abdfbbf0c0 100644 --- a/app/Notifications/AcceptanceAssetDeclinedNotification.php +++ b/app/Notifications/AcceptanceAssetDeclinedNotification.php @@ -25,6 +25,7 @@ class AcceptanceAssetDeclinedNotification extends Notification $this->item_model = $params['item_model']; $this->item_serial = $params['item_serial']; $this->declined_date = Helper::getFormattedDateObject($params['declined_date'], 'date', false); + $this->note = $params['note']; $this->assigned_to = $params['assigned_to']; $this->company_name = $params['company_name']; $this->settings = Setting::getSettings(); @@ -62,6 +63,7 @@ class AcceptanceAssetDeclinedNotification extends Notification 'item_tag' => $this->item_tag, 'item_model' => $this->item_model, 'item_serial' => $this->item_serial, + 'note' => $this->note, 'declined_date' => $this->declined_date, 'assigned_to' => $this->assigned_to, 'company_name' => $this->company_name, diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index 6c5e883636..43845c3075 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -363,6 +363,15 @@ class AssetFactory extends Factory }); } + public function hasMultipleCustomFields(array $fields = null): self + { + return $this->state(function () use ($fields) { + return [ + 'model_id' => AssetModel::factory()->hasMultipleCustomFields($fields), + ]; + }); + } + /** * This allows bypassing model level validation if you want to purposefully diff --git a/database/factories/AssetModelFactory.php b/database/factories/AssetModelFactory.php index ed3d478261..6790897567 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -439,4 +439,13 @@ class AssetModelFactory extends Factory ]; }); } + + public function hasMultipleCustomFields(array $fields = null) + { + return $this->state(function () use ($fields) { + return [ + 'fieldset_id' => CustomFieldset::factory()->hasMultipleCustomFields($fields), + ]; + }); + } } diff --git a/database/factories/CustomFieldsetFactory.php b/database/factories/CustomFieldsetFactory.php index 9a410ba25f..a9e8b9ae12 100644 --- a/database/factories/CustomFieldsetFactory.php +++ b/database/factories/CustomFieldsetFactory.php @@ -53,4 +53,23 @@ class CustomFieldsetFactory extends Factory $fieldset->fields()->attach($field, ['order' => '1', 'required' => false]); }); } + + public function hasMultipleCustomFields(array $fields = null): self + { + return $this->afterCreating(function (CustomFieldset $fieldset) use ($fields) { + if (empty($fields)) { + $mac_address = CustomField::factory()->macAddress()->create(); + $ram = CustomField::factory()->ram()->create(); + $cpu = CustomField::factory()->cpu()->create(); + + $fieldset->fields()->attach($mac_address, ['order' => '1', 'required' => false]); + $fieldset->fields()->attach($ram, ['order' => '2', 'required' => false]); + $fieldset->fields()->attach($cpu, ['order' => '3', 'required' => false]); + } else { + foreach ($fields as $field) { + $fieldset->fields()->attach($field, ['order' => '1', 'required' => false]); + } + } + }); + } } diff --git a/database/migrations/2024_03_18_164714_add_note_to_checkout_acceptance_table.php b/database/migrations/2024_03_18_164714_add_note_to_checkout_acceptance_table.php new file mode 100644 index 0000000000..75d251b60d --- /dev/null +++ b/database/migrations/2024_03_18_164714_add_note_to_checkout_acceptance_table.php @@ -0,0 +1,32 @@ +text('note')->after('signature_filename')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('checkout_acceptances', function (Blueprint $table) { + $table->dropColumn('note'); + }); + } +} diff --git a/package-lock.json b/package-lock.json index 1a61476f90..e444bbb02f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2379,9 +2379,9 @@ } }, "alpinejs": { - "version": "3.13.5", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.5.tgz", - "integrity": "sha512-1d2XeNGN+Zn7j4mUAKXtAgdc4/rLeadyTMWeJGXF5DzwawPBxwTiBhFFm6w/Ei8eJxUZeyNWWSD9zknfdz1kEw==", + "version": "3.13.10", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.10.tgz", + "integrity": "sha512-86RB307VWICex0vG15Eq0x058cNNsvS57ohrjN6n/TJAVSFV+zXOK/E34nNHDHc6Poq+yTNCLqEzPqEkRBTMRQ==", "requires": { "@vue/reactivity": "~3.1.1" } diff --git a/package.json b/package.json index 0526d5370b..13bfc8a25f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "acorn-import-assertions": "^1.9.0", "admin-lte": "^2.4.18", "ajv": "^6.12.6", - "alpinejs": "3.13.5", + "alpinejs": "^3.13.10", "blueimp-file-upload": "^9.34.0", "bootstrap": "^3.4.1", "bootstrap-colorpicker": "^2.5.3", diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt new file mode 100644 index 0000000000..5b8d0c144a --- /dev/null +++ b/public/.well-known/security.txt @@ -0,0 +1,7 @@ +Contact: mailto:security@snipeitapp.com +Expires: 2025-05-16T11:30:00.000Z +Acknowledgments: https://snipeitapp.com/thanks +Preferred-Languages: en-US, pt-PT, de-DE +Canonical: https://github.com/snipe/snipe-it/blob/master/public/.well-known/security.txt +Policy: https://snipeitapp.com/security +Hiring: https://snipeitapp.com/company/careers \ No newline at end of file diff --git a/resources/lang/en-US/admin/settings/general.php b/resources/lang/en-US/admin/settings/general.php index 71fb8eb2c6..01adf36ebf 100644 --- a/resources/lang/en-US/admin/settings/general.php +++ b/resources/lang/en-US/admin/settings/general.php @@ -49,6 +49,7 @@ return [ 'default_eula_text' => 'Default EULA', 'default_language' => 'Default Language', 'default_eula_help_text' => 'You can also associate custom EULAs to specific asset categories.', + 'acceptance_note' => 'Add a note for your decision (Optional)', 'display_asset_name' => 'Display Asset Name', 'display_checkout_date' => 'Display Checkout Date', 'display_eol' => 'Display EOL in table view', diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index 0b6f613399..9510921b1f 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -295,6 +295,7 @@ return [ 'user' => 'User', 'accepted' => 'accepted', 'declined' => 'declined', + 'declined_note' => 'Declined Notes', 'unassigned' => 'Unassigned', 'unaccepted_asset_report' => 'Unaccepted Assets', 'users' => 'Users', diff --git a/resources/views/account/accept/create.blade.php b/resources/views/account/accept/create.blade.php index fa6e4b8b56..b9843cd47b 100644 --- a/resources/views/account/accept/create.blade.php +++ b/resources/views/account/accept/create.blade.php @@ -64,6 +64,15 @@ +
+
+
+ +
+
+ +
+
@if ($snipeSettings->require_accept_signature=='1')
@@ -133,5 +142,6 @@ } }); + @stop \ No newline at end of file diff --git a/resources/views/hardware/bulk.blade.php b/resources/views/hardware/bulk.blade.php index fa4680e2e8..d4f0544851 100755 --- a/resources/views/hardware/bulk.blade.php +++ b/resources/views/hardware/bulk.blade.php @@ -196,8 +196,8 @@ @include("models/custom_fields_form_bulk_edit",["models" => $models]) - @foreach ($assets as $key => $value) - + @foreach($assets as $asset) + @endforeach
diff --git a/resources/views/notifications/markdown/asset-acceptance.blade.php b/resources/views/notifications/markdown/asset-acceptance.blade.php index 50191d4a37..93292375a5 100644 --- a/resources/views/notifications/markdown/asset-acceptance.blade.php +++ b/resources/views/notifications/markdown/asset-acceptance.blade.php @@ -13,6 +13,9 @@ @if (isset($declined_date)) | **{{ ucfirst(trans('general.declined')) }}** | {{ $declined_date }} | @endif +@if (isset($note)) +| **{{ trans('general.notes') }}** | {{ $note }} | +@endif @if ((isset($item_tag)) && ($item_tag!='')) | **{{ trans('mail.asset_tag') }}** | {{ $item_tag }} | @endif diff --git a/resources/views/partials/label2-field-definitions.blade.php b/resources/views/partials/label2-field-definitions.blade.php index b701ffe69a..2a44fbf257 100644 --- a/resources/views/partials/label2-field-definitions.blade.php +++ b/resources/views/partials/label2-field-definitions.blade.php @@ -209,7 +209,9 @@ array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]); }, - get valueString() { return this.toString(this.fields); }, + get valueString() { + return this.getCombinedString(this.fields); + }, onTest: function(a) { console.log('test', a); }, @@ -229,7 +231,7 @@ }) })); }, - toString: function(fields) { + getCombinedString: function (fields) { return fields .map(field => field.options .map(option => option.label + '=' + option.datasource) diff --git a/tests/Feature/Assets/AssetsBulkEditTest.php b/tests/Feature/Assets/AssetsBulkEditTest.php new file mode 100644 index 0000000000..ee684d7676 --- /dev/null +++ b/tests/Feature/Assets/AssetsBulkEditTest.php @@ -0,0 +1,208 @@ +viewAssets()->editAssets()->create(); + $assets = Asset::factory()->count(2)->create(); + + $id_array = $assets->pluck('id')->toArray(); + + $this->actingAs($user)->post('/hardware/bulkedit', [ + 'ids' => $id_array, + 'order' => 'asc', + 'bulk_actions' => 'edit', + 'sort' => 'id' + ])->assertStatus(200); + } + + public function testStandardUserCannotAccessPage() + { + $user = User::factory()->create(); + $assets = Asset::factory()->count(2)->create(); + + $id_array = $assets->pluck('id')->toArray(); + + $this->actingAs($user)->post('/hardware/bulkedit', [ + 'ids' => $id_array, + 'order' => 'asc', + 'bulk_actions' => 'edit', + 'sort' => 'id' + ])->assertStatus(403); + } + + public function testBulkEditAssetsAcceptsAllPossibleAttributes() + { + // sets up all needed models and attributes on the assets + // this test does not deal with custom fields - will be dealt with in separate cases + $status1 = Statuslabel::factory()->create(); + $status2 = Statuslabel::factory()->create(); + $model1 = AssetModel::factory()->create(); + $model2 = AssetModel::factory()->create(); + $supplier1 = Supplier::factory()->create(); + $supplier2 = Supplier::factory()->create(); + $company1 = Company::factory()->create(); + $company2 = Company::factory()->create(); + $assets = Asset::factory()->count(10)->create([ + 'purchase_date' => '2023-01-01', + 'expected_checkin' => '2023-01-01', + 'status_id' => $status1->id, + 'model_id' => $model1->id, + // skipping locations on this test, it deserves it's own test + 'purchase_cost' => 1234.90, + 'supplier_id' => $supplier1->id, + 'company_id' => $company1->id, + 'order_number' => '123456', + 'warranty_months' => 24, + 'next_audit_date' => '2024-06-01', + 'requestable' => false + ]); + + // gets the ids together to submit to the endpoint + $id_array = $assets->pluck('id')->toArray(); + + // submits the ids and new values for each attribute + $this->actingAs(User::factory()->editAssets()->create())->post(route('hardware/bulksave'), [ + 'ids' => $id_array, + 'purchase_date' => '2024-01-01', + 'expected_checkin' => '2024-01-01', + 'status_id' => $status2->id, + 'model_id' => $model2->id, + 'purchase_cost' => 5678.92, + 'supplier_id' => $supplier2->id, + 'company_id' => $company2->id, + 'order_number' => '7890', + 'warranty_months' => 36, + 'next_audit_date' => '2025-01-01', + 'requestable' => true + ]) + ->assertStatus(302) + ->assertSessionHasNoErrors(); + + // asserts that each asset has the updated values + Asset::findMany($id_array)->each(function (Asset $asset) use ($status2, $model2, $supplier2, $company2) { + $this->assertEquals('2024-01-01', $asset->purchase_date->format('Y-m-d')); + $this->assertEquals('2024-01-01', $asset->expected_checkin->format('Y-m-d')); + $this->assertEquals($status2->id, $asset->status_id); + $this->assertEquals($model2->id, $asset->model_id); + $this->assertEquals(5678.92, $asset->purchase_cost); + $this->assertEquals($supplier2->id, $asset->supplier_id); + $this->assertEquals($company2->id, $asset->company_id); + $this->assertEquals(7890, $asset->order_number); + $this->assertEquals(36, $asset->warranty_months); + $this->assertEquals('2025-01-01', $asset->next_audit_date->format('Y-m-d')); + // shouldn't requestable be cast as a boolean??? it's not. + $this->assertEquals(1, $asset->requestable); + }); + } + + public function testBulkEditAssetsAcceptsAndUpdatesUnencryptedCustomFields() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + CustomField::factory()->ram()->create(); + CustomField::factory()->cpu()->create(); + + // when getting the custom field directly from the factory the field has not been fully created yet + // so we have to do a query afterwards to get the actual model :shrug: + + $ram = CustomField::where('name', 'RAM')->first(); + $cpu = CustomField::where('name', 'CPU')->first(); + + $assets = Asset::factory()->count(10)->hasMultipleCustomFields([$ram, $cpu])->create([ + $ram->db_column => 8, + $cpu->db_column => '2.1', + ]); + + $id_array = $assets->pluck('id')->toArray(); + + $this->actingAs(User::factory()->editAssets()->create())->post(route('hardware/bulksave'), [ + 'ids' => $id_array, + $ram->db_column => 16, + $cpu->db_column => '4.1', + ])->assertStatus(302); + + Asset::findMany($id_array)->each(function (Asset $asset) use ($ram, $cpu) { + $this->assertEquals(16, $asset->{$ram->db_column}); + $this->assertEquals('4.1', $asset->{$cpu->db_column}); + }); + } + + public function testBulkEditAssetsAcceptsAndUpdatesEncryptedCustomFields() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on MySQL'); + + CustomField::factory()->testEncrypted()->create(); + + $encrypted = CustomField::where('name', 'Test Encrypted')->first(); + + $assets = Asset::factory()->count(10)->hasEncryptedCustomField($encrypted)->create([ + $encrypted->db_column => Crypt::encrypt('Original Encrypted Text'), + ]); + + $id_array = $assets->pluck('id')->toArray(); + + $this->actingAs(User::factory()->admin()->create())->post(route('hardware/bulksave'), [ + 'ids' => $id_array, + $encrypted->db_column => 'New Encrypted Text', + ])->assertStatus(302); + + Asset::findMany($id_array)->each(function (Asset $asset) use ($encrypted) { + $this->assertEquals('New Encrypted Text', Crypt::decrypt($asset->{$encrypted->db_column})); + }); + } + + public function testBulkEditAssetsRequiresAdminUserToUpdateEncryptedCustomFields() + { + $this->markIncompleteIfMySQL('Custom Fields tests do not work on mysql'); + $edit_user = User::factory()->editAssets()->create(); + $admin_user = User::factory()->admin()->create(); + + CustomField::factory()->testEncrypted()->create(); + + $encrypted = CustomField::where('name', 'Test Encrypted')->first(); + + $admin_assets = Asset::factory()->count(5)->hasEncryptedCustomField($encrypted)->create([ + $encrypted->db_column => Crypt::encrypt('Original Encrypted Text'), + ]); + + $standard_assets = Asset::factory()->count(5)->hasEncryptedCustomField($encrypted)->create([ + $encrypted->db_column => Crypt::encrypt('Original Encrypted Text'), + ]); + + $admin_id_array = $admin_assets->pluck('id')->toArray(); + $standard_id_array = $standard_assets->pluck('id')->toArray(); + + $this->actingAs($admin_user)->post(route('hardware/bulksave'), [ + 'ids' => $admin_id_array, + $encrypted->db_column => 'New Encrypted Text', + ])->assertStatus(302); + + // do we want to return an error when this happens??? + $this->actingAs($edit_user)->post(route('hardware/bulksave'), [ + 'ids' => $standard_id_array, + $encrypted->db_column => 'New Encrypted Text', + ])->assertStatus(302); + + Asset::findMany($admin_id_array)->each(function (Asset $asset) use ($encrypted) { + $this->assertEquals('New Encrypted Text', Crypt::decrypt($asset->{$encrypted->db_column})); + }); + + Asset::findMany($standard_id_array)->each(function (Asset $asset) use ($encrypted) { + $this->assertEquals('Original Encrypted Text', Crypt::decrypt($asset->{$encrypted->db_column})); + }); + } +}