diff --git a/plist b/plist index 7e3354d1c..23eb787ab 100644 --- a/plist +++ b/plist @@ -673,7 +673,6 @@ /usr/local/opnsense/mvc/app/models/OPNsense/Base/Validators/Email.php /usr/local/opnsense/mvc/app/models/OPNsense/Base/Validators/IntegerValidator.php /usr/local/opnsense/mvc/app/models/OPNsense/Base/Validators/MinMaxValidator.php -/usr/local/opnsense/mvc/app/models/OPNsense/Base/Validators/NetworkValidator.php /usr/local/opnsense/mvc/app/models/OPNsense/Base/Validators/Numericality.php /usr/local/opnsense/mvc/app/models/OPNsense/Base/Validators/PresenceOf.php /usr/local/opnsense/mvc/app/models/OPNsense/Base/Validators/Regex.php @@ -1042,6 +1041,7 @@ /usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/ModelRelationFieldTest/config.xml /usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/NetworkAliasFieldTest.php /usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/NetworkAliasFieldTest/config.xml +/usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/NetworkFieldTest.php /usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/OptionFieldTest.php /usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/PortFieldTest.php /usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/ProtocolFieldTest.php diff --git a/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/NetworkField.php b/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/NetworkField.php index 7d3e0a0b5..21c1baf6b 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/NetworkField.php +++ b/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/NetworkField.php @@ -28,7 +28,8 @@ namespace OPNsense\Base\FieldTypes; -use OPNsense\Base\Validators\NetworkValidator; +use OPNsense\Firewall\Util; +use OPNsense\Base\Validators\CallbackValidator; /** * @package OPNsense\Base\FieldTypes @@ -190,24 +191,90 @@ class NetworkField extends BaseField return gettext('Please specify a valid network segment or IP address.'); } + /** + * @param string $input data to test + * @return bool if valid network address or segment (using this objects settings) + */ + protected function isValidInput($input) + { + $result = true; + if ($this->internalFieldSeparator == null) { + $values = [$input]; + } else { + $values = explode($this->internalFieldSeparator, $input); + } + foreach ($values as $value) { + // parse filter options + $filterOpt = 0; + switch (strtolower($this->internalAddressFamily ?? '')) { + case "ipv4": + $filterOpt |= FILTER_FLAG_IPV4; + break; + case "ipv6": + $filterOpt |= FILTER_FLAG_IPV6; + break; + default: + $filterOpt |= FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6; + } + + // split network + if (strpos($value, "/") !== false) { + if ($this->internalNetMaskAllowed === false) { + return false; + } else { + $cidr = $value; + $parts = explode("/", $value); + if (count($parts) > 2 || !ctype_digit($parts[1])) { + // more parts then expected or second part is not numeric + return false; + } else { + $mask = $parts[1]; + $value = $parts[0]; + if (strpos($parts[0], ":") !== false) { + // probably ipv6, mask must be between 0..128 + if ($mask < 0 || $mask > 128) { + return false; + } + } else { + // most likely ipv4 address, mask must be between 0..32 + if ($mask < 0 || $mask > 32) { + return false; + } + } + } + + if ($this->internalStrict === true && !Util::isSubnetStrict($cidr)) { + return false; + } + } + } elseif ($this->internalNetMaskRequired === true) { + return false; + } + + if (filter_var($value, FILTER_VALIDATE_IP, $filterOpt) === false) { + return false; + } + } + return true;; + } + /** * retrieve field validators for this field type - * @return array returns Text/regex validator + * @return array returns validators */ public function getValidators() { $validators = parent::getValidators(); if ($this->internalValue != null) { if ($this->internalValue != "any" || $this->internalWildcardEnabled == false) { - // accept any as target - $validators[] = new NetworkValidator([ - 'message' => $this->getValidationMessage(), - 'split' => $this->internalFieldSeparator, - 'netMaskRequired' => $this->internalNetMaskRequired, - 'netMaskAllowed' => $this->internalNetMaskAllowed, - 'version' => $this->internalAddressFamily, - 'strict' => $this->internalStrict - ]); + $that = $this; + $validators[] = new CallbackValidator(["callback" => function ($data) use ($that) { + $messages = []; + if (!$that->isValidInput($data)) { + $messages[] = $this->getValidationMessage(); + } + return $messages; + }]); } } return $validators; diff --git a/src/opnsense/mvc/app/models/OPNsense/Base/Validators/NetworkValidator.php b/src/opnsense/mvc/app/models/OPNsense/Base/Validators/NetworkValidator.php deleted file mode 100644 index 6202ee253..000000000 --- a/src/opnsense/mvc/app/models/OPNsense/Base/Validators/NetworkValidator.php +++ /dev/null @@ -1,133 +0,0 @@ -getOption('message'); - $fieldSplit = $this->getOption('split', null); - if ($fieldSplit == null) { - $values = array($validator->getValue($attribute)); - } else { - $values = explode($fieldSplit, $validator->getValue($attribute)); - } - foreach ($values as $value) { - // parse filter options - $filterOpt = 0; - switch (strtolower($this->getOption('version') ?? '')) { - case "ipv4": - $filterOpt |= FILTER_FLAG_IPV4; - break; - case "ipv6": - $filterOpt |= FILTER_FLAG_IPV6; - break; - default: - $filterOpt |= FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6; - } - - if ($this->getOption('noReserved') === true) { - $filterOpt |= FILTER_FLAG_NO_RES_RANGE; - } - - if ($this->getOption('noPrivate') === true) { - $filterOpt |= FILTER_FLAG_NO_PRIV_RANGE; - } - - // split network - if (strpos($value, "/") !== false) { - if ($this->getOption('netMaskAllowed') === false) { - $result = false; - } else { - $cidr = $value; - $parts = explode("/", $value); - if (count($parts) > 2 || !ctype_digit($parts[1])) { - // more parts then expected or second part is not numeric - $result = false; - } else { - $mask = $parts[1]; - $value = $parts[0]; - if (strpos($parts[0], ":") !== false) { - // probably ipv6, mask must be between 0..128 - if ($mask < 0 || $mask > 128) { - $result = false; - } - } else { - // most likely ipv4 address, mask must be between 0..32 - if ($mask < 0 || $mask > 32) { - $result = false; - } - } - } - - if ($this->getOption('strict') === true && !Util::isSubnetStrict($cidr)) { - $result = false; - } - } - } elseif ($this->getOption('netMaskRequired') === true) { - $result = false; - } - - - if (filter_var($value, FILTER_VALIDATE_IP, $filterOpt) === false) { - $result = false; - } - - if (!$result) { - // append validation message - $validator->appendMessage(new Message($msg, $attribute, 'NetworkValidator')); - } - } - - return $result; - } -} diff --git a/src/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/NetworkFieldTest.php b/src/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/NetworkFieldTest.php new file mode 100644 index 000000000..53677262a --- /dev/null +++ b/src/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/NetworkFieldTest.php @@ -0,0 +1,135 @@ +assertInstanceOf('\OPNsense\Base\FieldTypes\NetworkField', new NetworkField()); + } + + public function testRequiredEmpty() + { + $this->expectException(\OPNsense\Base\ValidationException::class); + $this->expectExceptionMessage("PresenceOf"); + $field = new NetworkField(); + $field->setRequired("Y"); + $field->setValue(""); + $this->validateThrow($field); + } + + public function testRequiredNotEmpty() + { + $field = new NetworkField(); + $field->setRequired("Y"); + $field->setValue("192.168.1.1"); + $this->assertEmpty($this->validate($field)); + } + + public function testValidValuesV4() + { + $field = new NetworkField(); + $field->setNetMaskRequired("Y"); + $field->setAddressFamily("ipv4"); + foreach (["192.168.1.1/24", "10.0.0.1/24"] as $value) { + $field->setValue($value); + $this->assertEmpty($this->validate($field)); + } + } + + public function testValidValuesV6() + { + $field = new NetworkField(); + $field->setNetMaskRequired("Y"); + $field->setAddressFamily("ipv6"); + foreach (["2000::1/128", "fe80::5a8c:fcff:0001:ffe2/64"] as $value) { + $field->setValue($value); + $this->assertEmpty($this->validate($field)); + } + } + + public function testInValidValuesV4() + { + $field = new NetworkField(); + $field->setNetMaskRequired("Y"); + $field->setAddressFamily("ipv4"); + foreach (["192.168.1.1", "2000::1", "A"] as $value) { + $field->setValue($value); + $this->assertNotEmpty($this->validate($field)); + } + } + + public function testInValidValuesV6() + { + $field = new NetworkField(); + $field->setNetMaskRequired("Y"); + $field->setAddressFamily("ipv6"); + foreach (["192.168.1.1", "2000::1", "A"] as $value) { + $field->setValue($value); + $this->assertNotEmpty($this->validate($field)); + } + } + + public function testValidValuesStrict() + { + $field = new NetworkField(); + $field->setNetMaskRequired("Y"); + $field->setStrict("Y"); + foreach (["192.168.1.0/24", "2000::0/64"] as $value) { + $field->setValue($value); + $this->assertEmpty($this->validate($field)); + } + } + + public function testInValidValuesStrict() + { + $field = new NetworkField(); + $field->setNetMaskRequired("Y"); + $field->setStrict("Y"); + foreach (["192.168.1.1/24", "2000::1:1/64"] as $value) { + $field->setValue($value); + $this->assertNotEmpty($this->validate($field)); + } + } + + public function testIsContainer() + { + $field = new NetworkField(); + $this->assertFalse($field->isContainer()); + } +}