diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b66b3eb8..d5faef96 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: /usr/local/bin/VBoxManage controlvm ${{ matrix.FREEBSD_VERSION }} poweroff || true /usr/local/bin/VBoxManage snapshot ${{matrix.FREEBSD_VERSION}} restore initial - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pfSense-pkg-RESTAPI-${{ env.BUILD_VERSION }}-${{ matrix.FREEBSD_ID }}.pkg path: pfSense-pkg-RESTAPI-${{ env.BUILD_VERSION }}-${{ matrix.FREEBSD_ID }}.pkg @@ -96,7 +96,7 @@ jobs: /usr/local/bin/VBoxManage controlvm ${{ matrix.PFSENSE_VERSION }} poweroff || true /usr/local/bin/VBoxManage snapshot ${{ matrix.PFSENSE_VERSION }} restore initial - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: openapi-${{ matrix.PFSENSE_VERSION }}.json path: openapi-${{ matrix.PFSENSE_VERSION }}.json @@ -119,7 +119,7 @@ jobs: path: openapi-${{ matrix.PFSENSE_VERSION }}.json - name: Install Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "20" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c2450ab3..6358f03d 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20 - name: Install npm packages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47e0b6c0..9ea964fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,13 +64,13 @@ jobs: /usr/local/bin/VBoxManage controlvm ${{ matrix.FREEBSD_VERSION }} poweroff || true /usr/local/bin/VBoxManage snapshot ${{matrix.FREEBSD_VERSION}} restore initial - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pfSense-${{ matrix.PFSENSE_VERSION }}-pkg-RESTAPI.pkg path: pfSense-${{ matrix.PFSENSE_VERSION }}-pkg-RESTAPI.pkg - name: Release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: files: pfSense-${{ matrix.PFSENSE_VERSION }}-pkg-RESTAPI.pkg @@ -116,13 +116,13 @@ jobs: /usr/local/bin/VBoxManage snapshot pfSense-${{ env.DEFAULT_PFSENSE_VERSION }}-RELEASE restore initial - name: Upload OpenAPI schema - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: openapi.json path: openapi.json - name: Upload GraphQL schema - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: schema.graphql path: schema.graphql @@ -207,10 +207,10 @@ jobs: mv ./.phpdoc/build/* ./www/php-docs/ - name: Upload artifact - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: "./www" - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/composer.lock b/composer.lock index 717323e5..322cb115 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "firebase/php-jwt", - "version": "v7.0.4", + "version": "v7.0.5", "source": { "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "e41f1bd7dbe3c5455c3f72d4338cfeb083b71931" + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e41f1bd7dbe3c5455c3f72d4338cfeb083b71931", - "reference": "e41f1bd7dbe3c5455c3f72d4338cfeb083b71931", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", "shasum": "" }, "require": { @@ -65,23 +65,23 @@ "php" ], "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v7.0.4" + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" }, - "time": "2026-03-27T21:17:19+00:00" + "time": "2026-04-01T20:38:03+00:00" }, { "name": "webonyx/graphql-php", - "version": "v15.31.3", + "version": "v15.32.3", "source": { "type": "git", "url": "https://github.com/webonyx/graphql-php.git", - "reference": "c20acbef1cb4af427ef6797512bfb2e651a85db9" + "reference": "993bf0bea17f870412ad8a90f60c41cb8d5f1145" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/c20acbef1cb4af427ef6797512bfb2e651a85db9", - "reference": "c20acbef1cb4af427ef6797512bfb2e651a85db9", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/993bf0bea17f870412ad8a90f60c41cb8d5f1145", + "reference": "993bf0bea17f870412ad8a90f60c41cb8d5f1145", "shasum": "" }, "require": { @@ -90,16 +90,16 @@ "php": "^7.4 || ^8" }, "require-dev": { - "amphp/amp": "^2.6", - "amphp/http-server": "^2.1", + "amphp/amp": "^2.6 || ^3", + "amphp/http-server": "^2.1 || ^3", "dms/phpunit-arraysubset-asserts": "dev-master", "ergebnis/composer-normalize": "^2.28", - "friendsofphp/php-cs-fixer": "3.94.2", + "friendsofphp/php-cs-fixer": "3.95.1", "mll-lab/php-cs-fixer-config": "5.13.0", "nyholm/psr7": "^1.5", "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "2.1.43", + "phpstan/phpstan": "2.1.51", "phpstan/phpstan-phpunit": "2.0.16", "phpstan/phpstan-strict-rules": "2.0.10", "phpunit/phpunit": "^9.5 || ^10.5.21 || ^11", @@ -110,9 +110,10 @@ "symfony/polyfill-php81": "^1.23", "symfony/var-exporter": "^5 || ^6 || ^7 || ^8", "thecodingmachine/safe": "^1.3 || ^2 || ^3", - "ticketswap/phpstan-error-formatter": "1.2.6" + "ticketswap/phpstan-error-formatter": "1.3.0" }, "suggest": { + "amphp/amp": "To leverage async resolving on AMPHP platform (v3 with AmpFutureAdapter, v2 with AmpPromiseAdapter)", "amphp/http-server": "To leverage async resolving with webserver on AMPHP platform", "psr/http-message": "To use standard GraphQL server", "react/promise": "To leverage async resolving on React PHP platform" @@ -135,7 +136,7 @@ ], "support": { "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v15.31.3" + "source": "https://github.com/webonyx/graphql-php/tree/v15.32.3" }, "funding": [ { @@ -147,7 +148,7 @@ "type": "open_collective" } ], - "time": "2026-03-29T18:22:41+00:00" + "time": "2026-04-24T13:49:35+00:00" } ], "packages-dev": [], diff --git a/package-lock.json b/package-lock.json index 45b3545e..fdc00204 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,11 +4,10 @@ "requires": true, "packages": { "": { - "name": "pfsense-api", "devDependencies": { "@prettier/plugin-php": "^0.25.0", - "@stoplight/spectral-cli": "^6.15.0", - "prettier": "^3.8.1" + "@stoplight/spectral-cli": "^6.15.1", + "prettier": "^3.8.3" } }, "node_modules/@asyncapi/specs": { @@ -242,9 +241,9 @@ } }, "node_modules/@stoplight/spectral-cli": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-cli/-/spectral-cli-6.15.0.tgz", - "integrity": "sha512-FVeQIuqQQnnLfa8vy+oatTKUve7uU+3SaaAfdjpX/B+uB1NcfkKRJYhKT9wMEehDRaMPL5AKIRYMCFerdEbIpw==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-cli/-/spectral-cli-6.15.1.tgz", + "integrity": "sha512-ev72bUglbaZvFSMWCP5o1Iso5NGgbLZOAuedvRxYrUMey9dVCR83i033tSFvDv6cpj86HsbEmiilh8vwrY/asQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -262,7 +261,7 @@ "chalk": "4.1.2", "fast-glob": "~3.2.12", "hpagent": "~1.2.0", - "lodash": "~4.17.21", + "lodash": "^4.18.1", "pony-cause": "^1.1.1", "stacktracey": "^2.1.8", "tslib": "^2.8.1", @@ -276,9 +275,9 @@ } }, "node_modules/@stoplight/spectral-core": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.21.0.tgz", - "integrity": "sha512-oj4e/FrDLUhBRocIW+lRMKlJ/q/rDZw61HkLbTFsdMd+f/FTkli2xHNB1YC6n1mrMKjjvy7XlUuFkC7XxtgbWw==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.22.0.tgz", + "integrity": "sha512-4hTxMDs4TFUG4/jKjaZttA65gNuV2PCKI9+51I+J4nL6ylo17DlbW+sl6byKnBuV/85HxaV33ri5fEGlp8lTSA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -291,17 +290,17 @@ "@stoplight/types": "~13.6.0", "@types/es-aggregate-error": "^1.0.2", "@types/json-schema": "^7.0.11", - "ajv": "^8.17.1", + "ajv": "^8.18.0", "ajv-errors": "~3.0.0", "ajv-formats": "~2.1.1", "es-aggregate-error": "^1.0.7", + "expr-eval-fork": "^3.0.1", "jsonpath-plus": "^10.3.0", - "lodash": "~4.17.23", + "lodash": "^4.18.1", "lodash.topath": "^4.5.2", - "minimatch": "3.1.2", + "minimatch": "^3.1.4", "nimma": "0.2.3", "pony-cause": "^1.1.1", - "simple-eval": "1.0.1", "tslib": "^2.8.1" }, "engines": { @@ -362,9 +361,9 @@ } }, "node_modules/@stoplight/spectral-functions": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-functions/-/spectral-functions-1.10.1.tgz", - "integrity": "sha512-obu8ZfoHxELOapfGsCJixKZXZcffjg+lSoNuttpmUFuDzVLT3VmH8QkPXfOGOL5Pz80BR35ClNAToDkdnYIURg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-functions/-/spectral-functions-1.10.2.tgz", + "integrity": "sha512-PIfPUgTRo8EtAnL1MIrzhHoUuojSaE8shGSMaHS3BxGyc8d079BE5+TqJa1/WLUb9YT9JQnZ0Aj4xfi8NcJOIw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -373,11 +372,11 @@ "@stoplight/spectral-core": "^1.19.4", "@stoplight/spectral-formats": "^1.8.1", "@stoplight/spectral-runtime": "^1.1.2", - "ajv": "^8.17.1", + "ajv": "^8.18.0", "ajv-draft-04": "~1.0.0", "ajv-errors": "~3.0.0", "ajv-formats": "~2.1.1", - "lodash": "~4.17.21", + "lodash": "^4.18.1", "tslib": "^2.8.1" }, "engines": { @@ -504,9 +503,9 @@ "dev": true }, "node_modules/@stoplight/spectral-rulesets": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-rulesets/-/spectral-rulesets-1.22.0.tgz", - "integrity": "sha512-l2EY2jiKKLsvnPfGy+pXC0LeGsbJzcQP5G/AojHgf+cwN//VYxW1Wvv4WKFx/CLmLxc42mJYF2juwWofjWYNIQ==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-rulesets/-/spectral-rulesets-1.22.1.tgz", + "integrity": "sha512-DaaQJioKuYkRsOuKIJfX2ek7G7f6OCU3CI3K7ABaOcTFMiHj29SJLDdb04mCjXZFXMlXHjmCl2ZpKW6heieXpw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -519,11 +518,11 @@ "@stoplight/spectral-runtime": "^1.1.2", "@stoplight/types": "^13.6.0", "@types/json-schema": "^7.0.7", - "ajv": "^8.17.1", + "ajv": "^8.18.0", "ajv-formats": "~2.1.1", "json-schema-traverse": "^1.0.0", "leven": "3.1.0", - "lodash": "~4.17.21", + "lodash": "^4.18.1", "tslib": "^2.8.1" }, "engines": { @@ -1219,6 +1218,16 @@ "node": ">=6" } }, + "node_modules/expr-eval-fork": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/expr-eval-fork/-/expr-eval-fork-3.0.3.tgz", + "integrity": "sha512-BhC+hbc5lIVjygr840n5DEkW3MQq7H9o+mc1/N7Z5uIiCFVyESLL5DIE7LNq4CYUNxy+XjA+3jRrL/h0Kt2xcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1248,10 +1257,21 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", - "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", - "dev": true + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fastq": { "version": "1.17.1", @@ -1954,9 +1974,9 @@ "license": "MIT" }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, @@ -2005,10 +2025,11 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2167,9 +2188,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -2409,18 +2430,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/simple-eval": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-eval/-/simple-eval-1.0.1.tgz", - "integrity": "sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==", - "dev": true, - "dependencies": { - "jsep": "^1.3.6" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 3fcccbb2..e0401f37 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "devDependencies": { - "prettier": "^3.8.1", + "prettier": "^3.8.3", "@prettier/plugin-php": "^0.25.0", - "@stoplight/spectral-cli": "^6.15.0" + "@stoplight/spectral-cli": "^6.15.1" } } 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'];