From e8da9434db52b452d109b3708aa2aac08e415a41 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Mon, 7 Apr 2025 20:43:56 -0600 Subject: [PATCH 01/59] ci: build and test on pfSense-2.8.0-BETA --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa49a528c..3f64af7fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,6 +59,8 @@ jobs: include: - PFSENSE_VERSION: pfSense-2.7.2-RELEASE FREEBSD_ID: freebsd14 + - PFSENSE_VERSION: pfSense-2.8.0-BETA + FREEBSD_ID: freebsd15 steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 @@ -107,6 +109,8 @@ jobs: include: - PFSENSE_VERSION: pfSense-2.7.2-RELEASE FREEBSD_ID: freebsd14 + - PFSENSE_VERSION: pfSense-2.8.0-BETA + FREEBSD_ID: freebsd15 steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 @@ -134,6 +138,8 @@ jobs: include: - PFSENSE_VERSION: pfSense-2.7.2-RELEASE FREEBSD_ID: freebsd14 + - PFSENSE_VERSION: pfSense-2.8.0-BETA + FREEBSD_ID: freebsd15 steps: - uses: actions/checkout@v4 From 0fc2898eecdf3c1c0145490c44faf798e5d2bd4b Mon Sep 17 00:00:00 2001 From: Victor Gamov Date: Mon, 12 May 2025 15:23:03 +0300 Subject: [PATCH 02/59] create/delete FreeRADIUS user (initial commit) --- .../pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc | 27 +++ .../pkg/RESTAPI/Models/FreeRADIUSUser.inc | 160 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc new file mode 100644 index 000000000..219afbf09 --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc @@ -0,0 +1,27 @@ +url = '/api/v2/services/freeradius/user'; + $this->model_name = 'FreeRADIUSUser'; + $this->request_method_options = ['GET', 'POST', 'DELETE']; + $this->many = false; + + # Construct the parent Endpoint object + parent::__construct(); + } +} + diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc new file mode 100644 index 000000000..1586f782f --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -0,0 +1,160 @@ +packages = ['pfSense-pkg-freeradius3']; + $this->package_includes = ['freeradius.inc']; + $this->config_path = 'installedpackages/freeradius/config'; + $this->many = true; + $this->always_apply = true; + + # + # Set model fields + # + $this->username = new StringField( + required: true, + unique: true, + internal_name: 'varusersusername', + ); + + $this->password = new StringField( + required: true, + conditions: ['motp_enable' => false], + allow_empty: false, + allow_null: false, + internal_name: 'varuserspassword', + sensitive: true, + ); + $this->password_encryption = new StringField( + required: false, + conditions: ['motp_enable' => false], + choices: [ 'Cleartext-Password', 'MD5-Password', 'MD5-Password-hashed', 'NT-Password-hashed' ], + default: 'Cleartext-Password', + internal_name: 'varuserspasswordencryption', + ); + + $this->motp_enable = new BooleanField( + required: false, + default: false, + indicates_true: 'on', + indicates_false: 'off', + internal_name: 'varusersmotpenable', + ); + $this->motp_authmethod = new StringField( + required: false, + conditions: ['motp_enable' => true], + choices: [ 'motp', 'googleauth' ], + default: 'googleauth', + internal_name: 'varusersauthmethod', + ); + $this->motp_secret = new StringField( + required: true, + conditions: ['motp_enable' => true], + allow_null: false, + internal_name: 'varusersmotpinitsecret', + sensitive: true, + ); + $this->motp_pin = new StringField( + required: true, + conditions: ['motp_enable' => true], + allow_null: false, + minimum_length: 4, + maximum_length: 4, + internal_name: 'varusersmotppin', + sensitive: true, + ); + $this->motp_offset = new IntegerField( + required: false, + conditions: ['motp_enable' => true], + allow_null: false, + default: 0, + internal_name: 'varusersmotpoffset', + ); + + $this->description = new StringField( + required: false, + allow_empty: true, + default: "", + validators: [ + new RegexValidator(pattern: "/^[a-zA-Z0-9 _,.;:+=()-]*$/", error_msg: 'Value contains invalid characters.'), + ], + ); + + parent::__construct($id, $parent_id, $data, ...$options); + } + + + /** + * + */ + public function _create() { + $input_errors = []; + + $user = $this->to_internal(); + freeradius_validate_users($user, $input_errors); + + if ( ! empty($input_errors) ) { + throw new ServerError( + message: "Some errors occured: input_errors={$input_errors[0]}", + response_id: 'FIELD_INVALID_CHOICE' + ); + } + + parent::_create(); + } + + + /** + * Apply the creation of this User. + */ + public function apply_create() { + freeradius_users_resync(); + } + + /** + * Apply the deletion of this User. + */ + public function apply_delete() { + freeradius_users_resync(); + } +} + From 56efb61193daeb71026d46b5c388113230fa66f2 Mon Sep 17 00:00:00 2001 From: Victor Gamov Date: Tue, 13 May 2025 22:51:29 +0300 Subject: [PATCH 03/59] 'motp_enable' is StringField with "on"/"off" choices and required now. This allow to properly set conditions for 'password' --- .../pkg/RESTAPI/Models/FreeRADIUSUser.inc | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 1586f782f..8f1bf495e 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -27,7 +27,7 @@ class FreeRADIUSUser extends Model { public StringField $username; public StringField $password; public StringField $password_encryption; - public BooleanField $motp_enable; + public StringField $motp_enable; public StringField $motp_authmethod; public StringField $motp_secret; public StringField $motp_pin; @@ -58,7 +58,7 @@ class FreeRADIUSUser extends Model { $this->password = new StringField( required: true, - conditions: ['motp_enable' => false], + conditions: ['motp_enable' => 'off'], allow_empty: false, allow_null: false, internal_name: 'varuserspassword', @@ -66,36 +66,34 @@ class FreeRADIUSUser extends Model { ); $this->password_encryption = new StringField( required: false, - conditions: ['motp_enable' => false], + conditions: ['motp_enable' => 'off'], choices: [ 'Cleartext-Password', 'MD5-Password', 'MD5-Password-hashed', 'NT-Password-hashed' ], default: 'Cleartext-Password', internal_name: 'varuserspasswordencryption', ); - $this->motp_enable = new BooleanField( - required: false, - default: false, - indicates_true: 'on', - indicates_false: 'off', + $this->motp_enable = new StringField( + required: true, + choices: [ 'on', 'off' ], internal_name: 'varusersmotpenable', ); $this->motp_authmethod = new StringField( required: false, - conditions: ['motp_enable' => true], + conditions: ['motp_enable' => 'on'], choices: [ 'motp', 'googleauth' ], default: 'googleauth', internal_name: 'varusersauthmethod', ); $this->motp_secret = new StringField( required: true, - conditions: ['motp_enable' => true], + conditions: ['motp_enable' => 'on'], allow_null: false, internal_name: 'varusersmotpinitsecret', sensitive: true, ); $this->motp_pin = new StringField( required: true, - conditions: ['motp_enable' => true], + conditions: ['motp_enable' => 'on'], allow_null: false, minimum_length: 4, maximum_length: 4, @@ -104,7 +102,7 @@ class FreeRADIUSUser extends Model { ); $this->motp_offset = new IntegerField( required: false, - conditions: ['motp_enable' => true], + conditions: ['motp_enable' => 'on'], allow_null: false, default: 0, internal_name: 'varusersmotpoffset', @@ -129,7 +127,12 @@ class FreeRADIUSUser extends Model { public function _create() { $input_errors = []; + if ( $this->motp_enable->value == 'off' ) { + $this->motp_enable->value = ''; + } + $user = $this->to_internal(); + freeradius_validate_users($user, $input_errors); if ( ! empty($input_errors) ) { From ec57b29157bf60916dcd8a219aebec62d4505e9b Mon Sep 17 00:00:00 2001 From: Victor Gamov Date: Wed, 14 May 2025 21:20:18 +0300 Subject: [PATCH 04/59] Endpoint properly named now. Plural (Many) Endpoint added. --- .../ServicesFreeRADIUSUserEndpoint.inc | 27 +++++++++++++++++++ .../ServicesFreeRADIUSUsersEndpoint.inc | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc new file mode 100644 index 000000000..9e34bdc29 --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc @@ -0,0 +1,27 @@ +url = '/api/v2/services/freeradius/user'; + $this->model_name = 'FreeRADIUSUser'; + $this->many = false; + $this->request_method_options = ['GET', 'POST', 'DELETE']; + + # Construct the parent Endpoint object + parent::__construct(); + } +} + diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc new file mode 100644 index 000000000..47c4985d0 --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc @@ -0,0 +1,27 @@ +url = '/api/v2/services/freeradius/users'; + $this->model_name = 'FreeRADIUSUser'; + $this->many = true; + $this->request_method_options = ['GET', 'DELETE']; + + # Construct the parent Endpoint object + parent::__construct(); + } +} + From a257c17e9e35c2952fae2de1f515fcbb10652d7d Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Tue, 20 May 2025 21:24:37 -0600 Subject: [PATCH 05/59] tests: adjust FirewallRule tests for pfSense 2.8.0 --- .../local/pkg/RESTAPI/Tests/APIModelsFirewallRuleTestCase.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFirewallRuleTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFirewallRuleTestCase.inc index d7eef9619..47ed22120 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFirewallRuleTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFirewallRuleTestCase.inc @@ -280,7 +280,7 @@ class APIModelsFirewallRuleTestCase extends TestCase { # Ensure the pfctl rule with this rule object's tracker is set to sloppy $pfctl_rules = file_get_contents('/tmp/rules.debug'); - $pfctl_rule = "ridentifier {$rule->tracker->value} keep state ( sloppy )"; + $pfctl_rule = "ridentifier {$rule->tracker->value} keep state (sloppy)"; $this->assert_str_contains($pfctl_rules, $pfctl_rule); # Delete the firewall rule From 9a1915421295c4588c6ca8acbf593b765a48edba Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Tue, 20 May 2025 22:38:32 -0600 Subject: [PATCH 06/59] fix: clear gateway cache before applying routing changes pfSense 2.8.0 adds a new backend GatewaysCache that is not automatically kept up-to-date. This means there can be a conflict between the current configuration and the cache when the routing subsystem is applied which can result in duplicate or unintentional routing changes. This change clears the cache before we apply routing changes to ensure its always up-to-date with the config --- .../local/pkg/RESTAPI/Dispatchers/RoutingApplyDispatcher.inc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/RoutingApplyDispatcher.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/RoutingApplyDispatcher.inc index 7672a7cb7..eb3c62b5f 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/RoutingApplyDispatcher.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/RoutingApplyDispatcher.inc @@ -15,10 +15,12 @@ class RoutingApplyDispatcher extends Dispatcher { public function process(...$arguments): void { global $g; + # Clear the gateway cache to ensure the latest status is fetched during routing changes + unset($GLOBALS['GatewaysCache']); + # Check for the pending changes file and unserialize it if (file_exists("{$g['tmp_path']}/.system_routes.apply")) { $to_apply_list = unserialize(file_get_contents("{$g['tmp_path']}/.system_routes.apply")); - # Run commands to apply these changes foreach ($to_apply_list as $to_apply) { mwexec("{$to_apply}"); From 77d0f8541de091d91f1422cb7230a1b099d70591 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Tue, 20 May 2025 22:39:18 -0600 Subject: [PATCH 07/59] style: run prettier on changed files --- composer.lock | 290 +++++++++--------- .../pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc | 1 - .../ServicesFreeRADIUSUserEndpoint.inc | 1 - .../ServicesFreeRADIUSUsersEndpoint.inc | 1 - .../pkg/RESTAPI/Models/FreeRADIUSUser.inc | 29 +- 5 files changed, 152 insertions(+), 170 deletions(-) diff --git a/composer.lock b/composer.lock index 47cf8b02d..472816ee7 100644 --- a/composer.lock +++ b/composer.lock @@ -1,156 +1,146 @@ { - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "a32ab4a8fc071e68a251a9446caf15b9", - "packages": [ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a32ab4a8fc071e68a251a9446caf15b9", + "packages": [ + { + "name": "firebase/php-jwt", + "version": "v6.11.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["BSD-3-Clause"], + "authors": [ { - "name": "firebase/php-jwt", - "version": "v6.11.1", - "source": { - "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "shasum": "" - }, - "require": { - "php": "^8.0" - }, - "require-dev": { - "guzzlehttp/guzzle": "^7.4", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "psr/cache": "^2.0||^3.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0" - }, - "suggest": { - "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" - }, - "type": "library", - "autoload": { - "psr-4": { - "Firebase\\JWT\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Neuman Vong", - "email": "neuman+pear@twilio.com", - "role": "Developer" - }, - { - "name": "Anant Narayanan", - "email": "anant@php.net", - "role": "Developer" - } - ], - "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", - "keywords": [ - "jwt", - "php" - ], - "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" - }, - "time": "2025-04-09T20:32:01+00:00" + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" }, { - "name": "webonyx/graphql-php", - "version": "v15.20.0", - "source": { - "type": "git", - "url": "https://github.com/webonyx/graphql-php.git", - "reference": "60feb7ad5023c0ef411efbdf9792d3df5812e28f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/60feb7ad5023c0ef411efbdf9792d3df5812e28f", - "reference": "60feb7ad5023c0ef411efbdf9792d3df5812e28f", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": "^7.4 || ^8" - }, - "require-dev": { - "amphp/amp": "^2.6", - "amphp/http-server": "^2.1", - "dms/phpunit-arraysubset-asserts": "dev-master", - "ergebnis/composer-normalize": "^2.28", - "friendsofphp/php-cs-fixer": "3.73.1", - "mll-lab/php-cs-fixer-config": "5.11.0", - "nyholm/psr7": "^1.5", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "2.1.8", - "phpstan/phpstan-phpunit": "2.0.4", - "phpstan/phpstan-strict-rules": "2.0.4", - "phpunit/phpunit": "^9.5 || ^10.5.21 || ^11", - "psr/http-message": "^1 || ^2", - "react/http": "^1.6", - "react/promise": "^2.0 || ^3.0", - "rector/rector": "^2.0", - "symfony/polyfill-php81": "^1.23", - "symfony/var-exporter": "^5 || ^6 || ^7", - "thecodingmachine/safe": "^1.3 || ^2 || ^3" - }, - "suggest": { - "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" - }, - "type": "library", - "autoload": { - "psr-4": { - "GraphQL\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP port of GraphQL reference implementation", - "homepage": "https://github.com/webonyx/graphql-php", - "keywords": [ - "api", - "graphql" - ], - "support": { - "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v15.20.0" - }, - "funding": [ - { - "url": "https://opencollective.com/webonyx-graphql-php", - "type": "open_collective" - } - ], - "time": "2025-03-21T08:45:04+00:00" + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": ["jwt", "php"], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + }, + "time": "2025-04-09T20:32:01+00:00" + }, + { + "name": "webonyx/graphql-php", + "version": "v15.20.0", + "source": { + "type": "git", + "url": "https://github.com/webonyx/graphql-php.git", + "reference": "60feb7ad5023c0ef411efbdf9792d3df5812e28f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/60feb7ad5023c0ef411efbdf9792d3df5812e28f", + "reference": "60feb7ad5023c0ef411efbdf9792d3df5812e28f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "^7.4 || ^8" + }, + "require-dev": { + "amphp/amp": "^2.6", + "amphp/http-server": "^2.1", + "dms/phpunit-arraysubset-asserts": "dev-master", + "ergebnis/composer-normalize": "^2.28", + "friendsofphp/php-cs-fixer": "3.73.1", + "mll-lab/php-cs-fixer-config": "5.11.0", + "nyholm/psr7": "^1.5", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "2.1.8", + "phpstan/phpstan-phpunit": "2.0.4", + "phpstan/phpstan-strict-rules": "2.0.4", + "phpunit/phpunit": "^9.5 || ^10.5.21 || ^11", + "psr/http-message": "^1 || ^2", + "react/http": "^1.6", + "react/promise": "^2.0 || ^3.0", + "rector/rector": "^2.0", + "symfony/polyfill-php81": "^1.23", + "symfony/var-exporter": "^5 || ^6 || ^7", + "thecodingmachine/safe": "^1.3 || ^2 || ^3" + }, + "suggest": { + "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" + }, + "type": "library", + "autoload": { + "psr-4": { + "GraphQL\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "description": "A PHP port of GraphQL reference implementation", + "homepage": "https://github.com/webonyx/graphql-php", + "keywords": ["api", "graphql"], + "support": { + "issues": "https://github.com/webonyx/graphql-php/issues", + "source": "https://github.com/webonyx/graphql-php/tree/v15.20.0" + }, + "funding": [ + { + "url": "https://opencollective.com/webonyx-graphql-php", + "type": "open_collective" } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.3.0" + ], + "time": "2025-03-21T08:45:04+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc index 219afbf09..385b4bb9c 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc @@ -24,4 +24,3 @@ class FreeRADIUSUser extends Endpoint { parent::__construct(); } } - diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc index 9e34bdc29..de26f3373 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc @@ -24,4 +24,3 @@ class ServicesFreeRADIUSUserEndpoint extends Endpoint { parent::__construct(); } } - diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc index 47c4985d0..02e007d62 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc @@ -24,4 +24,3 @@ class ServicesFreeRADIUSUsersEndpoint extends Endpoint { parent::__construct(); } } - diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 8f1bf495e..22131b25b 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -23,7 +23,6 @@ use RESTAPI\Validators\RegexValidator; * Defines a Model that represents OpenVPN Client config Export. */ class FreeRADIUSUser extends Model { - public StringField $username; public StringField $password; public StringField $password_encryption; @@ -50,11 +49,7 @@ class FreeRADIUSUser extends Model { # # Set model fields # - $this->username = new StringField( - required: true, - unique: true, - internal_name: 'varusersusername', - ); + $this->username = new StringField(required: true, unique: true, internal_name: 'varusersusername'); $this->password = new StringField( required: true, @@ -67,20 +62,20 @@ class FreeRADIUSUser extends Model { $this->password_encryption = new StringField( required: false, conditions: ['motp_enable' => 'off'], - choices: [ 'Cleartext-Password', 'MD5-Password', 'MD5-Password-hashed', 'NT-Password-hashed' ], + choices: ['Cleartext-Password', 'MD5-Password', 'MD5-Password-hashed', 'NT-Password-hashed'], default: 'Cleartext-Password', internal_name: 'varuserspasswordencryption', ); $this->motp_enable = new StringField( required: true, - choices: [ 'on', 'off' ], + choices: ['on', 'off'], internal_name: 'varusersmotpenable', ); $this->motp_authmethod = new StringField( required: false, conditions: ['motp_enable' => 'on'], - choices: [ 'motp', 'googleauth' ], + choices: ['motp', 'googleauth'], default: 'googleauth', internal_name: 'varusersauthmethod', ); @@ -111,23 +106,25 @@ class FreeRADIUSUser extends Model { $this->description = new StringField( required: false, allow_empty: true, - default: "", + default: '', validators: [ - new RegexValidator(pattern: "/^[a-zA-Z0-9 _,.;:+=()-]*$/", error_msg: 'Value contains invalid characters.'), + new RegexValidator( + pattern: "/^[a-zA-Z0-9 _,.;:+=()-]*$/", + error_msg: 'Value contains invalid characters.', + ), ], ); parent::__construct($id, $parent_id, $data, ...$options); } - /** * */ public function _create() { $input_errors = []; - if ( $this->motp_enable->value == 'off' ) { + if ($this->motp_enable->value == 'off') { $this->motp_enable->value = ''; } @@ -135,17 +132,16 @@ class FreeRADIUSUser extends Model { freeradius_validate_users($user, $input_errors); - if ( ! empty($input_errors) ) { + if (!empty($input_errors)) { throw new ServerError( message: "Some errors occured: input_errors={$input_errors[0]}", - response_id: 'FIELD_INVALID_CHOICE' + response_id: 'FIELD_INVALID_CHOICE', ); } parent::_create(); } - /** * Apply the creation of this User. */ @@ -160,4 +156,3 @@ class FreeRADIUSUser extends Model { freeradius_users_resync(); } } - From 5ffa6562f2ddb364f9cc9f24d8c2484822b6bfd7 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Tue, 20 May 2025 22:49:18 -0600 Subject: [PATCH 08/59] fix: password hashes are now nested under an 'item' key In pfSense 2.8.0, password hashes generated by local_user_set_password() are now nested under an 'item' key. This change ensures we look for the hash at that location. --- pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc index 8bca09199..8de7943ec 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc @@ -137,7 +137,7 @@ class User extends Model { if ($this->initial_object->password->value !== $password) { $hash = []; local_user_set_password($hash, $password); - return $hash[$this->password->internal_name]; + return $hash["item"][$this->password->internal_name]; } return $password; From 2675cd1a6540e163c72681bda2df374dc0e97442 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Tue, 20 May 2025 22:50:58 -0600 Subject: [PATCH 09/59] style: run prettier on changed files --- pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc index 8de7943ec..afbe1bcd2 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/User.inc @@ -137,7 +137,7 @@ class User extends Model { if ($this->initial_object->password->value !== $password) { $hash = []; local_user_set_password($hash, $password); - return $hash["item"][$this->password->internal_name]; + return $hash['item'][$this->password->internal_name]; } return $password; From 40f17dc3320020338d6d075b4300bdf795f4815a Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 22 May 2025 16:40:15 -0600 Subject: [PATCH 10/59] fix: use get_pf_reserved() to identify reserved names instead of globals --- .../usr/local/pkg/RESTAPI/Validators/FilterNameValidator.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Validators/FilterNameValidator.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Validators/FilterNameValidator.inc index 203b7c005..24d55a173 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Validators/FilterNameValidator.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Validators/FilterNameValidator.inc @@ -22,10 +22,10 @@ class FilterNameValidator extends Validator { * @throws ValidationError When the value is not a valid firewall filter name. */ public function validate(mixed $value, string $field_name = ''): void { - global $pf_reserved_keywords, $reserved_table_names; + $reserved_names = get_pf_reserved(); # Throw an exception if this name is reserved - if (in_array($value, array_merge($pf_reserved_keywords, $reserved_table_names))) { + if (in_array($value, $reserved_names)) { throw new ValidationError( message: "Field '$field_name' cannot be '$value' because it is a name reserved by the system.", response_id: 'FILTER_NAME_VALIDATOR_RESERVED_NAME_USED', From bf9ef30ce22041bc448b03fc26c8e57510886ab6 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 22 May 2025 17:49:15 -0600 Subject: [PATCH 11/59] fix: replace OpenVPNClientSpecificOverride remove_route field with remove_options In pfSense 2.8.0. remove_route is replaced with a more general remove_options with additional push-remove options --- .../Models/OpenVPNClientSpecificOverride.inc | 27 ++++++++++++++----- ...sOpenVPNClientSpecificOverrideTestCase.inc | 4 +-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc index 53d30c271..a87ada41a 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc @@ -30,7 +30,7 @@ class OpenVPNClientSpecificOverride extends Model { public StringField $remote_networkv6; public BooleanField $gwredir; public BooleanField $push_reset; - public BooleanField $remove_route; + public StringField $remove_options; public StringField $dns_domain; public StringField $dns_server1; public StringField $dns_server2; @@ -134,12 +134,25 @@ class OpenVPNClientSpecificOverride extends Model { indicates_false: '', help_text: 'Enables or disables preventing this client from receiving any server-defined client settings.', ); - $this->remove_route = new BooleanField( - default: false, - indicates_true: 'yes', - indicates_false: '', - help_text: 'Enables or disables preventing this client from receiving any server-defined routes ' . - 'without removing any other options.', + $this->remove_options = new StringField( + default: [], + choices: [ + 'remove_route', + 'remove_iroute', + 'remove_redirect_gateway', + 'remove_inactive', + 'remove_ping', + 'remove_ping_action', + 'remove_dnsdomain', + 'remove_dnsservers', + 'remove_blockoutsidedns', + 'remove_ntpservers', + 'remove_netbios_ntype', + 'remove_netbios_scope', + 'remove_wins', + ], + many: true, + help_text: 'Specifies the push-remove options to apply to the client', ); $this->dns_domain = new StringField( default: '', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNClientSpecificOverrideTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNClientSpecificOverrideTestCase.inc index 68d5127c4..3744a76fd 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNClientSpecificOverrideTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNClientSpecificOverrideTestCase.inc @@ -85,7 +85,7 @@ class APIModelsOpenVPNClientSpecificOverrideTestCase extends TestCase { remote_networkv6: ['1234::/64'], gwredir: true, push_reset: true, - remove_route: true, + remove_options: ['remove_route'], dns_domain: 'example.com', dns_server1: '127.0.0.1', dns_server2: '127.0.0.2', @@ -138,7 +138,7 @@ class APIModelsOpenVPNClientSpecificOverrideTestCase extends TestCase { remote_networkv6: ['4321::/64'], gwredir: false, push_reset: false, - remove_route: false, + remove_options: [], dns_domain: 'updated.example.com', dns_server1: '127.0.1.1', dns_server2: '127.0.1.2', From ea3870d5ee6e20fafb6116a91783578cd9f27ac6 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 22 May 2025 18:17:16 -0600 Subject: [PATCH 12/59] fix: do not allow push_reset and remove_options to both be set --- .../pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc | 1 + .../APIModelsOpenVPNClientSpecificOverrideTestCase.inc | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc index a87ada41a..c73f6ecfa 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc @@ -152,6 +152,7 @@ class OpenVPNClientSpecificOverride extends Model { 'remove_wins', ], many: true, + conditions: ['push_reset' => false], help_text: 'Specifies the push-remove options to apply to the client', ); $this->dns_domain = new StringField( diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNClientSpecificOverrideTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNClientSpecificOverrideTestCase.inc index 3744a76fd..9ec8d0dc8 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNClientSpecificOverrideTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNClientSpecificOverrideTestCase.inc @@ -84,7 +84,7 @@ class APIModelsOpenVPNClientSpecificOverrideTestCase extends TestCase { remote_network: ['10.1.2.0/24'], remote_networkv6: ['1234::/64'], gwredir: true, - push_reset: true, + push_reset: false, remove_options: ['remove_route'], dns_domain: 'example.com', dns_server1: '127.0.0.1', @@ -105,7 +105,7 @@ class APIModelsOpenVPNClientSpecificOverrideTestCase extends TestCase { "/var/etc/openvpn/server{$this->ovpns->vpnid->value}/csc/{$cso->common_name->value}", ); $this->assert_str_contains($cso_conf, 'disable'); // For `block` field - $this->assert_str_contains($cso_conf, 'push-reset'); + $this->assert_str_does_not_contain($cso_conf, 'push-reset'); $this->assert_str_contains($cso_conf, 'push-remove route'); $this->assert_str_contains($cso_conf, 'push "route 10.1.2.0 255.255.255.0"'); $this->assert_str_contains($cso_conf, 'push "route-ipv6 1234::/64"'); @@ -137,7 +137,7 @@ class APIModelsOpenVPNClientSpecificOverrideTestCase extends TestCase { remote_network: ['10.2.3.0/24'], remote_networkv6: ['4321::/64'], gwredir: false, - push_reset: false, + push_reset: true, remove_options: [], dns_domain: 'updated.example.com', dns_server1: '127.0.1.1', @@ -158,7 +158,7 @@ class APIModelsOpenVPNClientSpecificOverrideTestCase extends TestCase { "/var/etc/openvpn/server{$this->ovpns->vpnid->value}/csc/{$cso->common_name->value}", ); $this->assert_str_does_not_contain($cso_conf, 'disable'); // For `block` field - $this->assert_str_does_not_contain($cso_conf, 'push-reset'); + $this->assert_str_contains($cso_conf, 'push-reset'); $this->assert_str_does_not_contain($cso_conf, 'push-remove route'); $this->assert_str_contains($cso_conf, 'push "route 10.2.3.0 255.255.255.0"'); $this->assert_str_contains($cso_conf, 'push "route-ipv6 4321::/64"'); From 2102e7c652fdf6df1ec41622207eb0595610b50c Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 22 May 2025 18:18:37 -0600 Subject: [PATCH 13/59] fix: adjust expected bool strings for push_reset In pfSense 2.8.0, the OpenVPNClientSpecificOverride::push_reset field is represented as empty string when true, and null when false (default) --- .../local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc | 2 -- 1 file changed, 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc index c73f6ecfa..aeb0817bf 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNClientSpecificOverride.inc @@ -130,8 +130,6 @@ class OpenVPNClientSpecificOverride extends Model { ); $this->push_reset = new BooleanField( default: false, - indicates_true: 'yes', - indicates_false: '', help_text: 'Enables or disables preventing this client from receiving any server-defined client settings.', ); $this->remove_options = new StringField( From 496ca397387cda56f5cb20f11e33903bfafef716 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 22 May 2025 19:02:14 -0600 Subject: [PATCH 14/59] fix: use new services_dhcpd_configure() function to apply DHCP Server changes --- .../Dispatchers/DHCPServerApplyDispatcher.inc | 53 +------------------ 1 file changed, 1 insertion(+), 52 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc index 807b70237..089caa1d9 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc @@ -13,57 +13,6 @@ class DHCPServerApplyDispatcher extends Dispatcher { * Reloads the DHCP server and associated services. */ protected function _process(mixed ...$arguments): void { - $retvaldhcp = 0; - $retvaldns = 0; - - if (Model::is_config_enabled('dnsmasq') && Model::is_config_enabled('dnsmasq', 'regdhcpstatic')) { - $retvaldns |= services_dnsmasq_configure(); - if ($retvaldns == 0) { - clear_subsystem_dirty('hosts'); - clear_subsystem_dirty('dhcpd'); - } - if (Model::is_config_enabled('unbound') && Model::is_config_enabled('unbound', 'regdhcpstatic')) { - $retvaldns |= services_unbound_configure(); - if ($retvaldns == 0) { - clear_subsystem_dirty('unbound'); - clear_subsystem_dirty('hosts'); - clear_subsystem_dirty('dhcpd'); - } - } - } else { - $retvaldhcp |= services_dhcpd_configure(); - if ($retvaldhcp == 0) { - clear_subsystem_dirty('dhcpd'); - } - } - /* BIND package - Bug #3710 */ - if (!function_exists('is_package_installed')) { - require_once 'pkg-utils.inc'; - } - if ( - is_package_installed('pfSense-pkg-bind') && - Model::is_config_enabled('installedpackages/bind/config/0', 'enable_bind') - ) { - $reloadbind = false; - if (is_array(Model::get_config('installedpackages/bindzone'))) { - $bindzone = Model::get_config('installedpackages/bindzone/config'); - } else { - $bindzone = []; - } - for ($x = 0; $x < sizeof($bindzone); $x++) { - $zone = $bindzone[$x]; - if ($zone['regdhcpstatic'] == 'on') { - $reloadbind = true; - break; - } - } - if ($reloadbind === true) { - if (file_exists('/usr/local/pkg/bind.inc')) { - require_once '/usr/local/pkg/bind.inc'; - bind_sync(); - } - } - } - filter_configure(); + services_dhcpd_configure(); } } From 93e6330c55fd90473d2862ab6f4a78e8f193cecb Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 22 May 2025 19:02:50 -0600 Subject: [PATCH 15/59] fix: trim leading and extra slashes in config paths --- .../usr/local/pkg/RESTAPI/Core/Model.inc | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc index 0bec530c1..1483eb467 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc @@ -598,7 +598,10 @@ class Model { * @param $path string config path with '/' as separators */ protected static function init_config(string $path) { - # Initialize the configuration array of a specified path. + # Remove leading slashes and extra slashes + $path = trim($path, '/'); + + # Initialize the configuration array of a specified path config_init_path($path); } @@ -610,6 +613,9 @@ class Model { * path keys an empty string and $default is non-null */ public static function get_config(string $path, mixed $default = null) { + # Remove leading slashes and extra slashes + $path = trim($path, '/'); + return config_get_path($path, $default); } @@ -621,6 +627,9 @@ class Model { * @returns mixed $val or $default if the path prefix does not exist */ public static function set_config(string $path, mixed $value, mixed $default = null) { + # Remove leading slashes and extra slashes + $path = trim($path, '/'); + return config_set_path($path, $value, $default); } @@ -631,6 +640,9 @@ class Model { * @param $path string The Model config path including any Model ID */ public function merge_config(string $path) { + # Remove leading slashes and extra slashes + $path = trim($path, '/'); + # Loop through each field known to this Model foreach ($this->get_fields() as $field) { # Determine the field path @@ -662,6 +674,9 @@ class Model { * @returns array copy of the removed value or null */ public static function del_config(string $path): mixed { + # Remove leading slashes and extra slashes + $path = trim($path, '/'); + return config_del_path($path); } @@ -675,6 +690,9 @@ class Model { * non-null value, otherwise false. */ public static function is_config_enabled(string $path, string $enable_key = 'enable'): bool { + # Remove leading slashes and extra slashes + $path = trim($path, '/'); + return config_path_enabled($path, $enable_key); } From aeed77c41c21af8effe5c7326700b7f6a6eae414 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Mon, 26 May 2025 09:26:09 -0600 Subject: [PATCH 16/59] ci: deprecate builds and tests for pfSense 2.7.2 and 24.03 --- .github/workflows/build.yml | 2 -- .github/workflows/release.yml | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3f64af7fc..6ccc2e0c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -107,8 +107,6 @@ jobs: strategy: matrix: include: - - PFSENSE_VERSION: pfSense-2.7.2-RELEASE - FREEBSD_ID: freebsd14 - PFSENSE_VERSION: pfSense-2.8.0-BETA FREEBSD_ID: freebsd15 steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea95f957f..f798e3301 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,10 +26,8 @@ jobs: matrix: include: # Note: The first item in this matrix must use env.DEFAULT_PFSENSE_VERSION as the PFSENSE_VERSION! - - FREEBSD_VERSION: FreeBSD-14.0-CURRENT - PFSENSE_VERSION: "2.7.2" - FREEBSD_VERSION: FreeBSD-15.0-CURRENT - PFSENSE_VERSION: "24.03" + PFSENSE_VERSION: "2.8.0" - FREEBSD_VERSION: FreeBSD-15.0-CURRENT PFSENSE_VERSION: "24.11" From 85a805ab40a636ec49e2cdae4c4a0496c6a29abd Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Mon, 26 May 2025 09:27:20 -0600 Subject: [PATCH 17/59] tests: debug PortForward natreflection test --- .../usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc index 6dcc29b20..a35ce3a96 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc @@ -97,6 +97,7 @@ class APIModelsPortForwardTestCase extends TestCase { $rules_debug = file_get_contents('/tmp/rules.debug'); $purenat_rule = "rdr on {$this->env['PFREST_LAN_IF']} inet proto tcp from any to 127.3.2.1 port 8443 -> 127.1.2.3"; $purenat_rule_ovpn = "rdr on { {$this->env['PFREST_LAN_IF']} openvpn } inet proto tcp from any to 127.3.2.1 port 8443 -> 127.1.2.3"; + var_dump($rules_debug); $this->assert_is_true( str_contains($rules_debug, $purenat_rule) or str_contains($rules_debug, $purenat_rule_ovpn), ); From 7028bd02bd15f54db38a1c155b373a2cf88c20d2 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Mon, 26 May 2025 09:37:45 -0600 Subject: [PATCH 18/59] fix: clear dirty dhcp subsystem after applying --- .../local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc index 089caa1d9..32d70052b 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Dispatchers/DHCPServerApplyDispatcher.inc @@ -14,5 +14,6 @@ class DHCPServerApplyDispatcher extends Dispatcher { */ protected function _process(mixed ...$arguments): void { services_dhcpd_configure(); + clear_subsystem_dirty('dhcpd'); } } From 2b3411dd478b56119b68049e54f96f12fe7da6d9 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Mon, 26 May 2025 09:48:28 -0600 Subject: [PATCH 19/59] tests: remove defaultleasetime and maxleasetime from DHCPServerAddressPool kea tests These fields are currently not available for Kea, but remain for ISC DHCP --- .../Tests/APIModelsDHCPServerAddressPoolTestCase.inc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerAddressPoolTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerAddressPoolTestCase.inc index a66e1a619..a239cd1c2 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerAddressPoolTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsDHCPServerAddressPoolTestCase.inc @@ -360,8 +360,6 @@ class APIModelsDHCPServerAddressPoolTestCase extends TestCase { mac_allow: ['00:11:22:33:44:55'], mac_deny: ['55:44:33:22:11:00'], domainsearchlist: ['example.com'], - defaultleasetime: 7201, - maxleasetime: 86401, gateway: '192.168.1.2', dnsserver: ['127.0.0.53'], ntpserver: ['127.0.0.123'], @@ -374,8 +372,6 @@ class APIModelsDHCPServerAddressPoolTestCase extends TestCase { $kea_json = json_decode($kea_conf, associative: true); $kea_pool = $kea_json['Dhcp4']['subnet4'][0]['pools'][1]; $this->assert_equals($kea_pool['pool'], $pool->range_from->value . ' - ' . $pool->range_to->value); - $this->assert_equals($kea_pool['valid-lifetime'], (string) $pool->defaultleasetime->value); - $this->assert_equals($kea_pool['max-valid-lifetime'], (string) $pool->maxleasetime->value); $this->assert_equals( $this->get_kea_option_by_name('domain-name-servers', $kea_pool['option-data']), $pool->dnsserver->value[0], @@ -403,8 +399,6 @@ class APIModelsDHCPServerAddressPoolTestCase extends TestCase { mac_allow: ['55:44:33:22:11:00'], mac_deny: ['00:11:22:33:44:55'], domainsearchlist: ['new.example.com'], - defaultleasetime: 7205, - maxleasetime: 86405, gateway: '192.168.1.5', dnsserver: ['127.0.1.53'], ntpserver: ['127.0.1.123'], @@ -416,8 +410,6 @@ class APIModelsDHCPServerAddressPoolTestCase extends TestCase { $kea_json = json_decode($kea_conf, associative: true); $kea_pool = $kea_json['Dhcp4']['subnet4'][0]['pools'][1]; $this->assert_equals($kea_pool['pool'], $pool->range_from->value . ' - ' . $pool->range_to->value); - $this->assert_equals($kea_pool['valid-lifetime'], (string) $pool->defaultleasetime->value); - $this->assert_equals($kea_pool['max-valid-lifetime'], (string) $pool->maxleasetime->value); $this->assert_equals( $this->get_kea_option_by_name('domain-name-servers', $kea_pool['option-data']), $pool->dnsserver->value[0], From 6951958f16cfc820cf38b55433509cc46ef1ace1 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Mon, 26 May 2025 09:58:11 -0600 Subject: [PATCH 20/59] fix: ensure a sane default is used for ConfigHistoryRevision's 'filesize' field --- .../pkg/RESTAPI/Models/ConfigHistoryRevision.inc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc index 6cea03f38..a473d48a0 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc @@ -30,7 +30,7 @@ class ConfigHistoryRevision extends Model { help_text: 'The configuration version associated with this change.', ); $this->filesize = new IntegerField( - default: 0, + default: -1, help_text: 'The file size (in bytes) of the configuration file associated with this change.', ); @@ -42,8 +42,18 @@ class ConfigHistoryRevision extends Model { * @return array The configuration history as an array of objects. */ protected function get_config_history(): array { + # Get our current configuration history, but remove the versions key. These are not needed for the Model. $config_history = get_backups(); unset($config_history['versions']); + + # Loop through each entry in the configuration history and normalize the data + foreach ($config_history as &$entry) { + # If filesize is not a numeric, set it to the default + if (!is_numeric($entry['filesize'])) { + $entry['filesize'] = $this->filesize->default; + } + } + return $config_history; } From b9d927ecd594634b1a8d98e6cb20639227eba58f Mon Sep 17 00:00:00 2001 From: Victor Gamov Date: Mon, 26 May 2025 20:36:54 +0300 Subject: [PATCH 21/59] - wrong named FreeRADIUSUser.inc deleted due ServicesFreeRADIUSUser(s)Endpoint.inc - Endpoints comments fixed --- .../pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc | 27 ------------------- .../ServicesFreeRADIUSUserEndpoint.inc | 4 +-- .../ServicesFreeRADIUSUsersEndpoint.inc | 4 +-- 3 files changed, 4 insertions(+), 31 deletions(-) delete mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc deleted file mode 100644 index 219afbf09..000000000 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc +++ /dev/null @@ -1,27 +0,0 @@ -url = '/api/v2/services/freeradius/user'; - $this->model_name = 'FreeRADIUSUser'; - $this->request_method_options = ['GET', 'POST', 'DELETE']; - $this->many = false; - - # Construct the parent Endpoint object - parent::__construct(); - } -} - diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc index 9e34bdc29..1a3a71a1a 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc @@ -7,8 +7,8 @@ require_once 'RESTAPI/autoloader.inc'; use RESTAPI\Core\Endpoint; /** - * Defines an Endpoint for interacting with a single OpenVPNExport Model object at - * /api/v2/vpn/openvpn/clientexport. + * Defines an Endpoint for interacting with a single FreeRADIUSUser Model object at + * /api/v2/services/freeradius/user */ class ServicesFreeRADIUSUserEndpoint extends Endpoint { public function __construct() { diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc index 47c4985d0..6ae7efc87 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc @@ -7,8 +7,8 @@ require_once 'RESTAPI/autoloader.inc'; use RESTAPI\Core\Endpoint; /** - * Defines an Endpoint for interacting with a single OpenVPNExport Model object at - * /api/v2/vpn/openvpn/clientexport. + * Defines an Endpoint for interacting with a many FreeRADIUSUser Model objects at + * /api/v2/services/freeradius/users */ class ServicesFreeRADIUSUsersEndpoint extends Endpoint { public function __construct() { From 13379b395269a484deef9c242ca1a5e44a5025b4 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 20:33:29 -0600 Subject: [PATCH 22/59] fix: better normalize config paths beforing writing/reading This fixes an issue that would silently cause writes to fail if there were extra slashes in the config path --- .../usr/local/pkg/RESTAPI/Core/Model.inc | 51 ++++++++++--------- .../RESTAPI/Tests/APICoreModelTestCase.inc | 11 ++++ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc index 1483eb467..4c42b30c7 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc @@ -593,16 +593,29 @@ class Model { return $this->config_path; } + /** + * Normalizes a config path by removing extra slashes and ensuring it is not empty. + * @param string $path The config path to normalize. This is usually a string with '/' as separators. + * @return string The normalized config path + */ + public static function normalize_config_path(string $path): string { + # Remove leading and trailing slashes, and duplicate slashes, if slashes are present + if (str_contains($path, '/')) { + $path = trim($path, '/'); + $path = preg_replace('/\/+/', '/', $path); + } + + # Return the normalized config path + return $path; + } + /** * Initialize the configuration array of a specific config path * @param $path string config path with '/' as separators */ - protected static function init_config(string $path) { - # Remove leading slashes and extra slashes - $path = trim($path, '/'); - + protected static function init_config(string $path): void { # Initialize the configuration array of a specified path - config_init_path($path); + config_init_path(self::normalize_config_path($path)); } /** @@ -612,11 +625,8 @@ class Model { * @returns mixed value at path or $default if the path does not exist or if the * path keys an empty string and $default is non-null */ - public static function get_config(string $path, mixed $default = null) { - # Remove leading slashes and extra slashes - $path = trim($path, '/'); - - return config_get_path($path, $default); + public static function get_config(string $path, mixed $default = null): mixed { + return config_get_path(self::normalize_config_path($path), $default); } /** @@ -626,11 +636,8 @@ class Model { * @param $default mixed value to return if the path is not found * @returns mixed $val or $default if the path prefix does not exist */ - public static function set_config(string $path, mixed $value, mixed $default = null) { - # Remove leading slashes and extra slashes - $path = trim($path, '/'); - - return config_set_path($path, $value, $default); + public static function set_config(string $path, mixed $value, mixed $default = null): mixed { + return config_set_path(self::normalize_config_path($path), $value, $default); } /** @@ -639,9 +646,9 @@ class Model { * object that are not defined in a Field assigned to this Model will be left unchanged. * @param $path string The Model config path including any Model ID */ - public function merge_config(string $path) { + public function merge_config(string $path): void { # Remove leading slashes and extra slashes - $path = trim($path, '/'); + $path = self::normalize_config_path($path); # Loop through each field known to this Model foreach ($this->get_fields() as $field) { @@ -674,10 +681,7 @@ class Model { * @returns array copy of the removed value or null */ public static function del_config(string $path): mixed { - # Remove leading slashes and extra slashes - $path = trim($path, '/'); - - return config_del_path($path); + return config_del_path(self::normalize_config_path($path)); } /** @@ -690,10 +694,7 @@ class Model { * non-null value, otherwise false. */ public static function is_config_enabled(string $path, string $enable_key = 'enable'): bool { - # Remove leading slashes and extra slashes - $path = trim($path, '/'); - - return config_path_enabled($path, $enable_key); + return config_path_enabled(self::normalize_config_path($path), $enable_key); } /** diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc index 5820c475b..87dc911a4 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc @@ -1289,4 +1289,15 @@ class APICoreModelTestCase extends RESTAPI\Core\TestCase { } } } + + /** + * Checks that the normalize_config_path() method correctly removes extra slashes in the config path + */ + public function test_normalize_config_path(): void { + $this->assert_equals(Model::normalize_config_path("/test/path"), "test/path"); + $this->assert_equals(Model::normalize_config_path("/test//path"), "test/path"); + $this->assert_equals(Model::normalize_config_path("test/path/"), "test/path"); + $this->assert_equals(Model::normalize_config_path("/test//path/"), "test/path"); + $this->assert_equals(Model::normalize_config_path("test"), "test"); + } } From c241b4512c02f81f0bacd5638c58d700242c4d6b Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 20:35:16 -0600 Subject: [PATCH 23/59] fix: format ca config for cert_renew in CertificateAuthorityRenew This updates the model to be compatible with changes to pfSense's cert_renew() function introduced in pfSense 2.8.0. This fixes an issue that would cause a fatal configuration overwrite on pfSense 2.8.0. --- .../pkg/RESTAPI/Models/CertificateAuthorityRenew.inc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateAuthorityRenew.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateAuthorityRenew.inc index 98f0d72ce..4dcc25cef 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateAuthorityRenew.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateAuthorityRenew.inc @@ -95,7 +95,11 @@ class CertificateAuthorityRenew extends Model { public function _create(): void { # Extract details from the Certificate Authority to renew $ca_config = &lookup_ca($this->caref->value); - $this->oldserial->value = cert_get_serial($ca_config['crt']); + $this->oldserial->value = cert_get_serial($ca_config['item']['crt']); + $this->id = $ca_config['idx']; + + # The pfSense cert_renew() function expects a 'path' key with the config path + $ca_config["path"] = "ca/{$ca_config['idx']}"; # Renew the cert using pfSense's built in cert_renew function $renewed = cert_renew( @@ -114,8 +118,8 @@ class CertificateAuthorityRenew extends Model { } # Otherwise, continue with the renewal - $this->newserial->value = cert_get_serial($ca_config['crt']); - $msg = "Renewed CA {$ca_config['descr']} ({$ca_config['refid']}) - Serial {$this->oldserial->value} -> {$this->newserial->value}"; + $this->newserial->value = cert_get_serial($ca_config['item']['crt']); + $msg = "Renewed CA {$ca_config['item']['descr']} ({$ca_config['item']['refid']}) - Serial {$this->oldserial->value} -> {$this->newserial->value}"; $this->log_error($msg); $this->write_config($msg); } From 09c909783f595c13065d4cc1495f41cb796356fe Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 21:03:53 -0600 Subject: [PATCH 24/59] tests: check trust store directory in CertificateAuthority tests instead of certctl In pfSense 2.8.0, it takes quite a lot of time for the system to rehash trusted CAs via certctl. But we can tell if a certificate is trusted by checking for the CA in /usr/local/etc/ssl/certs/ instead" --- .../APIModelsCertificateAuthorityTestCase.inc | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsCertificateAuthorityTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsCertificateAuthorityTestCase.inc index f5f4434db..0e5fb401a 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsCertificateAuthorityTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsCertificateAuthorityTestCase.inc @@ -111,23 +111,17 @@ R02Pul8ulWQ8Kl3Q3pou8As7W1mMzA2DxQ== ); $ca->create(); - # Wait a few seconds for the trust store to be rebuilt - sleep(3); - - # Ensure the certificate is found in the trust store - $certctl_list = new Command('certctl list'); - $this->assert_str_contains($certctl_list->output, 'example.com'); + # Ensure the cert is in the trust store directory + $truststore_dir_ls = glob("/usr/local/etc/ssl/certs/*.crt"); + $this->assert_is_not_empty($truststore_dir_ls, message: "Trust store directory should have one trusted CA!"); # Disable `trust` $ca->trust->value = false; $ca->update(); - # Wait a few seconds for the trust store to be rebuilt - sleep(3); - # Ensure the certificate is not found in the trust store - $certctl_list = new Command('certctl list'); - $this->assert_str_does_not_contain($certctl_list->output, 'example.com'); + $truststore_dir_ls = glob("/usr/local/etc/ssl/certs/*.crt"); + $this->assert_is_empty($truststore_dir_ls, message: "Trust store directory should have no trusted CAs!"); # Delete the CA $ca->delete(); From ead43962e2ec3d807ed42a05d3c9ceea8d246f91 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 21:20:03 -0600 Subject: [PATCH 25/59] fix: format cert config for cert_renew in CertificateRenew This updates the model to be compatible with changes to pfSense's cert_renew() function introduced in pfSense 2.8.0. This fixes an issue that would cause a fatal configuration overwrite on pfSense 2.8.0. --- .../local/pkg/RESTAPI/Models/CertificateRenew.inc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRenew.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRenew.inc index dcfa69fb1..d7773094d 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRenew.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRenew.inc @@ -79,7 +79,8 @@ class CertificateRenew extends Model { */ public function validate_certref(string $certref): string { # Ensure the Certificate is capable of being renewed. - if (!is_cert_locally_renewable(lookup_cert($certref))) { + $cert = lookup_cert($certref); + if (!is_cert_locally_renewable($cert["item"])) { throw new NotAcceptableError( message: "Certificate with refid `$certref` is not capable of being renewed.", response_id: 'CERTIFICATE_RENEW_UNAVAILABLE', @@ -95,7 +96,10 @@ class CertificateRenew extends Model { public function _create(): void { # Extract details from the Certificate to renew $cert_config = &lookup_cert($this->certref->value); - $this->oldserial->value = cert_get_serial($cert_config['crt']); + $this->oldserial->value = cert_get_serial($cert_config['item']['crt']); + + # The pfSense cert_renew() function expects a 'path' key with the config path + $cert_config["path"] = "cert/{$cert_config['idx']}"; # Renew the cert using pfSense's built in cert_renew function $renewed = cert_renew( @@ -114,8 +118,8 @@ class CertificateRenew extends Model { } # Otherwise, continue with the renewal - $this->newserial->value = cert_get_serial($cert_config['crt']); - $msg = "Renewed certificate {$cert_config['descr']} ({$cert_config['refid']}) - Serial {$this->oldserial->value} -> {$this->newserial->value}"; + $this->newserial->value = cert_get_serial($cert_config['item']['crt']); + $msg = "Renewed certificate {$cert_config['item']['descr']} ({$cert_config['item']['refid']}) - Serial {$this->oldserial->value} -> {$this->newserial->value}"; $this->log_error($msg); $this->write_config($msg); } From d285618a7c6af60a6ef984a8bd6c9388cfce2759 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 21:23:57 -0600 Subject: [PATCH 26/59] fix: format crl config for crl_update in CertificateRevocationList This updates the model to be compatible with changes to pfSense's crl_update() function introduced in pfSense 2.8.0. --- .../usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc index 449f36528..3ff7b296e 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc @@ -134,7 +134,8 @@ class CertificateRevocationList extends Model { */ public function to_x509_crl(): string { # Prep the CRL config for generation - $crl_config = $this->to_internal(); + $crl_config = []; + $crl_config["item"] = $this->to_internal(); $crl_config['idx'] = $this->id; # Attempt to update/generate the CRL From ec4e12ea371992d163173b2bd1f1d7bdd5e0e07c Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 21:40:14 -0600 Subject: [PATCH 27/59] tests: adjust nat reflection pattern checks in PortForward tests --- .../Tests/APIModelsPortForwardTestCase.inc | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc index a35ce3a96..c3d7d8308 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc @@ -95,12 +95,8 @@ class APIModelsPortForwardTestCase extends TestCase { # Ensure the pure NAT reflection rule is present $rules_debug = file_get_contents('/tmp/rules.debug'); - $purenat_rule = "rdr on {$this->env['PFREST_LAN_IF']} inet proto tcp from any to 127.3.2.1 port 8443 -> 127.1.2.3"; - $purenat_rule_ovpn = "rdr on { {$this->env['PFREST_LAN_IF']} openvpn } inet proto tcp from any to 127.3.2.1 port 8443 -> 127.1.2.3"; - var_dump($rules_debug); - $this->assert_is_true( - str_contains($rules_debug, $purenat_rule) or str_contains($rules_debug, $purenat_rule_ovpn), - ); + $purenat_rule = "inet proto tcp from any to 127.3.2.1 port 8443 -> 127.1.2.3"; + $this->assert_str_contains($rules_debug, $purenat_rule); # Update the NAT reflection mode NAT + proxy (enable) $port_forward->natreflection->value = 'enable'; @@ -108,14 +104,8 @@ class APIModelsPortForwardTestCase extends TestCase { # Ensure the pure NAT reflection rule is no longer present and the NAT proxy rule is present $rules_debug = file_get_contents('/tmp/rules.debug'); - $natproxy_rule = "rdr on {$this->env['PFREST_LAN_IF']} proto tcp from any to 127.3.2.1 port 8443 tag PFREFLECT -> 127.0.0.1 port 19000"; - $natproxy_rule_ovpn = "rdr on { {$this->env['PFREST_LAN_IF']} openvpn } proto tcp from any to 127.3.2.1 port 8443 tag PFREFLECT -> 127.0.0.1 port 19000"; - $this->assert_is_false( - str_contains($rules_debug, $purenat_rule) or str_contains($rules_debug, $purenat_rule_ovpn), - ); - $this->assert_is_true( - str_contains($rules_debug, $natproxy_rule) or str_contains($rules_debug, $natproxy_rule_ovpn), - ); + $natproxy_rule = "proto tcp from any to 127.3.2.1 port 8443 tag PFREFLECT -> 127.0.0.1 port 19000"; + $this->assert_str_contains($rules_debug, $natproxy_rule); # Delete the port forward and ensure the previous reflection rule is no longer present $port_forward->delete(apply: true); From 43e8fb8ecfdefa1c2ff0f86aff703c8d223a4973 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 21:40:55 -0600 Subject: [PATCH 28/59] style: run prettier on changed files --- .../pkg/RESTAPI/Models/CertificateAuthorityRenew.inc | 2 +- .../usr/local/pkg/RESTAPI/Models/CertificateRenew.inc | 4 ++-- .../pkg/RESTAPI/Models/CertificateRevocationList.inc | 2 +- .../local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc | 10 +++++----- .../Tests/APIModelsCertificateAuthorityTestCase.inc | 8 ++++---- .../pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateAuthorityRenew.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateAuthorityRenew.inc index 4dcc25cef..bbd6bc995 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateAuthorityRenew.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateAuthorityRenew.inc @@ -99,7 +99,7 @@ class CertificateAuthorityRenew extends Model { $this->id = $ca_config['idx']; # The pfSense cert_renew() function expects a 'path' key with the config path - $ca_config["path"] = "ca/{$ca_config['idx']}"; + $ca_config['path'] = "ca/{$ca_config['idx']}"; # Renew the cert using pfSense's built in cert_renew function $renewed = cert_renew( diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRenew.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRenew.inc index d7773094d..d2d3b673e 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRenew.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRenew.inc @@ -80,7 +80,7 @@ class CertificateRenew extends Model { public function validate_certref(string $certref): string { # Ensure the Certificate is capable of being renewed. $cert = lookup_cert($certref); - if (!is_cert_locally_renewable($cert["item"])) { + if (!is_cert_locally_renewable($cert['item'])) { throw new NotAcceptableError( message: "Certificate with refid `$certref` is not capable of being renewed.", response_id: 'CERTIFICATE_RENEW_UNAVAILABLE', @@ -99,7 +99,7 @@ class CertificateRenew extends Model { $this->oldserial->value = cert_get_serial($cert_config['item']['crt']); # The pfSense cert_renew() function expects a 'path' key with the config path - $cert_config["path"] = "cert/{$cert_config['idx']}"; + $cert_config['path'] = "cert/{$cert_config['idx']}"; # Renew the cert using pfSense's built in cert_renew function $renewed = cert_renew( diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc index 3ff7b296e..031bf96a8 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/CertificateRevocationList.inc @@ -135,7 +135,7 @@ class CertificateRevocationList extends Model { public function to_x509_crl(): string { # Prep the CRL config for generation $crl_config = []; - $crl_config["item"] = $this->to_internal(); + $crl_config['item'] = $this->to_internal(); $crl_config['idx'] = $this->id; # Attempt to update/generate the CRL diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc index 87dc911a4..f05ceeb92 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc @@ -1294,10 +1294,10 @@ class APICoreModelTestCase extends RESTAPI\Core\TestCase { * Checks that the normalize_config_path() method correctly removes extra slashes in the config path */ public function test_normalize_config_path(): void { - $this->assert_equals(Model::normalize_config_path("/test/path"), "test/path"); - $this->assert_equals(Model::normalize_config_path("/test//path"), "test/path"); - $this->assert_equals(Model::normalize_config_path("test/path/"), "test/path"); - $this->assert_equals(Model::normalize_config_path("/test//path/"), "test/path"); - $this->assert_equals(Model::normalize_config_path("test"), "test"); + $this->assert_equals(Model::normalize_config_path('/test/path'), 'test/path'); + $this->assert_equals(Model::normalize_config_path('/test//path'), 'test/path'); + $this->assert_equals(Model::normalize_config_path('test/path/'), 'test/path'); + $this->assert_equals(Model::normalize_config_path('/test//path/'), 'test/path'); + $this->assert_equals(Model::normalize_config_path('test'), 'test'); } } diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsCertificateAuthorityTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsCertificateAuthorityTestCase.inc index 0e5fb401a..d07f8a770 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsCertificateAuthorityTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsCertificateAuthorityTestCase.inc @@ -112,16 +112,16 @@ R02Pul8ulWQ8Kl3Q3pou8As7W1mMzA2DxQ== $ca->create(); # Ensure the cert is in the trust store directory - $truststore_dir_ls = glob("/usr/local/etc/ssl/certs/*.crt"); - $this->assert_is_not_empty($truststore_dir_ls, message: "Trust store directory should have one trusted CA!"); + $truststore_dir_ls = glob('/usr/local/etc/ssl/certs/*.crt'); + $this->assert_is_not_empty($truststore_dir_ls, message: 'Trust store directory should have one trusted CA!'); # Disable `trust` $ca->trust->value = false; $ca->update(); # Ensure the certificate is not found in the trust store - $truststore_dir_ls = glob("/usr/local/etc/ssl/certs/*.crt"); - $this->assert_is_empty($truststore_dir_ls, message: "Trust store directory should have no trusted CAs!"); + $truststore_dir_ls = glob('/usr/local/etc/ssl/certs/*.crt'); + $this->assert_is_empty($truststore_dir_ls, message: 'Trust store directory should have no trusted CAs!'); # Delete the CA $ca->delete(); diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc index c3d7d8308..6e9862a09 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsPortForwardTestCase.inc @@ -95,7 +95,7 @@ class APIModelsPortForwardTestCase extends TestCase { # Ensure the pure NAT reflection rule is present $rules_debug = file_get_contents('/tmp/rules.debug'); - $purenat_rule = "inet proto tcp from any to 127.3.2.1 port 8443 -> 127.1.2.3"; + $purenat_rule = 'inet proto tcp from any to 127.3.2.1 port 8443 -> 127.1.2.3'; $this->assert_str_contains($rules_debug, $purenat_rule); # Update the NAT reflection mode NAT + proxy (enable) @@ -104,7 +104,7 @@ class APIModelsPortForwardTestCase extends TestCase { # Ensure the pure NAT reflection rule is no longer present and the NAT proxy rule is present $rules_debug = file_get_contents('/tmp/rules.debug'); - $natproxy_rule = "proto tcp from any to 127.3.2.1 port 8443 tag PFREFLECT -> 127.0.0.1 port 19000"; + $natproxy_rule = 'proto tcp from any to 127.3.2.1 port 8443 tag PFREFLECT -> 127.0.0.1 port 19000'; $this->assert_str_contains($rules_debug, $natproxy_rule); # Delete the port forward and ensure the previous reflection rule is no longer present From 725263488b82a4070436a928482972a676e98b7f Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 21:44:36 -0600 Subject: [PATCH 29/59] fix: ensure ConfigHistoryRevision default is >=0 OpenAPI specifications state defaults cannot be negative --- .../usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc | 2 +- .../RESTAPI/Tests/APIModelsConfigHistoryRevisionTestCase.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc index a473d48a0..f0a85d85b 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/ConfigHistoryRevision.inc @@ -30,7 +30,7 @@ class ConfigHistoryRevision extends Model { help_text: 'The configuration version associated with this change.', ); $this->filesize = new IntegerField( - default: -1, + default: 0, help_text: 'The file size (in bytes) of the configuration file associated with this change.', ); diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsConfigHistoryRevisionTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsConfigHistoryRevisionTestCase.inc index b79e036c8..a487d59c6 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsConfigHistoryRevisionTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsConfigHistoryRevisionTestCase.inc @@ -15,7 +15,7 @@ class APIModelsConfigHistoryRevisionTestCase extends TestCase { $this->assert_is_not_empty($config_history->time->value); $this->assert_is_not_empty($config_history->description->value); $this->assert_is_not_empty($config_history->version->value); - $this->assert_is_not_empty($config_history->filesize->value); + $this->assert_is_true(is_integer($config_history->filesize->value)); } /** From 52fb6f6bbd0c91dd3bd460d02e576024ecfbfc16 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 23:07:48 -0600 Subject: [PATCH 30/59] ci: don't test on pfsense 2.7.2 --- .github/workflows/build.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ccc2e0c5..93aac7db3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,8 +21,6 @@ jobs: strategy: matrix: include: - - FREEBSD_VERSION: FreeBSD-14.0-CURRENT - FREEBSD_ID: freebsd14 - FREEBSD_VERSION: FreeBSD-15.0-CURRENT FREEBSD_ID: freebsd15 @@ -57,8 +55,6 @@ jobs: strategy: matrix: include: - - PFSENSE_VERSION: pfSense-2.7.2-RELEASE - FREEBSD_ID: freebsd14 - PFSENSE_VERSION: pfSense-2.8.0-BETA FREEBSD_ID: freebsd15 steps: @@ -134,8 +130,6 @@ jobs: strategy: matrix: include: - - PFSENSE_VERSION: pfSense-2.7.2-RELEASE - FREEBSD_ID: freebsd14 - PFSENSE_VERSION: pfSense-2.8.0-BETA FREEBSD_ID: freebsd15 From bd7f5541938e374db97a10d128747b011e2a46d0 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Wed, 28 May 2025 23:32:51 -0600 Subject: [PATCH 31/59] feat: add 'advanced' field to HAProxyBackendServer #682 --- .../usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc index 52d616e40..3e70884a0 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/HAProxyBackendServer.inc @@ -23,6 +23,7 @@ class HAProxyBackendServer extends Model { public BooleanField $ssl; public BooleanField $sslserververify; public IntegerField $serverid; + public StringField $advanced; public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], ...$options) { # Set model attributes @@ -80,6 +81,12 @@ class HAProxyBackendServer extends Model { help_text: 'The unique ID for this backend server. This value is set by the system for internal use and ' . 'cannot be changed.', ); + $this->advanced = new StringField( + default: '', + allow_empty: true, + help_text: 'Allows adding custom HAProxy server settings to the server.', + ); + parent::__construct($id, $parent_id, $data, ...$options); } From 66cc8a0a518c5045e12548e89fc420a5330d8916 Mon Sep 17 00:00:00 2001 From: Victor Gamov Date: Thu, 29 May 2025 10:47:29 +0300 Subject: [PATCH 32/59] Comments fixed --- .../files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 8f1bf495e..9356b8b46 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -20,7 +20,7 @@ use RESTAPI\Validators\IPAddressValidator; use RESTAPI\Validators\RegexValidator; /** - * Defines a Model that represents OpenVPN Client config Export. + * Defines a Model that represents FreeRADIUS Users */ class FreeRADIUSUser extends Model { From 47c972e539d5a02f78919cdcf77e9666955100c0 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 29 May 2025 13:30:02 -0600 Subject: [PATCH 33/59] ci: build, deploy and test against pfSense-2.8.0-RELEASE --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 93aac7db3..c58de7b4d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ jobs: strategy: matrix: include: - - PFSENSE_VERSION: pfSense-2.8.0-BETA + - PFSENSE_VERSION: pfSense-2.8.0-RELEASE FREEBSD_ID: freebsd15 steps: - uses: actions/checkout@v4 @@ -103,7 +103,7 @@ jobs: strategy: matrix: include: - - PFSENSE_VERSION: pfSense-2.8.0-BETA + - PFSENSE_VERSION: pfSense-2.8.0-RELEASE FREEBSD_ID: freebsd15 steps: - uses: actions/checkout@v4 @@ -130,7 +130,7 @@ jobs: strategy: matrix: include: - - PFSENSE_VERSION: pfSense-2.8.0-BETA + - PFSENSE_VERSION: pfSense-2.8.0-RELEASE FREEBSD_ID: freebsd15 steps: From 5bd1bf6ff50f0636597aa3185bf0cf6e46b32565 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 29 May 2025 22:05:44 -0600 Subject: [PATCH 34/59] fix: update ui button css classes #620 --- .../files/usr/local/pkg/RESTAPI/Core/Form.inc | 6 +++--- .../local/pkg/RESTAPI/Forms/SystemRESTAPISettingsForm.inc | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc index 769d1d195..d404b7f77 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc @@ -285,7 +285,7 @@ class Form { $tb .= ''; if ($this->editable) { $tb .= - "" . + "' class='btn btn-success'>" . gettext('Add') . ''; $tb .= ''; diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Forms/SystemRESTAPISettingsForm.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Forms/SystemRESTAPISettingsForm.inc index 65239235c..418d1b721 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Forms/SystemRESTAPISettingsForm.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Forms/SystemRESTAPISettingsForm.inc @@ -52,14 +52,14 @@ class SystemRESTAPISettingsForm extends Form { public array $buttons = [ 'rotate_server_key' => [ 'title' => 'Rotate Server Key', - 'icon' => 'fa-repeat', - 'classes' => ['btn-success', 'btn-sm'], + 'icon' => 'fa-solid fa-turn-up', + 'classes' => ['btn-success'], 'on_click' => "return confirm(\"Rotating the server key will void any existing JWTs. Proceed?\");", 'callable' => 'rotate_server_key', ], 'report_an_issue' => [ 'title' => 'Report an Issue', - 'icon' => 'fa-question-circle', + 'icon' => 'fa-solid fa-question-circle', 'link' => 'https://github.com/jaredhendrickson13/pfsense-api/issues/new/choose', 'classes' => ['btn-info'], ], From 09795d131e139caffd8ce8655e7ebbf9c69938a3 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 29 May 2025 22:59:00 -0600 Subject: [PATCH 35/59] ci: make 2.8.0 the default pfsense version in releases --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f798e3301..71b6ac905 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ concurrency: build env: SWAGGER_UI_VERSION: "5.17.10" PYTHON_VERSION: "3.10" - DEFAULT_PFSENSE_VERSION: "2.7.2" + DEFAULT_PFSENSE_VERSION: "2.8.0" # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: From 2e8d3788aed0718ca8fd6eec7ef2e94ad2833e5c Mon Sep 17 00:00:00 2001 From: Victor Gamov Date: Mon, 26 May 2025 20:36:54 +0300 Subject: [PATCH 36/59] - wrong named FreeRADIUSUser.inc deleted due ServicesFreeRADIUSUser(s)Endpoint.inc - Endpoints comments fixed --- .../pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc | 26 ------------------- .../ServicesFreeRADIUSUserEndpoint.inc | 4 +-- .../ServicesFreeRADIUSUsersEndpoint.inc | 4 +-- 3 files changed, 4 insertions(+), 30 deletions(-) delete mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc deleted file mode 100644 index 385b4bb9c..000000000 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/FreeRADIUSUser.inc +++ /dev/null @@ -1,26 +0,0 @@ -url = '/api/v2/services/freeradius/user'; - $this->model_name = 'FreeRADIUSUser'; - $this->request_method_options = ['GET', 'POST', 'DELETE']; - $this->many = false; - - # Construct the parent Endpoint object - parent::__construct(); - } -} diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc index de26f3373..9d660015c 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc @@ -7,8 +7,8 @@ require_once 'RESTAPI/autoloader.inc'; use RESTAPI\Core\Endpoint; /** - * Defines an Endpoint for interacting with a single OpenVPNExport Model object at - * /api/v2/vpn/openvpn/clientexport. + * Defines an Endpoint for interacting with a single FreeRADIUSUser Model object at + * /api/v2/services/freeradius/user */ class ServicesFreeRADIUSUserEndpoint extends Endpoint { public function __construct() { diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc index 02e007d62..823e5896f 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc @@ -7,8 +7,8 @@ require_once 'RESTAPI/autoloader.inc'; use RESTAPI\Core\Endpoint; /** - * Defines an Endpoint for interacting with a single OpenVPNExport Model object at - * /api/v2/vpn/openvpn/clientexport. + * Defines an Endpoint for interacting with a many FreeRADIUSUser Model objects at + * /api/v2/services/freeradius/users */ class ServicesFreeRADIUSUsersEndpoint extends Endpoint { public function __construct() { From 2916d75f62a9bdbe95e4db2482e489c1f5ad5283 Mon Sep 17 00:00:00 2001 From: Victor Gamov Date: Thu, 29 May 2025 10:47:29 +0300 Subject: [PATCH 37/59] Comments fixed --- .../files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 22131b25b..565fcfb94 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -20,7 +20,7 @@ use RESTAPI\Validators\IPAddressValidator; use RESTAPI\Validators\RegexValidator; /** - * Defines a Model that represents OpenVPN Client config Export. + * Defines a Model that represents FreeRADIUS Users */ class FreeRADIUSUser extends Model { public StringField $username; From 434882b1043ecb2964a09a8a651efc77930e1564 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 16:11:43 -0600 Subject: [PATCH 38/59] docs: update supported versions --- README.md | 4 ++-- docs/INSTALL_AND_CONFIG.md | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bcc675b9c..7490d35a0 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ commands are included below for quick reference. Install on pfSense CE: ```bash -pkg-static add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-2.7.2-pkg-RESTAPI.pkg +pkg-static add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-2.8.0-pkg-RESTAPI.pkg ``` Install on pfSense Plus: ```bash -pkg-static -C /dev/null add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-24.03-pkg-RESTAPI.pkg +pkg-static -C /dev/null add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-24.11-pkg-RESTAPI.pkg ``` > [!WARNING] diff --git a/docs/INSTALL_AND_CONFIG.md b/docs/INSTALL_AND_CONFIG.md index 0cd0a40ae..c730e1d14 100644 --- a/docs/INSTALL_AND_CONFIG.md +++ b/docs/INSTALL_AND_CONFIG.md @@ -10,19 +10,18 @@ run pfSense. It's recommended to follow Netgate's [minimum hardware requirements !!! Warning - The package is currently not compatible with 32-bit builds of pfSense. It is recommended to use the [legacy v1 package](https://github.com/jaredhendrickson13/pfsense-api/tree/legacy) for 32-bit systems. - While the package should behave identically on 64-bit architectures other than amd64, automated testing only covers amd64 - builds of pfSense. Support on other architectures is not guaranteed. + builds of pfSense CE. Support on other architectures is not guaranteed. ### Supported pfSense versions -- pfSense CE 2.7.2 -- pfSense Plus 24.03 +- pfSense CE 2.8.0 - pfSense Plus 24.11 !!! Warning Installation of the package on unsupported versions of pfSense may result in unexpected behavior and/or system instability. !!! Tip - Don't see your version of pfSense? Older versions of pfSense may be supported by older versions of this package. + Don't see your version of pfSense listed? Older versions of pfSense may be supported by older versions of this package. Check the [releases page](https://github.com/jaredhendrickson13/pfsense-api/releases). ## Installing the package @@ -33,13 +32,13 @@ The pfSense REST API package is built just like any other pfSense package and ca **Install on pfSense CE** ```bash -pkg-static add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-2.7.2-pkg-RESTAPI.pkg +pkg-static add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-2.8.0-pkg-RESTAPI.pkg ``` **Install on pfSense Plus** ```bash -pkg-static -C /dev/null add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-24.03-pkg-RESTAPI.pkg +pkg-static -C /dev/null add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-24.11-pkg-RESTAPI.pkg ``` !!! Important From 0c2b26899e2a9adeb1e4362d406c2742babb0e98 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:14:33 -0600 Subject: [PATCH 39/59] feat: add OpenVPNLog Model, Endpoint and tests --- .../Endpoints/StatusLogsOpenVPNEndpoint.inc | 23 +++++++++++ .../local/pkg/RESTAPI/Models/OpenVPNLog.inc | 39 +++++++++++++++++++ .../Tests/APIModelsOpenVPNLogTestCase.inc | 20 ++++++++++ 3 files changed, 82 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsOpenVPNEndpoint.inc create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNLog.inc create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNLogTestCase.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsOpenVPNEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsOpenVPNEndpoint.inc new file mode 100644 index 000000000..038fb7c3b --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsOpenVPNEndpoint.inc @@ -0,0 +1,23 @@ +url = '/api/v2/status/logs/openvpn'; + $this->model_name = 'OpenVPNLog'; + $this->many = true; + $this->request_method_options = ['GET']; + + # Construct the parent Endpoint object + parent::__construct(); + } +} diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNLog.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNLog.inc new file mode 100644 index 000000000..e98d3599b --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/OpenVPNLog.inc @@ -0,0 +1,39 @@ +internal_callable = 'get_openvpn_log'; + $this->many = true; + + $this->text = new StringField(default: '', help_text: 'The raw text of the openvpn log entry.'); + + parent::__construct($id, $parent_id, $data, ...$options); + } + + /** + * Obtains the openvpn log as an array. This method is the internal callable for this Model. + * @return array The openvpn log as an array of objects. + */ + protected function get_openvpn_log(): array { + return $this->read_log($this->log_file); + } +} diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNLogTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNLogTestCase.inc new file mode 100644 index 000000000..3e713ee30 --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsOpenVPNLogTestCase.inc @@ -0,0 +1,20 @@ +> /var/log/openvpn.log'); + $openvpn_logs = OpenVPNLog::read_all(limit: 5); + foreach ($openvpn_logs->model_objects as $openvpn_log) { + $this->assert_is_not_empty($openvpn_log->text->value); + } + } +} From d37af63a06555de27d60b7e83c1cc234c4d4965a Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:20:13 -0600 Subject: [PATCH 40/59] feat: add AuthLog Model, Endpoint and tests --- .../Endpoints/StatusLogsAuthEndpoint.inc | 23 +++++++++++ .../usr/local/pkg/RESTAPI/Models/AuthLog.inc | 39 +++++++++++++++++++ .../Tests/APIModelsAuthLogTestCase.inc | 18 +++++++++ 3 files changed, 80 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsAuthEndpoint.inc create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/AuthLog.inc create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsAuthLogTestCase.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsAuthEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsAuthEndpoint.inc new file mode 100644 index 000000000..038fb7c3b --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsAuthEndpoint.inc @@ -0,0 +1,23 @@ +url = '/api/v2/status/logs/openvpn'; + $this->model_name = 'OpenVPNLog'; + $this->many = true; + $this->request_method_options = ['GET']; + + # Construct the parent Endpoint object + parent::__construct(); + } +} diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/AuthLog.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/AuthLog.inc new file mode 100644 index 000000000..2aeece43a --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/AuthLog.inc @@ -0,0 +1,39 @@ +internal_callable = 'get_firewall_log'; + $this->many = true; + + $this->text = new StringField(default: '', help_text: 'The raw text of the firewall log entry.'); + + parent::__construct($id, $parent_id, $data, ...$options); + } + + /** + * Obtains the firewall log as an array. This method is the internal callable for this Model. + * @return array The firewall log as an array of objects. + */ + protected function get_firewall_log(): array { + return $this->read_log($this->log_file); + } +} diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsAuthLogTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsAuthLogTestCase.inc new file mode 100644 index 000000000..5fc8e44b2 --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsAuthLogTestCase.inc @@ -0,0 +1,18 @@ +model_objects as $firewall_log) { + $this->assert_is_not_empty($firewall_log->text->value); + } + } +} From 8e916dfaa54c16d3d9f5c0e78e5bdc71ee1c7e20 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:25:53 -0600 Subject: [PATCH 41/59] style: make format consistent in FreeRADIUSUser model --- .../Endpoints/StatusLogsAuthEndpoint.inc | 8 ++--- .../usr/local/pkg/RESTAPI/Models/AuthLog.inc | 18 +++++----- .../pkg/RESTAPI/Models/FreeRADIUSUser.inc | 36 +++++++------------ .../Tests/APIModelsAuthLogTestCase.inc | 12 +++---- 4 files changed, 32 insertions(+), 42 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsAuthEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsAuthEndpoint.inc index 038fb7c3b..de3d2560f 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsAuthEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/StatusLogsAuthEndpoint.inc @@ -7,13 +7,13 @@ require_once 'RESTAPI/autoloader.inc'; use RESTAPI\Core\Endpoint; /** - * Defines an Endpoint for interacting with many OpenVPNLog Models at /api/v2/status/logs/openvpn. + * Defines an Endpoint for interacting with many AuthLog Models at /api/v2/status/logs/auth. */ -class StatusLogsOpenVPNEndpoint extends Endpoint { +class StatusLogsAuthEndpoint extends Endpoint { public function __construct() { # Set Endpoint attributes - $this->url = '/api/v2/status/logs/openvpn'; - $this->model_name = 'OpenVPNLog'; + $this->url = '/api/v2/status/logs/auth'; + $this->model_name = 'AuthLog'; $this->many = true; $this->request_method_options = ['GET']; diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/AuthLog.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/AuthLog.inc index 2aeece43a..b178baa4a 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/AuthLog.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/AuthLog.inc @@ -7,33 +7,33 @@ use RESTAPI\Fields\StringField; use RESTAPI\ModelTraits\LogFileModelTraits; /** - * Defines a Model for interacting with the firewall log at /var/log/filter.log. + * Defines a Model for interacting with the auth log at /var/log/auth.log. */ -class FirewallLog extends Model { +class AuthLog extends Model { use LogFileModelTraits; /** - * @var string $log_file The path to the base firewall log file this Model will read. Any log files that have been rotated + * @var string $log_file The path to the base auth log file this Model will read. Any log files that have been rotated * will be read as well. */ - public string $log_file = '/var/log/filter.log'; + public string $log_file = '/var/log/auth.log'; public StringField $text; public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], ...$options) { # Set model attributes - $this->internal_callable = 'get_firewall_log'; + $this->internal_callable = 'get_auth_log'; $this->many = true; - $this->text = new StringField(default: '', help_text: 'The raw text of the firewall log entry.'); + $this->text = new StringField(default: '', help_text: 'The raw text of the auth log entry.'); parent::__construct($id, $parent_id, $data, ...$options); } /** - * Obtains the firewall log as an array. This method is the internal callable for this Model. - * @return array The firewall log as an array of objects. + * Obtains the auth log as an array. This method is the internal callable for this Model. + * @return array The auth log as an array of objects. */ - protected function get_firewall_log(): array { + protected function get_auth_log(): array { return $this->read_log($this->log_file); } } diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 565fcfb94..fe7ebb312 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -33,40 +33,31 @@ class FreeRADIUSUser extends Model { public IntegerField $motp_offset; public StringField $description; - /** - * - */ public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], mixed ...$options) { - # # Set model attributes - # $this->packages = ['pfSense-pkg-freeradius3']; $this->package_includes = ['freeradius.inc']; $this->config_path = 'installedpackages/freeradius/config'; $this->many = true; $this->always_apply = true; - # # Set model fields - # $this->username = new StringField(required: true, unique: true, internal_name: 'varusersusername'); - $this->password = new StringField( required: true, - conditions: ['motp_enable' => 'off'], allow_empty: false, allow_null: false, - internal_name: 'varuserspassword', sensitive: true, + internal_name: 'varuserspassword', + conditions: ['motp_enable' => 'off'], ); $this->password_encryption = new StringField( required: false, - conditions: ['motp_enable' => 'off'], - choices: ['Cleartext-Password', 'MD5-Password', 'MD5-Password-hashed', 'NT-Password-hashed'], default: 'Cleartext-Password', + choices: ['Cleartext-Password', 'MD5-Password', 'MD5-Password-hashed', 'NT-Password-hashed'], internal_name: 'varuserspasswordencryption', + conditions: ['motp_enable' => 'off'], ); - $this->motp_enable = new StringField( required: true, choices: ['on', 'off'], @@ -74,39 +65,38 @@ class FreeRADIUSUser extends Model { ); $this->motp_authmethod = new StringField( required: false, - conditions: ['motp_enable' => 'on'], - choices: ['motp', 'googleauth'], default: 'googleauth', + choices: ['motp', 'googleauth'], internal_name: 'varusersauthmethod', + conditions: ['motp_enable' => 'on'], ); $this->motp_secret = new StringField( required: true, - conditions: ['motp_enable' => 'on'], allow_null: false, - internal_name: 'varusersmotpinitsecret', sensitive: true, + internal_name: 'varusersmotpinitsecret', + conditions: ['motp_enable' => 'on'], ); $this->motp_pin = new StringField( required: true, - conditions: ['motp_enable' => 'on'], allow_null: false, + sensitive: true, minimum_length: 4, maximum_length: 4, internal_name: 'varusersmotppin', - sensitive: true, + conditions: ['motp_enable' => 'on'], ); $this->motp_offset = new IntegerField( required: false, - conditions: ['motp_enable' => 'on'], - allow_null: false, default: 0, + allow_null: false, internal_name: 'varusersmotpoffset', + conditions: ['motp_enable' => 'on'], ); - $this->description = new StringField( required: false, - allow_empty: true, default: '', + allow_empty: true, validators: [ new RegexValidator( pattern: "/^[a-zA-Z0-9 _,.;:+=()-]*$/", diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsAuthLogTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsAuthLogTestCase.inc index 5fc8e44b2..241933263 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsAuthLogTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsAuthLogTestCase.inc @@ -3,16 +3,16 @@ namespace RESTAPI\Tests; use RESTAPI\Core\TestCase; -use RESTAPI\Models\FirewallLog; +use RESTAPI\Models\AuthLog; -class APIModelsFirewallLogTestCase extends TestCase { +class APIModelsAuthLogTestCase extends TestCase { /** - * Checks if we can correctly read the firewall logs. + * Checks if we can correctly read the auth logs. */ public function test_read(): void { - $firewall_logs = FirewallLog::read_all(limit: 5); - foreach ($firewall_logs->model_objects as $firewall_log) { - $this->assert_is_not_empty($firewall_log->text->value); + $auth_logs = AuthLog::read_all(limit: 5); + foreach ($auth_logs->model_objects as $auth_log) { + $this->assert_is_not_empty($auth_log->text->value); } } } From dafa39011d708abec01327f30dcc527eb389175c Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:34:35 -0600 Subject: [PATCH 42/59] chore: add help texts to FreeRADIUSUser fields --- .../local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index fe7ebb312..464a964c0 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -42,7 +42,12 @@ class FreeRADIUSUser extends Model { $this->always_apply = true; # Set model fields - $this->username = new StringField(required: true, unique: true, internal_name: 'varusersusername'); + $this->username = new StringField( + required: true, + unique: true, + internal_name: 'varusersusername', + help_text: 'The username for this user.', + ); $this->password = new StringField( required: true, allow_empty: false, @@ -50,6 +55,7 @@ class FreeRADIUSUser extends Model { sensitive: true, internal_name: 'varuserspassword', conditions: ['motp_enable' => 'off'], + help_text: 'The password for this username.', ); $this->password_encryption = new StringField( required: false, @@ -57,11 +63,13 @@ class FreeRADIUSUser extends Model { choices: ['Cleartext-Password', 'MD5-Password', 'MD5-Password-hashed', 'NT-Password-hashed'], internal_name: 'varuserspasswordencryption', conditions: ['motp_enable' => 'off'], + help_text: 'The encryption method for the password.', ); $this->motp_enable = new StringField( required: true, choices: ['on', 'off'], internal_name: 'varusersmotpenable', + help_text: 'Enable or disable the use of Mobile One-Time Password (MOTP) for this user.', ); $this->motp_authmethod = new StringField( required: false, @@ -69,6 +77,7 @@ class FreeRADIUSUser extends Model { choices: ['motp', 'googleauth'], internal_name: 'varusersauthmethod', conditions: ['motp_enable' => 'on'], + help_text: 'The authentication method for the Mobile One-Time Password (MOTP).', ); $this->motp_secret = new StringField( required: true, @@ -76,6 +85,7 @@ class FreeRADIUSUser extends Model { sensitive: true, internal_name: 'varusersmotpinitsecret', conditions: ['motp_enable' => 'on'], + help_text: 'The secret for the Mobile One-Time Password (MOTP).', ); $this->motp_pin = new StringField( required: true, @@ -85,6 +95,7 @@ class FreeRADIUSUser extends Model { maximum_length: 4, internal_name: 'varusersmotppin', conditions: ['motp_enable' => 'on'], + help_text: 'The PIN for the Mobile One-Time Password (MOTP). It must be exactly 4 digits.', ); $this->motp_offset = new IntegerField( required: false, @@ -92,6 +103,7 @@ class FreeRADIUSUser extends Model { allow_null: false, internal_name: 'varusersmotpoffset', conditions: ['motp_enable' => 'on'], + help_text: 'The timezone offset for this user.', ); $this->description = new StringField( required: false, @@ -103,6 +115,7 @@ class FreeRADIUSUser extends Model { error_msg: 'Value contains invalid characters.', ), ], + help_text: 'A description for this user.', ); parent::__construct($id, $parent_id, $data, ...$options); From 345e4841ec6f3e275d1dbbabfbd8ba41a3e0ec57 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:36:12 -0600 Subject: [PATCH 43/59] chore: make FreeRADIUSUser 'motp_enable' a BooleanField --- .../files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 464a964c0..153d86b6f 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -26,7 +26,7 @@ class FreeRADIUSUser extends Model { public StringField $username; public StringField $password; public StringField $password_encryption; - public StringField $motp_enable; + public BooleanField $motp_enable; public StringField $motp_authmethod; public StringField $motp_secret; public StringField $motp_pin; @@ -65,9 +65,10 @@ class FreeRADIUSUser extends Model { conditions: ['motp_enable' => 'off'], help_text: 'The encryption method for the password.', ); - $this->motp_enable = new StringField( + $this->motp_enable = new BooleanField( required: true, - choices: ['on', 'off'], + indicates_true: 'on', + indicates_false: 'off', internal_name: 'varusersmotpenable', help_text: 'Enable or disable the use of Mobile One-Time Password (MOTP) for this user.', ); From 7a5c0a01561d788a31d7fd770261601bf9ed6c05 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:45:28 -0600 Subject: [PATCH 44/59] chore: replace FreeRADIUS _create override with validate_extra --- .../pkg/RESTAPI/Models/FreeRADIUSUser.inc | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 153d86b6f..5e0a6852e 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -125,25 +125,21 @@ class FreeRADIUSUser extends Model { /** * */ - public function _create() { + public function validate_extra(): void { + # Run service level validations $input_errors = []; - - if ($this->motp_enable->value == 'off') { - $this->motp_enable->value = ''; - } - $user = $this->to_internal(); - freeradius_validate_users($user, $input_errors); + # If there were validation errors that were not caught by the model fields, throw a ValidationError. + # Ideally the Model should catch all validation errors itself so prompt the user to report this error if (!empty($input_errors)) { - throw new ServerError( - message: "Some errors occured: input_errors={$input_errors[0]}", - response_id: 'FIELD_INVALID_CHOICE', + throw new ValidationError( + message: "An unexpected validation error has occurred: $input_errors[0]. Please report this issue at " . + 'https://github.com/jaredhendrickson13/pfsense-api/issues/new', + response_id: 'FREERADIUS_USER_UNEXPECTED_VALIDATION_ERROR', ); } - - parent::_create(); } /** From 0b8b74af3097dd118a9441debf513947fcc7e1dd Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:46:09 -0600 Subject: [PATCH 45/59] chore: optimize imports --- .../files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 5e0a6852e..30a12fed1 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -5,18 +5,10 @@ namespace RESTAPI\Models; require_once 'RESTAPI/autoloader.inc'; use RESTAPI\Core\Model; -use RESTAPI\Fields\Base64Field; use RESTAPI\Fields\BooleanField; -use RESTAPI\Fields\ForeignModelField; use RESTAPI\Fields\IntegerField; -use RESTAPI\Fields\PortField; -use RESTAPI\Fields\ObjectField; use RESTAPI\Fields\StringField; -use RESTAPI\Responses\ConflictError; use RESTAPI\Responses\ValidationError; -use RESTAPI\Responses\ServerError; -use RESTAPI\Validators\HostnameValidator; -use RESTAPI\Validators\IPAddressValidator; use RESTAPI\Validators\RegexValidator; /** From c3fe50a372e0aea021c89a8c3bb95bf5c1a24c92 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:46:57 -0600 Subject: [PATCH 46/59] chore: add return types for FreeRADIUSUser apply_create and apply_delete --- .../files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 30a12fed1..b038bf8d8 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -137,14 +137,14 @@ class FreeRADIUSUser extends Model { /** * Apply the creation of this User. */ - public function apply_create() { + public function apply_create(): void { freeradius_users_resync(); } /** * Apply the deletion of this User. */ - public function apply_delete() { + public function apply_delete(): void { freeradius_users_resync(); } } From 76cf27a9c4205750dd9a7dcda7cb02ae1afba0eb Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:48:31 -0600 Subject: [PATCH 47/59] chore: change conditions to expect motp_enable as boolean --- .../usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index b038bf8d8..7400a1212 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -46,7 +46,7 @@ class FreeRADIUSUser extends Model { allow_null: false, sensitive: true, internal_name: 'varuserspassword', - conditions: ['motp_enable' => 'off'], + conditions: ['motp_enable' => false], help_text: 'The password for this username.', ); $this->password_encryption = new StringField( @@ -54,7 +54,7 @@ class FreeRADIUSUser extends Model { default: 'Cleartext-Password', choices: ['Cleartext-Password', 'MD5-Password', 'MD5-Password-hashed', 'NT-Password-hashed'], internal_name: 'varuserspasswordencryption', - conditions: ['motp_enable' => 'off'], + conditions: ['motp_enable' => false], help_text: 'The encryption method for the password.', ); $this->motp_enable = new BooleanField( @@ -69,7 +69,7 @@ class FreeRADIUSUser extends Model { default: 'googleauth', choices: ['motp', 'googleauth'], internal_name: 'varusersauthmethod', - conditions: ['motp_enable' => 'on'], + conditions: ['motp_enable' => true], help_text: 'The authentication method for the Mobile One-Time Password (MOTP).', ); $this->motp_secret = new StringField( @@ -77,7 +77,7 @@ class FreeRADIUSUser extends Model { allow_null: false, sensitive: true, internal_name: 'varusersmotpinitsecret', - conditions: ['motp_enable' => 'on'], + conditions: ['motp_enable' => true], help_text: 'The secret for the Mobile One-Time Password (MOTP).', ); $this->motp_pin = new StringField( @@ -87,7 +87,7 @@ class FreeRADIUSUser extends Model { minimum_length: 4, maximum_length: 4, internal_name: 'varusersmotppin', - conditions: ['motp_enable' => 'on'], + conditions: ['motp_enable' => true], help_text: 'The PIN for the Mobile One-Time Password (MOTP). It must be exactly 4 digits.', ); $this->motp_offset = new IntegerField( @@ -95,7 +95,7 @@ class FreeRADIUSUser extends Model { default: 0, allow_null: false, internal_name: 'varusersmotpoffset', - conditions: ['motp_enable' => 'on'], + conditions: ['motp_enable' => true], help_text: 'The timezone offset for this user.', ); $this->description = new StringField( From 226dd9f6431721a8108a2364e5b3f93452585efe Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 19:49:14 -0600 Subject: [PATCH 48/59] chore: add missing doc string to FreeRADIUSUser validate_extra --- .../files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 7400a1212..53b5529e0 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -115,7 +115,7 @@ class FreeRADIUSUser extends Model { } /** - * + * Perform additional validation on the Model's fields and data. */ public function validate_extra(): void { # Run service level validations From 35c42a620062537c82e046174024f8d84da3bd49 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 20:39:27 -0600 Subject: [PATCH 49/59] feat: allow FreeRADIUSUsers to be updated and replaced --- .../Endpoints/ServicesFreeRADIUSUserEndpoint.inc | 2 +- .../Endpoints/ServicesFreeRADIUSUsersEndpoint.inc | 2 +- .../usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc | 13 +++---------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc index 9d660015c..c7d9edfcf 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUserEndpoint.inc @@ -18,7 +18,7 @@ class ServicesFreeRADIUSUserEndpoint extends Endpoint { $this->url = '/api/v2/services/freeradius/user'; $this->model_name = 'FreeRADIUSUser'; $this->many = false; - $this->request_method_options = ['GET', 'POST', 'DELETE']; + $this->request_method_options = ['GET', 'POST', 'PATCH', 'DELETE']; # Construct the parent Endpoint object parent::__construct(); diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc index 823e5896f..cf3fcc81c 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Endpoints/ServicesFreeRADIUSUsersEndpoint.inc @@ -18,7 +18,7 @@ class ServicesFreeRADIUSUsersEndpoint extends Endpoint { $this->url = '/api/v2/services/freeradius/users'; $this->model_name = 'FreeRADIUSUser'; $this->many = true; - $this->request_method_options = ['GET', 'DELETE']; + $this->request_method_options = ['GET', 'PUT', 'DELETE']; # Construct the parent Endpoint object parent::__construct(); diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc index 53b5529e0..d5c2355e3 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/FreeRADIUSUser.inc @@ -60,7 +60,7 @@ class FreeRADIUSUser extends Model { $this->motp_enable = new BooleanField( required: true, indicates_true: 'on', - indicates_false: 'off', + indicates_false: '', internal_name: 'varusersmotpenable', help_text: 'Enable or disable the use of Mobile One-Time Password (MOTP) for this user.', ); @@ -135,16 +135,9 @@ class FreeRADIUSUser extends Model { } /** - * Apply the creation of this User. + * Apply the changes made to this Model to the FreeRADIUS configuration. */ - public function apply_create(): void { - freeradius_users_resync(); - } - - /** - * Apply the deletion of this User. - */ - public function apply_delete(): void { + public function apply(): void { freeradius_users_resync(); } } From 0efdc219804b9c2ebe72a8f3ac8304d847fd802c Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 30 May 2025 20:40:05 -0600 Subject: [PATCH 50/59] tests: add tests for the FreeRADIUSUser model --- .../Tests/APIModelsFreeRADIUSUserTestCase.inc | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFreeRADIUSUserTestCase.inc diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFreeRADIUSUserTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFreeRADIUSUserTestCase.inc new file mode 100644 index 000000000..cd09c610c --- /dev/null +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFreeRADIUSUserTestCase.inc @@ -0,0 +1,57 @@ +create(); + $raddb = file_get_contents('/usr/local/etc/raddb/users'); + $this->assert_str_contains($raddb, 'testuser" Cleartext-Password := "testpassword"'); + + # Ensure we can read the created user from the config + $read_user = new FreeRADIUSUser(id: $user->id); + $this->assert_equals($read_user->username->value, 'testuser'); + $this->assert_equals($read_user->password_encryption->value, 'Cleartext-Password'); + $this->assert_equals($read_user->motp_enable->value, false); + $this->assert_equals($read_user->description->value, 'Test User'); + + # Ensure we can update the user + $user = new FreeRADIUSUser( + id: $read_user->id, + username: 'motptestuser', + motp_enable: true, + motp_authmethod: 'motp', + motp_secret: 'abcdef0123456789', + motp_pin: '1234', + motp_offset: 30, + ); + $user->update(); + $raddb = file_get_contents('/usr/local/etc/raddb/users'); + $this->assert_str_does_not_contain($raddb, 'testuser" Cleartext-Password := "testpassword"'); + $this->assert_str_contains($raddb, '"motptestuser" Auth-Type = motp'); + $this->assert_str_contains($raddb, 'MOTP-Init-Secret = abcdef0123456789'); + $this->assert_str_contains($raddb, 'MOTP-PIN = 1234'); + $this->assert_str_contains($raddb, 'MOTP-Offset = 30'); + + # Delete the user and ensure it is removed from the database + $user->delete(); + $raddb = file_get_contents('/usr/local/etc/raddb/users'); + $this->assert_str_does_not_contain($raddb, '"motptestuser" Auth-Type = motp'); + } +} From 0b951c87e0402cb7e59c38a70e27dc0a39f3829c Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 00:10:56 -0600 Subject: [PATCH 51/59] feat: use standard cron expressions for dispatcher schedules --- .../Caches/RESTAPIVersionReleasesCache.inc | 2 +- .../usr/local/pkg/RESTAPI/Core/Dispatcher.inc | 41 ++++++++++++------- .../Tests/APICoreDispatcherTestCase.inc | 2 +- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/RESTAPIVersionReleasesCache.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/RESTAPIVersionReleasesCache.inc index 6db84fb2e..74f00be6b 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/RESTAPIVersionReleasesCache.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/RESTAPIVersionReleasesCache.inc @@ -15,7 +15,7 @@ class RESTAPIVersionReleasesCache extends Cache { const RELEASES_URL = 'https://api.github.com/repos/jaredhendrickson13/pfsense-api/releases'; public int $timeout = 30; - public string $schedule = 'hourly'; + public string $schedule = '0 * * * *'; /** * Retrieves available release information from external repos and updates the releases cache files. diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Dispatcher.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Dispatcher.inc index 78e708d24..e3afd0730 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Dispatcher.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Dispatcher.inc @@ -19,10 +19,6 @@ class Dispatcher { * @const DISPATCH_SCRIPT The absolute file path to the dispatch.sh helper script. */ const DISPATCH_SCRIPT = '/usr/local/pkg/RESTAPI/.resources/scripts/dispatch.sh'; - /** - * @const SCHEDULE_OPTIONS The cron event schedules supported by Dispatchers. - */ - const SCHEDULE_OPTIONS = ['hourly', 'daily', 'weekly']; /** * @var string $full_name @@ -297,27 +293,27 @@ class Dispatcher { # Only proceed if a schedule was requested if ($this->schedule) { # Ensure the requested schedule is supported - if (!in_array($this->schedule, self::SCHEDULE_OPTIONS)) { + if (count(explode(' ', $this->schedule)) !== 5) { throw new ServerError( message: "Dispatcher schedule `$this->schedule` is not a supported schedule frequency.", response_id: 'DISPATCHER_SCHEDULE_UNSUPPORTED', ); } - # Check if a cron job already exists for this dispatcher - $dispatcher_cron_job_q = CronJob::query(command: $this->schedule_command); - - # Delete the cron job for this dispatcher if it exists, so we can recreate it with current values - if ($dispatcher_cron_job_q->exists()) { - $existing_cron_job = $dispatcher_cron_job_q->first(); - $existing_cron_job->packages = []; // Don't require the pfSense-pkg-Cron package to delete - $existing_cron_job->delete(); - } + # Remove any existing scheduled CronJob for this Dispatcher + $this->remove_schedule(); # Create the cron job for this dispatcher + $cron_expr = explode(' ', $this->schedule); $cron_job = new CronJob( - data: ['minute' => "@$this->schedule", 'who' => 'root', 'command' => $this->schedule_command], require_pkg: false, + minute: $cron_expr[0], + hour: $cron_expr[1], + mday: $cron_expr[2], + month: $cron_expr[3], + wday: $cron_expr[4], + who: 'root', + command: $this->schedule_command, ); $cron_job->create(); @@ -330,4 +326,19 @@ class Dispatcher { return null; } + + /** + * Removes the scheduled CronJob for this Dispatcher if it exists. + */ + public function remove_schedule(): void { + # Check if a cron job already exists for this dispatcher + $dispatcher_cron_job_q = CronJob::query(command: $this->schedule_command); + + # Delete the cron job for this dispatcher if it exists, so we can recreate it with current values + if ($dispatcher_cron_job_q->exists()) { + $existing_cron_job = $dispatcher_cron_job_q->first(); + $existing_cron_job->packages = []; // Don't require the pfSense-pkg-Cron package to delete + $existing_cron_job->delete(); + } + } } diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc index 148067f58..663b5a0c3 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc @@ -134,7 +134,7 @@ class APICoreDispatcherTestCase extends TestCase { $this->assert_equals($dispatcher->setup_schedule(), null); # Assign the dispatcher a schedule and ensure setup_schedule() returns a CronJob object with the correct schedule - $dispatcher->schedule = 'daily'; + $dispatcher->schedule = '* 12 * * *'; $dispatcher_cron_job = $dispatcher->setup_schedule(); $cron_job_cmd = '/usr/local/pkg/RESTAPI/.resources/scripts/manage.php notifydispatcher Dispatcher'; $this->assert_equals($dispatcher_cron_job->minute->value, "@$dispatcher->schedule"); From 5e4429fb6f47f3ce3cbf8710d570481bed3c91bd Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 00:16:33 -0600 Subject: [PATCH 52/59] fix: run AvailablePackageCache at irregular interval #700 This should help prevent a potential conflicts with other repo related tasks on the system. --- .../usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc index 37e2bc0eb..eb9a78013 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Caches/AvailablePackageCache.inc @@ -12,8 +12,7 @@ use function RESTAPI\Dispatchers\get_pkg_info; */ class AvailablePackageCache extends Cache { public int $timeout = 120; - public string $schedule = 'hourly'; - + public string $schedule = '12 * * * *'; # Run at irregular interval to avoid conflicts with repo jobs /** * Retrieves the available package information to cache from external repos */ From 6e2fa88bd1083a74fccf1835b577ee7fdb63169e Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 00:19:01 -0600 Subject: [PATCH 53/59] fix: improve file cleanup when the package is deleted Before, we were neglecting to cleanup some dynamic files like forms, endpoints, caches and schemas. This change includes de-install tasks to handle cleanup of these files. This also improves the order of de-installation tasks to avoid package de-install warnings about missing files due to improper removal order. --- pfSense-pkg-RESTAPI/files/pkg-deinstall.in | 27 ++++-- .../pkg/RESTAPI/.resources/scripts/manage.php | 97 +++++++++++++++---- .../files/usr/local/pkg/RESTAPI/Core/Form.inc | 17 ++++ 3 files changed, 113 insertions(+), 28 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/pkg-deinstall.in b/pfSense-pkg-RESTAPI/files/pkg-deinstall.in index b7866c4c9..b8823e77f 100644 --- a/pfSense-pkg-RESTAPI/files/pkg-deinstall.in +++ b/pfSense-pkg-RESTAPI/files/pkg-deinstall.in @@ -1,14 +1,23 @@ #!/bin/sh -if [ "${2}" != "DEINSTALL" ]; then - exit 0 -fi +if [ "${2}" == "DEINSTALL" ]; then + # Remove forms and dispatcher schedules + /usr/local/bin/php -f /usr/local/pkg/RESTAPI/.resources/scripts/manage.php removeforms + /usr/local/bin/php -f /usr/local/pkg/RESTAPI/.resources/scripts/manage.php unscheduledispatchers -# Unlink this package from pfSense -/usr/local/bin/php -f /etc/rc.packages %%PORTNAME%% POST-DEINSTALL + # Unlink this package from pfSense + /usr/local/bin/php -f /etc/rc.packages %%PORTNAME%% POST-DEINSTALL -# Remove the pfsense-RESTAPI command line tool -/bin/rm /usr/local/bin/pfsense-restapi + # Remove the pfsense-RESTAPI command line tool + /bin/rm /usr/local/bin/pfsense-restapi +fi -# Remove Endpoints -rm -rf /usr/local/www/api/v2 2>/dev/null +if [ "${2}" == "POST-DEINSTALL" ]; then + # Remove other files and directories generated by the package + printf "Removing endpoints, schemas, and caches..." + find /usr/local/www/api/v2/ -depth 1 -not -name documentation -not -name schema -exec rm -rf {} + + rm -rf /usr/local/www/api/v2/schema/* + rm /usr/local/pkg/RESTAPI/.resources/cache/* + rm /usr/local/pkg/RESTAPI/.resources/schemas/* + printf " done.\n" +fi diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/.resources/scripts/manage.php b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/.resources/scripts/manage.php index eda18d294..33524acd9 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/.resources/scripts/manage.php +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/.resources/scripts/manage.php @@ -82,6 +82,31 @@ function build_forms(): void { build_privs(); } +/** + * Removes Forms (UI pages) built by the REST API package. + */ +function remove_forms(): void { + # Print that we are starting to remove forms + print 'Removing forms... '; + + # Import each form class + foreach (glob('/usr/local/pkg/RESTAPI/Forms/*.inc') as $file) { + # Import classes files and create object + require_once $file; + $form_class = '\\RESTAPI\\Forms\\' . str_replace('.inc', '', basename($file)); + $form_obj = new $form_class(); + + # Remove the form URL + if (!$form_obj->delete_form_url()) { + print "failed! ($form_obj->url)"; + exit(1); + } + } + + # Print that the removal is done if we made it through the loop + print 'done.' . PHP_EOL; +} + /** * Automatically creates pfSense privileges for each Endpoint class defined in \RESTAPI\Endpoints and each Form class * defined in \RESTAPI\Forms. @@ -181,6 +206,30 @@ function schedule_dispatchers(): void { } } +/** + * Removes the cron jobs for all Dispatcher classes in \RESTAPI\Dispatchers and all Cache classes in \RESTAPI\Caches + * with configured schedules. + */ +function unschedule_dispatchers(): void { + # Variables + $dispatchers = get_classes_from_namespace('\\RESTAPI\\Dispatchers\\'); + $caches = get_classes_from_namespace('\\RESTAPI\\Caches\\'); + + # Include both Dispatcher classes and Cache classes. Cache classes inherit from the Dispatcher class. + $classes = array_merge($dispatchers, $caches); + + # Loop through each defined Dispatcher class and remove the cron jobs for dispatchers with schedules + foreach ($classes as $class) { + $dispatcher = new $class(); + if ($dispatcher->schedule) { + # Start removing the schedules + echo "Removing schedule for $class... "; + $dispatcher->remove_schedule(); + echo 'done.' . PHP_EOL; + } + } +} + /** * Refreshes the cache file by obtaining new day for a given Cache object. * @param string|null $cache_name The shortname of the Cache class that should have its cache file refreshed. @@ -367,7 +416,7 @@ function revert(string $tag): void { /** * Delete the REST API package and restart the webConfigurator to remove nginx changes. */ -function delete() { +function delete(): void { echo shell_exec('/usr/local/sbin/pkg-static delete -y pfSense-pkg-RESTAPI'); echo shell_exec('/etc/rc.restart_webgui'); } @@ -400,24 +449,26 @@ function help(): void { echo 'SYNTAX:' . PHP_EOL; echo ' pfsense-restapi ' . PHP_EOL; echo 'COMMANDS:' . PHP_EOL; - echo ' version : Display the current package version and build information' . PHP_EOL; - echo ' help : Display the help page (this page)' . PHP_EOL; - echo ' buildendpoints : Build all REST API Endpoints included in this package' . PHP_EOL; - echo ' buildforms : Build all REST API Forms included in this package' . PHP_EOL; - echo ' buildprivs : Build all REST API privileges included in this package' . PHP_EOL; - echo ' buildschemas : Build all Schema/documentation files' . PHP_EOL; - echo ' notifydispatcher : Start a dispatcher process' . PHP_EOL; - echo ' scheduledispatchers : Sets up cron jobs for dispatchers and caches on a schedule.' . PHP_EOL; - echo ' refreshcache : Refresh the cache file for a given cache class.' . PHP_EOL; - echo ' runtests : Run all REST API unit Tests. Warning: this may be disruptive!' . PHP_EOL; - echo ' restartwebgui : Restart the webConfigurator in the background' . PHP_EOL; - echo ' update : Update package to the latest stable version available' . PHP_EOL; - echo ' revert : Revert package to a specified version' . PHP_EOL; - echo ' delete : Delete package from this system' . PHP_EOL; - echo ' rotateserverkey : Rotate the REST API server key and remove all existing tokens' . PHP_EOL; - echo ' backup : Create a backup of the REST API configuration' . PHP_EOL; - echo ' restore : Restore the REST API configuration from the latest backup' . PHP_EOL; - echo " sync : Sync this system's REST API configuration to configured HA nodes" . PHP_EOL; + echo ' version : Display the current package version and build information' . PHP_EOL; + echo ' help : Display the help page (this page)' . PHP_EOL; + echo ' buildendpoints : Build all REST API Endpoints included in this package' . PHP_EOL; + echo ' buildforms : Build all REST API Forms included in this package' . PHP_EOL; + echo ' removeforms : Remove all REST API Forms included in this package' . PHP_EOL; + echo ' buildprivs : Build all REST API privileges included in this package' . PHP_EOL; + echo ' buildschemas : Build all Schema/documentation files' . PHP_EOL; + echo ' notifydispatcher : Start a dispatcher process' . PHP_EOL; + echo ' scheduledispatchers : Sets up cron jobs for dispatchers and caches on a schedule.' . PHP_EOL; + echo ' unscheduledispatchers : Removes cron jobs for dispatchers and caches on a schedule.' . PHP_EOL; + echo ' refreshcache : Refresh the cache file for a given cache class.' . PHP_EOL; + echo ' runtests : Run all REST API unit Tests. Warning: this may be disruptive!' . PHP_EOL; + echo ' restartwebgui : Restart the webConfigurator in the background' . PHP_EOL; + echo ' update : Update package to the latest stable version available' . PHP_EOL; + echo ' revert : Revert package to a specified version' . PHP_EOL; + echo ' delete : Delete package from this system' . PHP_EOL; + echo ' rotateserverkey : Rotate the REST API server key and remove all existing tokens' . PHP_EOL; + echo ' backup : Create a backup of the REST API configuration' . PHP_EOL; + echo ' restore : Restore the REST API configuration from the latest backup' . PHP_EOL; + echo " sync : Sync this system's REST API configuration to configured HA nodes" . PHP_EOL; echo PHP_EOL; } @@ -425,6 +476,10 @@ function help(): void { if ($argv[1] == 'buildforms') { build_forms(); } +# REMOVE_FORMS COMMAND +elseif ($argv[1] == 'removeforms') { + remove_forms(); +} # BUILDENDPOINTS COMMAND elseif ($argv[1] == 'buildendpoints') { build_endpoints(); @@ -447,6 +502,10 @@ function help(): void { elseif ($argv[1] == 'scheduledispatchers') { schedule_dispatchers(); } +# UNSCHEDULE_DISPATCHER COMMAND +elseif ($argv[1] == 'unscheduledispatchers') { + unschedule_dispatchers(); +} # REFRESH_CACHE COMMAND elseif ($argv[1] == 'refreshcache') { refresh_cache(cache_name: $argv[2]); diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc index d404b7f77..f2bb0b1c0 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Form.inc @@ -562,4 +562,21 @@ class Form { } return false; } + + /** + * Deletes the Form's endpoint file from the pfSense webroot. + * @returns bool Returns true if the file was successfully deleted, otherwise false. + */ + public function delete_form_url(): bool { + # Assign the absolute path to the file. Assume index.php filename if not specified. + $filename = "/usr/local/www/$this->url"; + $filename = str_ends_with($filename, '.php') ? $filename : "$filename/index.php"; + $content = file_get_contents($filename); + + # Delete the file and return true if it was successfully deleted + if (is_file($filename) and str_contains($content, 'RESTAPI/Forms') and unlink($filename)) { + return true; + } + return false; + } } From e392334aacae6f77513ccdbbc4cd6cbe81aec80c Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 00:19:58 -0600 Subject: [PATCH 54/59] build: use FreeBSD 15 for vagrant builds by default Now that CE is on FreeBSD-15.0-CURRENT this should be the default for all builds --- Vagrantfile | 8 ++++---- vagrant-build.sh | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 2f82c76e9..12b938bff 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,6 +1,6 @@ Vagrant.configure("2") do |config| config.vm.guest = :freebsd - config.vm.box = ENV['FREEBSD_VERSION'] || "freebsd/FreeBSD-14.0-CURRENT" + config.vm.box = ENV['FREEBSD_VERSION'] || "freebsd/FreeBSD-15.0-CURRENT" config.vm.synced_folder ".", "/vagrant", id: "vagrant-root", disabled: true config.ssh.shell = "sh" config.vm.base_mac = "080027D14C66" @@ -8,12 +8,12 @@ Vagrant.configure("2") do |config| config.vm.provision "shell", inline: <<-SHELL pkg update pkg upgrade - pkg install -y python38 + pkg install -y python311 pkg install -y php82-composer pkg install -y gitup gitup ports - su vagrant -c "python3.8 -m ensurepip" - su vagrant -c "python3.8 -m pip install jinja2" + su vagrant -c "python3.11 -m ensurepip" + su vagrant -c "python3.11 -m pip install jinja2" SHELL config.vm.provider "virtualbox" do |vb| vb.customize ["modifyvm", :id, "--memory", "1024"] diff --git a/vagrant-build.sh b/vagrant-build.sh index 6165f9ce3..d92abd17c 100755 --- a/vagrant-build.sh +++ b/vagrant-build.sh @@ -1,7 +1,7 @@ #!/bin/sh # Set build variables -FREEBSD_VERSION=${FREEBSD_VERSION:-"freebsd/FreeBSD-14.0-CURRENT"} +FREEBSD_VERSION=${FREEBSD_VERSION:-"freebsd/FreeBSD-15.0-CURRENT"} BUILD_VERSION=${BUILD_VERSION:-"0.0_0-dev"} # Start the vagrant box @@ -19,7 +19,7 @@ rsync -avz --progress -e "ssh -F $SSH_CONFIG_FILE" ../pfsense-api vagrant@defaul cat << END | vagrant ssh composer install --working-dir /home/vagrant/build/pfsense-api cp -r /home/vagrant/build/pfsense-api/vendor/* /home/vagrant/build/pfsense-api/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/.resources/vendor/ -python3.8 /home/vagrant/build/pfsense-api/tools/make_package.py -t $BUILD_VERSION +python3.11 /home/vagrant/build/pfsense-api/tools/make_package.py -t $BUILD_VERSION END # Copy the built package back to the host using SCP From 3b4ae0189b678960006b76f3f65a5f7d43c3dccb Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 00:25:35 -0600 Subject: [PATCH 55/59] feat: fully implement WireGuardTunnel 'descr' field #705 --- .../files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc | 5 +++++ .../pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc | 2 ++ 2 files changed, 7 insertions(+) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc index 721d6c95e..26535d1b7 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc @@ -51,6 +51,11 @@ class WireGuardTunnel extends Model { indicates_false: 'no', help_text: 'Enables or disables this tunnels and any associated peers.', ); + $this->descr = new StringField( + required: false, + default: '', + help_text: 'A description for this WireGuard tunnel.', + ); $this->listenport = new PortField( unique: true, default: '51820', diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc index c7a391852..3a2f5cb4d 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsWireGuardTunnelTestCase.inc @@ -124,6 +124,7 @@ class APIModelsWireGuardTunnelTestCase extends TestCase { $tunnel = new WireGuardTunnel( privatekey: 'KG0BA4UyPilHH5qnXCfr6Lw8ynecOPor88tljLy3AHk=', listenport: '55000', + descr: 'test', async: false, ); $tunnel->create(apply: true); @@ -134,6 +135,7 @@ class APIModelsWireGuardTunnelTestCase extends TestCase { $this->assert_str_contains($wg_showconf->output, 'ListenPort = ' . $tunnel->listenport->value); $this->assert_str_contains($wg_showconf->output, 'PrivateKey = ' . $tunnel->privatekey->value); $this->assert_str_contains($wg_show->output, 'public key: ' . $tunnel->publickey->value); + $this->assert_equals($tunnel->descr->value, 'test'); # Update the tunnel with new values $tunnel->from_representation(privatekey: 'GNdQw+ujEIVgys4B2dDCXcBpiiQsNd2bAq5hnTp+smg=', listenport: '51820'); From 78bdb7e0eebaa8969c203daf76ee29dcc55c803f Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 09:37:37 -0600 Subject: [PATCH 56/59] fix: allow WireGuardTunnel 'descr' to be empty --- .../files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc index 26535d1b7..11a213ae0 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/WireGuardTunnel.inc @@ -54,6 +54,7 @@ class WireGuardTunnel extends Model { $this->descr = new StringField( required: false, default: '', + allow_empty: true, help_text: 'A description for this WireGuard tunnel.', ); $this->listenport = new PortField( From 3363f331fe8fee749b4b732349060589040e9e1c Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 15:39:41 -0600 Subject: [PATCH 57/59] tests: corrected expected schedule string in Dispatcher test --- .../usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc index 663b5a0c3..82d492ea3 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc @@ -137,7 +137,7 @@ class APICoreDispatcherTestCase extends TestCase { $dispatcher->schedule = '* 12 * * *'; $dispatcher_cron_job = $dispatcher->setup_schedule(); $cron_job_cmd = '/usr/local/pkg/RESTAPI/.resources/scripts/manage.php notifydispatcher Dispatcher'; - $this->assert_equals($dispatcher_cron_job->minute->value, "@$dispatcher->schedule"); + $this->assert_equals($dispatcher_cron_job->minute->value, $dispatcher->schedule); $this->assert_equals($dispatcher_cron_job->command->value, $cron_job_cmd); # Delete the CronJob From 7db19474349dbc04c31a8aaf0b0d02cc297f05d1 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 16:36:13 -0600 Subject: [PATCH 58/59] tests: check correct cron syntax in Dispatcher --- .../local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc index 82d492ea3..1ac45c976 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc @@ -134,10 +134,14 @@ class APICoreDispatcherTestCase extends TestCase { $this->assert_equals($dispatcher->setup_schedule(), null); # Assign the dispatcher a schedule and ensure setup_schedule() returns a CronJob object with the correct schedule - $dispatcher->schedule = '* 12 * * *'; + $dispatcher->schedule = '1 2 3 4 5'; $dispatcher_cron_job = $dispatcher->setup_schedule(); $cron_job_cmd = '/usr/local/pkg/RESTAPI/.resources/scripts/manage.php notifydispatcher Dispatcher'; - $this->assert_equals($dispatcher_cron_job->minute->value, $dispatcher->schedule); + $this->assert_equals($dispatcher_cron_job->minute->value, "1"); + $this->assert_equals($dispatcher_cron_job->hour->value, "2"); + $this->assert_equals($dispatcher_cron_job->mday->value, "3"); + $this->assert_equals($dispatcher_cron_job->month->value, "4"); + $this->assert_equals($dispatcher_cron_job->wday->value, "5"); $this->assert_equals($dispatcher_cron_job->command->value, $cron_job_cmd); # Delete the CronJob From 4b40141efd00f14d99e2452372105300767f806f Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 31 May 2025 17:21:29 -0600 Subject: [PATCH 59/59] style: run prettier on changed files --- .../pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc index 1ac45c976..ceddaf442 100644 --- a/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc +++ b/pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreDispatcherTestCase.inc @@ -137,11 +137,11 @@ class APICoreDispatcherTestCase extends TestCase { $dispatcher->schedule = '1 2 3 4 5'; $dispatcher_cron_job = $dispatcher->setup_schedule(); $cron_job_cmd = '/usr/local/pkg/RESTAPI/.resources/scripts/manage.php notifydispatcher Dispatcher'; - $this->assert_equals($dispatcher_cron_job->minute->value, "1"); - $this->assert_equals($dispatcher_cron_job->hour->value, "2"); - $this->assert_equals($dispatcher_cron_job->mday->value, "3"); - $this->assert_equals($dispatcher_cron_job->month->value, "4"); - $this->assert_equals($dispatcher_cron_job->wday->value, "5"); + $this->assert_equals($dispatcher_cron_job->minute->value, '1'); + $this->assert_equals($dispatcher_cron_job->hour->value, '2'); + $this->assert_equals($dispatcher_cron_job->mday->value, '3'); + $this->assert_equals($dispatcher_cron_job->month->value, '4'); + $this->assert_equals($dispatcher_cron_job->wday->value, '5'); $this->assert_equals($dispatcher_cron_job->command->value, $cron_job_cmd); # Delete the CronJob