Reject ambiguous inverted enrollment selectors

This commit is contained in:
ms2k 2026-08-07 04:07:19 +09:00
commit 707b060501
3 changed files with 40 additions and 0 deletions

View file

@ -46,6 +46,11 @@ class EnrollmentInspector
return false;
}
public static function inversionIsUnambiguous(string $selector): bool
{
return !str_contains($selector, ',');
}
private function loadAliases(): array
{
if (!class_exists('\OPNsense\Firewall\Alias')) {

View file

@ -6,7 +6,36 @@
namespace OPNsense\NordVPN;
use OPNsense\Base\BaseModel;
use OPNsense\Base\Messages\Message;
class Settings extends BaseModel
{
public function performValidation($validateFullModel = false)
{
$messages = parent::performValidation($validateFullModel);
foreach ($this->enrollments->enrollment->iterateItems() as $enrollment) {
if (!$validateFullModel && !$enrollment->isFieldChanged()) {
continue;
}
if (
(string)$enrollment->source_not === '1' &&
!EnrollmentInspector::inversionIsUnambiguous((string)$enrollment->source)
) {
$messages->appendMessage(new Message(
gettext('Inverting sources is only allowed for single targets to avoid mis-interpretations'),
$enrollment->source->__reference
));
}
if (
(string)$enrollment->destination_not === '1' &&
!EnrollmentInspector::inversionIsUnambiguous((string)$enrollment->destination)
) {
$messages->appendMessage(new Message(
gettext('Inverting destinations is only allowed for single targets to avoid mis-interpretations'),
$enrollment->destination->__reference
));
}
}
return $messages;
}
}

View file

@ -84,6 +84,12 @@ final class StoreAndInspectorTest extends TestCase
$this->assertTrue($inspector->includesPrivateDestinations('10.0.0.0/8', true));
}
public function testInversionRequiresOneNativeSelector(): void
{
$this->assertTrue(EnrollmentInspector::inversionIsUnambiguous('network_group'));
$this->assertFalse(EnrollmentInspector::inversionIsUnambiguous('private,relay'));
}
public function testStatusWritesAtomicallyAndRetainsDefaults(): void
{
$path = sys_get_temp_dir() . '/nordvpn-status-' . bin2hex(random_bytes(8)) . '.json';