Files
FOSSBilling/tests/Modules/System/AdminTest.php
T
52081d284a Improved type casting and removed unnecessary parameters (#3431)
* Cast ticket ID to integer

* Added type casting and removed unnecessary $per_page params

* Fixed tests

* Updated ddev PHP and nginx config

* Support ticket validation and upgrade checks

* Add order mock and constants in ServiceTest

* Require active status for API auth and clear tokens

* Validate client/admin sessions against DB

* Invalidate sessions on user status deactivation

* Check locale availability before toggling

* Escape database name properly

* Validate and cast rel_id to int in ticketCreateForClient to prevent TypeError

Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/ce700346-2168-43cc-8309-fc6baee7cc82

Co-authored-by: admdly <2807620+admdly@users.noreply.github.com>

* Improve rel_id validation error message to include the invalid value

Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/ce700346-2168-43cc-8309-fc6baee7cc82

Co-authored-by: admdly <2807620+admdly@users.noreply.github.com>

* Fix Notification AdminTest mock expectation to match actual paginateDoctrineQuery call

Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/2fb587cf-6312-4b98-89e5-10f074646eaa

Co-authored-by: admdly <2807620+admdly@users.noreply.github.com>

* Validate interface and custom interface inputs

* Tighten custom interface hostname validation

* Accept `per_page` in paginated API responses

* Validate HTTP interfaces and normalize upgrades

* Enforce active status and secure config export

* Cast rel_type/rel_task/rel_status to string in SupportTicketValidator to prevent TypeError

Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/1ecf2bb3-1139-420f-8421-41ae8f6d1d5f

Co-authored-by: admdly <2807620+admdly@users.noreply.github.com>

* Use FILTER_VALIDATE_INT instead of is_numeric for rel_id validation in Service.php

Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/7bacd505-ba15-42da-8f52-7621afa1d634

Co-authored-by: admdly <2807620+admdly@users.noreply.github.com>

* Validate rel_new_value as a positive integer before using as Product ID in upgrade tickets

Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/842c7e1c-255b-4f09-8235-f34681e0392b

Co-authored-by: admdly <2807620+admdly@users.noreply.github.com>

* Tighten `sValidHttpInterface` validation

* Add page parameter to pager calls

---------

Co-authored-by: Adam Daley <admdly@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: admdly <2807620+admdly@users.noreply.github.com>
2026-04-26 20:30:22 +01:00

125 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
namespace SystemTests;
use APIHelper\Request;
use PHPUnit\Framework\TestCase;
final class AdminTest extends TestCase
{
private function ipLookupWorking(): bool
{
$services = ['https://api64.ipify.org', 'https://ifconfig.io/ip', 'https://ip.hestiacp.com/'];
foreach ($services as $service) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_URL, $service);
$ip = curl_exec($ch);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return true;
}
}
return false;
}
public function testClearCache(): void
{
// Clear the cache
$result = Request::makeRequest('admin/system/clear_cache');
$this->assertTrue($result->wasSuccessful(), $result->generatePHPUnitMessage());
$this->assertIsBool($result->getResult());
}
public function testErrorReportingToggle(): void
{
// Get the starting value
$before = Request::makeRequest('admin/system/error_reporting_enabled')->getResult();
$this->assertIsBool($before);
// Toggle the option
$result = Request::makeRequest('admin/system/toggle_error_reporting');
$this->assertTrue($result->wasSuccessful(), $result->generatePHPUnitMessage());
$this->assertTrue($result->getResult());
// Check that it was correctly switched
$after = Request::makeRequest('admin/system/error_reporting_enabled')->getResult();
$this->assertIsBool($after);
$this->assertNotEquals($after, $before);
// Ensure we don't leave error reporting on (it shouldn't report anyways, but this is best practice)
if ($after) {
$result = Request::makeRequest('admin/system/toggle_error_reporting');
$this->assertTrue($result->wasSuccessful(), $result->generatePHPUnitMessage());
$this->assertTrue($result->getResult());
}
}
public function testGetAndSetNetworkInterfaces(): void
{
// Get the list of network interfaces, validate the response is as expected
$result = Request::makeRequest('admin/system/get_interface_ips');
$this->assertTrue($result->wasSuccessful(), $result->generatePHPUnitMessage());
$this->assertIsArray($result->getResult());
// And then validate they are all valid IP addresses
foreach ($result->getResult() as $ip) {
$this->assertTrue((bool) filter_var($ip, FILTER_VALIDATE_IP));
}
// Only test each found interface if ipify.org is functioning
if ($this->ipLookupWorking()) {
foreach ($result->getResult() as $ip) {
$testResult = Request::makeRequest('admin/system/set_interface_ip', ['interface' => $ip]);
$this->assertTrue($testResult->wasSuccessful(), $result->generatePHPUnitMessage());
sleep(2);
$result = Request::makeRequest('admin/system/env', ['ip' => true]);
$this->assertTrue($result->wasSuccessful(), $result->generatePHPUnitMessage());
$this->assertTrue((bool) filter_var($result->getResult(), FILTER_VALIDATE_IP));
}
}
// Finally, set it back to the default interface
Request::makeRequest('admin/system/set_interface_ip', ['interface' => '0']);
}
public function testInvalidInterfaceIsRejected(): void
{
$result = Request::makeRequest('admin/system/set_interface_ip', ['interface' => '12345']);
$this->assertFalse($result->wasSuccessful(), 'An invalid interface IP was accepted when it should have been rejected');
}
public function testInvalidCustomInterfaceIsRejected(): void
{
$result = Request::makeRequest('admin/system/set_interface_ip', ['custom_interface' => '!@#$%']);
$this->assertFalse($result->wasSuccessful(), 'An invalid custom interface was accepted when it should have been rejected');
}
public function testMaliciousInterfaceIsRejected(): void
{
$result = Request::makeRequest('admin/system/set_interface_ip', ['interface' => "x\"; echo 'pwned'; //"]);
$this->assertFalse($result->wasSuccessful(), 'A malicious interface value was accepted when it should have been rejected');
}
public function testMaliciousCustomInterfaceIsRejected(): void
{
$result = Request::makeRequest('admin/system/set_interface_ip', ['custom_interface' => "x\"; passthru('id'); //"]);
$this->assertFalse($result->wasSuccessful(), 'A malicious custom interface value was accepted when it should have been rejected');
}
public function testCustomInterfaceAcceptsValidHostname(): void
{
$result = Request::makeRequest('admin/system/set_interface_ip', ['custom_interface' => 'eth0']);
$this->assertTrue($result->wasSuccessful(), $result->generatePHPUnitMessage());
// Reset to default
Request::makeRequest('admin/system/set_interface_ip', ['custom_interface' => '', 'interface' => '0']);
}
}