mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-18 11:15:42 +00:00
90 lines
3.3 KiB
PHP
90 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Requests;
|
|
|
|
use App\Models\Actionlog;
|
|
use App\Models\AssetModel;
|
|
use App\Models\User;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Regression for the AssetModel requestability bypass. Before the
|
|
* fix, `POST /account/request/asset_model/{modelId}` skipped the
|
|
* requestable-flag check that Asset and Accessory get on the same
|
|
* endpoint. A normal user could hit that URL and create a request
|
|
* record for a model whose admin had explicitly set
|
|
* `requestable = 0`. Fix extends the gate at
|
|
* ViewAssetsController::getRequestItem to also validate
|
|
* AssetModel::RequestableModels().
|
|
*
|
|
* Also covers the adjacent hardening: the route parameter {itemType}
|
|
* is now constrained to `asset|asset_model|accessory` at the route
|
|
* level so arbitrary App\Models\* class names can no longer be
|
|
* instantiated via user-controlled URL segments.
|
|
*/
|
|
class AssetModelRequestGateTest extends TestCase
|
|
{
|
|
public function test_non_requestable_asset_model_cannot_be_requested_via_direct_post(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$model = AssetModel::factory()->create(['requestable' => 0]);
|
|
|
|
$response = $this->actingAs($user)
|
|
->post(route('account/request-item', ['itemType' => 'asset_model', 'itemId' => $model->id]));
|
|
|
|
$response->assertStatus(302);
|
|
$response->assertSessionHas('error');
|
|
|
|
// No request record should have been created.
|
|
$this->assertDatabaseMissing('action_logs', [
|
|
'item_id' => $model->id,
|
|
'item_type' => AssetModel::class,
|
|
'action_type' => 'requested',
|
|
]);
|
|
}
|
|
|
|
public function test_requestable_asset_model_can_still_be_requested(): void
|
|
{
|
|
// Non-regression: flipping the requestable flag ON must still
|
|
// let the request go through.
|
|
$user = User::factory()->create();
|
|
$model = AssetModel::factory()->create(['requestable' => 1]);
|
|
|
|
$response = $this->actingAs($user)
|
|
->post(route('account/request-item', ['itemType' => 'asset_model', 'itemId' => $model->id]));
|
|
|
|
$response->assertStatus(302);
|
|
$response->assertSessionHas('success');
|
|
}
|
|
|
|
public function test_arbitrary_item_type_is_rejected_by_route_constraint(): void
|
|
{
|
|
// Route now constrains {itemType} to asset|asset_model|accessory,
|
|
// so arbitrary values like `user` no longer resolve to
|
|
// App\Models\User via the studly_case concatenation inside
|
|
// the controller. Anything else 404s at the router.
|
|
$user = User::factory()->create();
|
|
$victim = User::factory()->create();
|
|
|
|
$response = $this->actingAs($user)
|
|
->post('/account/request/user/'.$victim->id);
|
|
|
|
$response->assertNotFound();
|
|
}
|
|
|
|
public function test_ignored_item_types_do_not_reach_the_controller(): void
|
|
{
|
|
// Belt-and-suspenders check for a couple more shapes.
|
|
$user = User::factory()->create();
|
|
|
|
foreach (['location', 'component', 'consumable', 'license'] as $itemType) {
|
|
$response = $this->actingAs($user)
|
|
->post('/account/request/'.$itemType.'/1');
|
|
$response->assertNotFound();
|
|
}
|
|
|
|
// Make sure no stray Actionlog rows landed from the loop.
|
|
$this->assertSame(0, Actionlog::where('action_type', 'requested')->count());
|
|
}
|
|
}
|