diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Field.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Field.inc index 2e052053..4914f279 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Field.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Field.inc @@ -26,6 +26,11 @@ class Field { */ const SENSITIVE_MASK = '********'; + /** + * @const MANY_MAXIMUM is the maximum number of items a 'many' field can hold it's array. + */ + const MANY_MAXIMUM = 65535; + /** * Represents the current value for this Field. * @@ -144,7 +149,7 @@ class Field { public bool $representation_only = false, public bool $many = false, public int $many_minimum = 0, - public int $many_maximum = 128, + public int $many_maximum = Field::MANY_MAXIMUM, public string|null $delimiter = ',', public string $verbose_name = '', public string $verbose_name_plural = '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/Base64Field.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/Base64Field.inc index 9805a488..b36fa094 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/Base64Field.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/Base64Field.inc @@ -85,7 +85,7 @@ class Base64Field extends Field { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, string|null $delimiter = ',', string $verbose_name = '', string $verbose_name_plural = '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/DateTimeField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/DateTimeField.inc index 34b85f9b..b0a992db 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/DateTimeField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/DateTimeField.inc @@ -93,7 +93,7 @@ class DateTimeField extends Field { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, string|null $delimiter = ',', string $verbose_name = '', string $verbose_name_plural = '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FilterAddressField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FilterAddressField.inc index c74aa375..ecaa3ccc 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FilterAddressField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FilterAddressField.inc @@ -5,6 +5,7 @@ namespace RESTAPI\Fields; require_once 'RESTAPI/autoloader.inc'; require_once 'RESTAPI/Fields/InterfaceField.inc'; +use RESTAPI\Core\Field; use RESTAPI\Models\FirewallAlias; use RESTAPI\Responses\ServerError; use RESTAPI\Responses\ValidationError; @@ -111,7 +112,7 @@ class FilterAddressField extends InterfaceField { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, string|null $delimiter = ',', string $verbose_name = '', string $verbose_name_plural = '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FloatField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FloatField.inc index 25665d8e..234323be 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FloatField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FloatField.inc @@ -5,11 +5,12 @@ namespace RESTAPI\Fields; require_once 'RESTAPI/autoloader.inc'; use RESTAPI; +use RESTAPI\Core\Field; /** * Defines a Field object for validating and storing a floating point number. */ -class FloatField extends RESTAPI\Core\Field { +class FloatField extends Field { /** * Defines the FloatField object and sets its options. * @param bool $required If `true`, this field is required to have a value at all times. @@ -80,7 +81,7 @@ class FloatField extends RESTAPI\Core\Field { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, public int $minimum = 0, public int $maximum = PHP_INT_MAX, string|null $delimiter = ',', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ForeignModelField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ForeignModelField.inc index a740b189..9a2920e8 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ForeignModelField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ForeignModelField.inc @@ -118,7 +118,7 @@ class ForeignModelField extends Field { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, string|null $delimiter = ',', string $verbose_name = '', string $verbose_name_plural = '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/IntegerField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/IntegerField.inc index d77c79a1..538887dd 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/IntegerField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/IntegerField.inc @@ -81,7 +81,7 @@ class IntegerField extends Field { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, public int $minimum = 0, public int $maximum = PHP_INT_MAX, string|null $delimiter = ',', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/InterfaceField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/InterfaceField.inc index c37421f0..27d941ab 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/InterfaceField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/InterfaceField.inc @@ -6,6 +6,7 @@ require_once 'RESTAPI/Fields/StringField.inc'; require_once 'RESTAPI/autoloader.inc'; use RESTAPI; +use RESTAPI\Core\Field; use RESTAPI\Core\Model; use RESTAPI\Core\ModelSet; use RESTAPI\Models\NetworkInterface; @@ -97,7 +98,7 @@ class InterfaceField extends StringField { bool $editable = true, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, string|null $delimiter = ',', string $verbose_name = '', string $verbose_name_plural = '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/KeyLenField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/KeyLenField.inc new file mode 100644 index 00000000..67ff48ea --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/KeyLenField.inc @@ -0,0 +1,204 @@ + "type1"] to this parameter. + * @param array $validators An array of Validator objects to run against this field. + * @param string $help_text Set a description for this field. This description will be used in API documentation. + */ + public function __construct( + bool $required = false, + bool $unique = false, + mixed $default = null, + string $default_callable = '', + array $choices = [], + string $choices_callable = '', + bool $allow_null = false, + bool $editable = true, + bool $read_only = false, + bool $write_only = false, + bool $representation_only = false, + bool $many = false, + int $many_minimum = 0, + int $many_maximum = Field::MANY_MAXIMUM, + public int $minimum = 0, + public int $maximum = PHP_INT_MAX, + string|null $delimiter = ',', + string $verbose_name = '', + string $verbose_name_plural = '', + string $internal_name = '', + string $internal_namespace = '', + array $referenced_by = [], + array $conditions = [], + array $validators = [], + string $help_text = '', + ) { + parent::__construct( + type: 'integer', + required: $required, + unique: $unique, + default: $default, + default_callable: $default_callable, + choices: $choices, + choices_callable: $choices_callable, + allow_null: $allow_null, + editable: $editable, + read_only: $read_only, + write_only: $write_only, + representation_only: $representation_only, + many: $many, + many_minimum: $many_minimum, + many_maximum: $many_maximum, + delimiter: $delimiter, + verbose_name: $verbose_name, + verbose_name_plural: $verbose_name_plural, + internal_name: $internal_name, + internal_namespace: $internal_namespace, + referenced_by: $referenced_by, + conditions: $conditions, + validators: $validators + [ + new RESTAPI\Validators\NumericRangeValidator(minimum: $minimum, maximum: $maximum), + ], + help_text: $help_text, + ); + } + + /** + * Converts the field value from its representation value into it's internal value. This namely handles converting + * 0 to 'auto'. + * @param mixed $representation_value The representation value to convert to its internal value + */ + protected function _to_internal(mixed $representation_value): array|string|null { + if ($representation_value === 0) { + return parent::_to_internal('auto'); + } + return parent::_to_internal($representation_value); + } + + /** + * Converts the field value to its representation form from its internal pfSense configuration value. + * @param string $internal_value The internal value from the pfSense configuration. + * @return int The field value in its representation form. + */ + protected function _from_internal(mixed $internal_value): mixed { + # Return the value as an integer if it's numeric + if (is_numeric($internal_value)) { + return intval($internal_value); + } + + # If the value is 'auto', return 0 (0 is the representation we use for auto) + if ($internal_value === 'auto') { + return 0; + } + + # If the value is an empty string, assume it's null + if ($internal_value === '') { + return null; + } + + # Otherwise, the internal value cannot be represented by this Field. Throw an error. + throw new RESTAPI\Responses\ServerError( + message: "Cannot parse KeyLenField '$this->name' from internal because its internal value is not a " . + "numeric value or 'auto'. Consider changing this field to a StringField.", + response_id: 'KEYLEN_FIELD_WITH_NON_INTEGER_INTERNAL_VALUE', + ); + } + + /** + * Converts this Field object to a PHP array representation of an OpenAPI schema property configuration. This is + * used when auto-generating API documentation. This method can be extended to add additional options to the OpenAPI + * schema property. + * @link https://swagger.io/docs/specification/data-models/ + * @return array A PHP array containing this field as a OpenAPI schema property configuration. + */ + public function to_openapi_property(): array { + # Run the parent to_openapi_property() to obtain the base property object, then make changes as needed. + $openapi_property = parent::to_openapi_property(); + + # Add the minimum and maximum to the OpenAPI property. + if ($this->many) { + $openapi_property['items']['minimum'] = $this->minimum; + $openapi_property['items']['maximum'] = $this->maximum; + } else { + $openapi_property['minimum'] = $this->minimum; + $openapi_property['maximum'] = $this->maximum; + } + + return $openapi_property; + } + + /** + * Converts this Field object into a pfSense webConfigurator form input. This method can be overridden by a child + * class to add custom input field creation. + * @param string $type The HTML input tag type. Not all Fields support input types. + * @param array $attributes An array of additional HTML input tag attributes. Not all Fields support input attributes. + * @return object The pfSense webConfigurator form input object. + * @link https://github.com/pfsense/pfsense/tree/master/src/usr/local/www/classes/Form + */ + public function to_form_input(string $type = 'number', array $attributes = []): object { + $attributes += ['min' => $this->minimum, 'max' => $this->maximum]; + return parent::to_form_input(type: $type, attributes: $attributes); + } +} diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ObjectField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ObjectField.inc index 8b323da0..a88a52f2 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ObjectField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ObjectField.inc @@ -90,7 +90,7 @@ class ObjectField extends Field { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, public int $minimum_length = 0, public int $maximum_length = 1024, string|null $delimiter = ',', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/PortField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/PortField.inc index 097ebaaf..afed1b7a 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/PortField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/PortField.inc @@ -94,7 +94,7 @@ class PortField extends Field { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, ?string $delimiter = ',', string $verbose_name = '', string $verbose_name_plural = '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/SpecialNetworkField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/SpecialNetworkField.inc index 07c3af67..f3f76b84 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/SpecialNetworkField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/SpecialNetworkField.inc @@ -5,6 +5,7 @@ namespace RESTAPI\Fields; require_once 'RESTAPI/autoloader.inc'; require_once 'RESTAPI/Fields/InterfaceField.inc'; +use RESTAPI\Core\Field; use RESTAPI\Models\FirewallAlias; use RESTAPI\Models\NetworkInterface; use RESTAPI\Responses\ServerError; @@ -103,7 +104,7 @@ class SpecialNetworkField extends InterfaceField { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, string|null $delimiter = ',', string $verbose_name = '', string $verbose_name_plural = '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/StringField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/StringField.inc index c239f034..45565b28 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/StringField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/StringField.inc @@ -85,7 +85,7 @@ class StringField extends Field { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, public int $minimum_length = 0, public int $maximum_length = 1024, string|null $delimiter = ',', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/UIDField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/UIDField.inc index fa9da148..f04efc46 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/UIDField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/UIDField.inc @@ -5,11 +5,12 @@ namespace RESTAPI\Fields; require_once 'RESTAPI/autoloader.inc'; use RESTAPI; +use RESTAPI\Core\Field; /** * Defines a Field that contains a unique ID. This field will automatically populate a unique ID that is immutable. */ -class UIDField extends RESTAPI\Core\Field { +class UIDField extends Field { /** * Defines the UIDField object and sets its options. * @param string $prefix A specific string to prefix to the generated UID. diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/UnixTimeField.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/UnixTimeField.inc index 47aac9c5..1b3a23da 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/UnixTimeField.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/UnixTimeField.inc @@ -5,6 +5,7 @@ namespace RESTAPI\Fields; require_once 'RESTAPI/autoloader.inc'; use RESTAPI; +use RESTAPI\Core\Field; use RESTAPI\Responses\ServerError; /** @@ -85,7 +86,7 @@ class UnixTimeField extends IntegerField { bool $representation_only = false, bool $many = false, int $many_minimum = 0, - int $many_maximum = 128, + int $many_maximum = Field::MANY_MAXIMUM, int $minimum = 0, int $maximum = PHP_INT_MAX, public bool $auto_add_now = true, diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/IPsecPhase2Encryption.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/IPsecPhase2Encryption.inc index 92dcb61a..bd0777de 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/IPsecPhase2Encryption.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/IPsecPhase2Encryption.inc @@ -4,7 +4,7 @@ namespace RESTAPI\Models; use RESTAPI\Core\Model; use RESTAPI\Dispatchers\IPsecApplyDispatcher; -use RESTAPI\Fields\IntegerField; +use RESTAPI\Fields\KeyLenField; use RESTAPI\Fields\StringField; use RESTAPI\Responses\ValidationError; @@ -13,7 +13,7 @@ use RESTAPI\Responses\ValidationError; */ class IPsecPhase2Encryption extends Model { public StringField $name; - public IntegerField $keylen; + public KeyLenField $keylen; public function __construct(mixed $id = null, mixed $parent_id = null, array $data = [], mixed ...$options) { # Obtain global p2 algorithm variables @@ -34,56 +34,35 @@ class IPsecPhase2Encryption extends Model { internal_name: 'name', help_text: 'The name of the encryption algorithm to use for this P2 encryption item.', ); - $this->keylen = new IntegerField( + $this->keylen = new KeyLenField( required: true, + choices_callable: 'get_supported_keylens', internal_name: 'keylen', conditions: ['name' => $this->get_keylen_enabled_algos()], - help_text: 'The key length for the encryption algorithm.', + help_text: 'The key length for the encryption algorithm. Use 0 to select key length automatically.', ); parent::__construct($id, $parent_id, $data, ...$options); } - /** - * Adds extra validation to the `keylen` field. - * @param int $keylen The incoming value to be validated. - * @returns int The validated value to be assigned. - * @throws ValidationError When the $keylen is not supported by the - * `name` field's assigned value. - */ - public function validate_keylen(int $keylen): int { - # Variables - $supported_keylens = $this->get_supported_keylens(name: $this->name->value); - - # Throw a validation error if this $keylen is not supported for the assigned algo - if (!in_array($keylen, $supported_keylens)) { - throw new ValidationError( - message: "Field `keylen` value `$keylen` is not valid for the `{$this->name->value}` algorithm.", - response_id: 'IPSEC_PHASE_2_ENCRYPTION_ALGORITHM_KEYLEN_INVALID_CHOICE', - ); - } - - return $keylen; - } - /** * Obtains all supported key lengths for an encryption algorithm with a provided algorithm name. * @param string $name The encryption algorithm name to obtain key lengths for. * @returns array An array of supported key lengths for this encryption algorithm. */ - public static function get_supported_keylens(string $name): array { + public function get_supported_keylens(): array { global $p2_ealgos; # Throw an error if an encryption algorithm does not exist with this name. - if (!array_key_exists($name, $p2_ealgos)) { + if (!array_key_exists($this->name->value, $p2_ealgos)) { throw new ValidationError( - message: "Could not obtain supported key lengths for unknown algorithm `$name`.", + message: "Could not obtain supported key lengths for unknown algorithm `{$this->name->value}`.", response_id: 'IPSEC_PHASE_2_ENCRYPTION_COULD_NOT_GET_KEYLENS_FOR_UNKNOWN_ALGO', ); } # Obtain the attributes for the encryption algorithm with this $name - $p2_ealgo = $p2_ealgos[$name]; + $p2_ealgo = $p2_ealgos[$this->name->value]; $key_lens = []; # Determine available key lengths for this algo when selections are available @@ -100,6 +79,9 @@ class IPsecPhase2Encryption extends Model { } } + # Accept '0', as this is the representation keyword for 'auto' + $key_lens[] = 0; + return $key_lens; } diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIFieldsKeyLenFieldTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIFieldsKeyLenFieldTestCase.inc new file mode 100644 index 00000000..b4c9fa92 --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIFieldsKeyLenFieldTestCase.inc @@ -0,0 +1,97 @@ +assert_equals(get_class($field->validators[0]), 'RESTAPI\Validators\NumericRangeValidator'); + } + + /** + * Checks that `_from_internal()` converts a numeric string to an integer. + */ + public function test_from_internal_numeric_string_returns_integer(): void { + $field = new KeyLenField(); + $field->from_internal('256'); + $this->assert_equals($field->value, 256); + $this->assert_is_true(is_int($field->value)); + } + + /** + * Checks that `_from_internal()` converts the 'auto' keyword to 0. + */ + public function test_from_internal_auto_returns_zero(): void { + $field = new KeyLenField(); + $field->from_internal('auto'); + $this->assert_equals($field->value, 0); + } + + /** + * Checks that `_from_internal()` converts an empty string to null. + */ + public function test_from_internal_empty_string_returns_null(): void { + $field = new KeyLenField(allow_null: true); + $field->from_internal(''); + $this->assert_equals($field->value, null); + } + + /** + * Checks that `_from_internal()` throws a ServerError for non-numeric, non-'auto' values. + */ + public function test_from_internal_invalid_value_throws_server_error(): void { + $this->assert_throws_response( + response_id: 'KEYLEN_FIELD_WITH_NON_INTEGER_INTERNAL_VALUE', + code: 500, + callable: function () { + $field = new KeyLenField(); + $field->from_internal('not_valid'); + }, + ); + } + + /** + * Checks that `keylen` can be assigned the value 0 and that `to_internal()` converts it to 'auto'. + */ + public function test_to_internal_zero_returns_auto(): void { + $field = new KeyLenField(default: 0); + $field->value = 0; + $this->assert_equals($field->value, 0); + $this->assert_equals($field->to_internal(), 'auto'); + } + + /** + * Checks that `to_internal()` passes non-zero integers through normally. + */ + public function test_to_internal_non_zero_passes_through(): void { + $field = new KeyLenField(default: 128); + $field->value = 128; + $this->assert_equals($field->to_internal(), '128'); + } + + /** + * Checks that `to_openapi_property()` includes `minimum` and `maximum` for non-many fields. + */ + public function test_to_openapi_property_includes_min_max(): void { + $field = new KeyLenField(minimum: 64, maximum: 512); + $property = $field->to_openapi_property(); + $this->assert_equals($property['minimum'], 64); + $this->assert_equals($property['maximum'], 512); + } + + /** + * Checks that `to_openapi_property()` includes `minimum` and `maximum` under `items` for many fields. + */ + public function test_to_openapi_property_includes_min_max_for_many(): void { + $field = new KeyLenField(many: true, minimum: 64, maximum: 512); + $property = $field->to_openapi_property(); + $this->assert_equals($property['items']['minimum'], 64); + $this->assert_equals($property['items']['maximum'], 512); + } +} diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsIPsecPhase2EncryptionTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsIPsecPhase2EncryptionTestCase.inc index 56536a12..75114714 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsIPsecPhase2EncryptionTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsIPsecPhase2EncryptionTestCase.inc @@ -3,34 +3,35 @@ namespace RESTAPI\Tests; use RESTAPI\Core\TestCase; +use RESTAPI\Fields\KeyLenField; use RESTAPI\Models\IPsecPhase2Encryption; class APIModelsIPsecPhase2EncryptionTestCase extends TestCase { # NOTE: This model is partially tested by /RESTAPI/Tests/APIModelsIPsecPhase2TestCase /** - * Checks that the `keylen` must be valid for the given encryption algorithm `name`. + * Checks that the `keylen` field uses `get_supported_keylens` as its choices callable. + */ + public function test_keylen_field_uses_choices_callable(): void { + $p2 = new IPsecPhase2Encryption(); + $this->assert_equals($p2->keylen->choices_callable, 'get_supported_keylens'); + } + + /** + * Checks that the `keylen` field's choices_callable correctly restricts values + * to those supported by the selected encryption algorithm. */ public function test_keylen_choices(): void { - # Ensure an error is thrown if the requested key length is not supported for the given encryption algorithm - $this->assert_throws_response( - response_id: 'IPSEC_PHASE_2_ENCRYPTION_ALGORITHM_KEYLEN_INVALID_CHOICE', - code: 400, - callable: function () { - $p2 = new IPsecPhase2Encryption(); - $p2->name->value = 'aes'; - $p2->validate_keylen(257); - }, - ); + # Ensure that `get_supported_keylens` returns only valid key lengths for 'aes' + $p2e = new IPsecPhase2Encryption(name: 'aes'); + $supported = $p2e->get_supported_keylens(); - # Ensure no error is thrown when the key length is supported for the given encryption algorithm - $this->assert_does_not_throw( - callable: function () { - $p2 = new IPsecPhase2Encryption(); - $p2->name->value = 'aes'; - $p2->validate_keylen(256); - }, - ); + $this->assert_is_true(in_array(128, $supported, true)); + $this->assert_is_true(in_array(256, $supported, true)); + $this->assert_is_false(in_array(257, $supported, true)); + + # Ensure 0 (auto) is always included in the supported key lengths + $this->assert_is_true(in_array(0, $supported, true)); } /** @@ -41,8 +42,8 @@ class APIModelsIPsecPhase2EncryptionTestCase extends TestCase { # Ensure an error is not thrown if `get_supported_keylens()` receives a valid `name` $this->assert_does_not_throw( callable: function () { - $keylen_enabled_algo = IPsecPhase2Encryption::get_keylen_enabled_algos()[0]; - IPsecPhase2Encryption::get_supported_keylens(name: $keylen_enabled_algo); + $p2e = new IPsecPhase2Encryption(name: 'aes'); + $p2e->get_supported_keylens(); }, ); @@ -51,7 +52,8 @@ class APIModelsIPsecPhase2EncryptionTestCase extends TestCase { response_id: 'IPSEC_PHASE_2_ENCRYPTION_COULD_NOT_GET_KEYLENS_FOR_UNKNOWN_ALGO', code: 400, callable: function () { - IPsecPhase2Encryption::get_supported_keylens(name: 'not a valid encryption algorithm'); + $p2e = new IPsecPhase2Encryption(name: 'not a valid encryption algorithm'); + $p2e->get_supported_keylens(); }, ); } @@ -63,10 +65,19 @@ class APIModelsIPsecPhase2EncryptionTestCase extends TestCase { public function test_get_supported_keylens(): void { global $p2_ealgos; + $p2e = new IPsecPhase2Encryption(); + # Loop each encryption algorithm that supported variable key lengths foreach (IPsecPhase2Encryption::get_keylen_enabled_algos() as $algo) { + $p2e->name->value = $algo; + # Loop through each key length option available to this algorithm and ensure it meets the correct parameters - foreach (IPsecPhase2Encryption::get_supported_keylens(name: $algo) as $keylen) { + foreach ($p2e->get_supported_keylens() as $keylen) { + # Skip 0 as it's always considered valid + if ($keylen === 0) { + continue; + } + # Obtain the parameters for supported key lengths $min_keylen = $p2_ealgos[$algo]['keysel']['lo']; $max_keylen = $p2_ealgos[$algo]['keysel']['hi'];