Skip to content

Commit 7c7da25

Browse files
feat: add REST API setting to allow use of devel package variants #877
1 parent 34a7ac3 commit 7c7da25

4 files changed

Lines changed: 141 additions & 4 deletions

File tree

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,11 +1769,19 @@ class Model {
17691769
* @throws NotFoundError When the Model requires a pfSense package that is not installed.
17701770
* @throws ServerError When a package requires a PHP include file that could not be found.
17711771
*/
1772-
private function check_packages() {
1772+
private function check_packages(): void {
1773+
# Check if the user has opted in to using development (-devel) package variants
1774+
$pkg_config = RESTAPI\Models\RESTAPISettings::get_pkg_config();
1775+
$allow_development_packages = ($pkg_config['allow_development_packages'] ?? '') === 'enabled';
1776+
17731777
# Loop through each required package and ensure it is present on the system.
17741778
foreach ($this->packages as $pkg) {
1775-
# Return an error if the package is not installed
1776-
if (!is_pkg_installed($pkg)) {
1779+
# When the pre-release packages setting is enabled, also accept the -devel variant of the package
1780+
$devel_pkg = $pkg . '-devel';
1781+
$pkg_installed = is_pkg_installed($pkg) || ($allow_development_packages && is_pkg_installed($devel_pkg));
1782+
1783+
# Return an error if neither the package nor its -devel variant (when allowed) is installed
1784+
if (!$pkg_installed) {
17771785
throw new NotFoundError(
17781786
message: "The requested action requires the '$pkg' package but it is not installed.",
17791787
response_id: 'MODEL_MISSING_REQUIRED_PACKAGE',

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/RESTAPISettings.inc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class RESTAPISettings extends Model {
3333
public BooleanField $log_successful_auth;
3434
public StringField $log_level;
3535
public BooleanField $allow_pre_releases;
36+
public BooleanField $allow_development_packages;
3637
public BooleanField $hateoas;
3738
public BooleanField $expose_sensitive_fields;
3839
public StringField $override_sensitive_fields;
@@ -123,6 +124,14 @@ class RESTAPISettings extends Model {
123124
and features that are currently under development and may not be fully stable. Use of pre-release versions
124125
is at your own risk.",
125126
);
127+
$this->allow_development_packages = new BooleanField(
128+
default: false,
129+
indicates_true: 'enabled',
130+
indicates_false: 'disabled',
131+
verbose_name: 'allow development packages',
132+
help_text: 'Enables or disables using the development (-devel) variant of pfSense packages ' .
133+
'required by specific API endpoints.',
134+
);
126135
$this->hateoas = new BooleanField(
127136
default: false,
128137
indicates_true: 'enabled',
@@ -145,8 +154,8 @@ class RESTAPISettings extends Model {
145154
default: [],
146155
choices_callable: 'get_override_sensitive_fields_choices',
147156
many: true,
148-
conditions: ['expose_sensitive_fields' => false],
149157
verbose_name: 'Override Sensitive Fields',
158+
conditions: ['expose_sensitive_fields' => false],
150159
help_text: 'Specifies a list of fields (formatted as ModelName:FieldName) that should have their sensitive ' .
151160
'attribute overridden. Fields selected here will not be considered sensitive and will be included in ' .
152161
'API responses regardless of the `expose_sensitive_fields` setting.',

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreModelTestCase.inc

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,82 @@ class APICoreModelTestCase extends RESTAPI\Core\TestCase {
260260
$test_model->validate();
261261
}
262262

263+
/**
264+
* Checks that validate() throws MODEL_MISSING_REQUIRED_PACKAGE when the -devel variant is installed but
265+
* the `allow_development_packages` setting is disabled (default).
266+
*/
267+
public function test_model_validate_packages_devel_not_allowed_by_default(): void {
268+
# Ensure the allow_development_packages setting is disabled
269+
$settings = new RESTAPI\Models\RESTAPISettings();
270+
$settings->from_internal();
271+
$original = $settings->allow_development_packages->value;
272+
273+
if ($original) {
274+
$settings->allow_development_packages->value = false;
275+
$settings->update(apply: false);
276+
}
277+
278+
# A package that ends with -devel that is not installed should still fail without the setting enabled
279+
$this->assert_throws_response(
280+
response_id: 'MODEL_MISSING_REQUIRED_PACKAGE',
281+
code: 404,
282+
callable: function () {
283+
$test_model = new Test();
284+
$test_model->packages = ['pfSense-pkg-some_package_we_dont_have'];
285+
$test_model->validate();
286+
},
287+
);
288+
}
289+
290+
/**
291+
* Checks that when `allow_development_packages` is enabled in RESTAPISettings, a Model whose required package
292+
* is not installed but whose -devel counterpart IS installed passes validation. Since we cannot install arbitrary
293+
* packages in tests, we verify the inverse: that a package whose -devel name also doesn't exist still fails.
294+
*/
295+
public function test_model_validate_packages_devel_variant_still_fails_when_neither_installed(): void {
296+
# Enable the allow_development_packages setting
297+
$settings = new RESTAPI\Models\RESTAPISettings();
298+
$settings->from_internal();
299+
$settings->allow_development_packages->value = true;
300+
$settings->update(apply: false);
301+
302+
# Even with the setting on, if neither the base package nor the -devel variant is installed, it should fail
303+
$this->assert_throws_response(
304+
response_id: 'MODEL_MISSING_REQUIRED_PACKAGE',
305+
code: 404,
306+
callable: function () {
307+
$test_model = new Test();
308+
$test_model->packages = ['pfSense-pkg-some_package_we_dont_have'];
309+
$test_model->validate();
310+
},
311+
);
312+
313+
# Restore the setting to its original value
314+
$settings->allow_development_packages->value = false;
315+
$settings->update(apply: false);
316+
}
317+
318+
/**
319+
* Checks that when `allow_development_packages` is enabled, a Model whose required package matches an installed
320+
* package (using pfSense-pkg-RESTAPI as a proxy for the base package) still passes validation normally.
321+
*/
322+
public function test_model_validate_packages_base_package_still_accepted_when_devel_enabled(): void {
323+
# Enable the allow_development_packages setting
324+
$settings = new RESTAPI\Models\RESTAPISettings();
325+
$settings->from_internal();
326+
$settings->allow_development_packages->value = true;
327+
$settings->update(apply: false);
328+
329+
# The base package (pfSense-pkg-RESTAPI) is installed; validation should still pass
330+
$test_model = new Test();
331+
$test_model->packages = ['pfSense-pkg-RESTAPI'];
332+
$test_model->validate();
333+
334+
# Restore the setting to its original value
335+
$settings->allow_development_packages->value = false;
336+
$settings->update(apply: false);
337+
}
338+
263339
/**
264340
* Checks to ensure the validate() method properly throws an error if we cannot include a required package include
265341
* file because it is not found in the PHP path.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace RESTAPI\Tests;
4+
5+
require_once 'RESTAPI/autoloader.inc';
6+
7+
use RESTAPI\Core\TestCase;
8+
use RESTAPI\Models\RESTAPISettings;
9+
10+
/**
11+
* Defines test cases for the RESTAPISettings Model.
12+
*/
13+
class APIModelsRESTAPISettingsTestCase extends TestCase {
14+
/**
15+
* Checks that the `allow_development_packages` field defaults to `false`.
16+
*/
17+
public function test_allow_development_packages_default_is_false(): void {
18+
$settings = new RESTAPISettings();
19+
$this->assert_is_false($settings->allow_development_packages->value);
20+
}
21+
22+
/**
23+
* Checks that the `allow_development_packages` field can be set to `true` and persisted.
24+
*/
25+
public function test_allow_development_packages_can_be_enabled(): void {
26+
# Enable the setting
27+
$settings = new RESTAPISettings(data: ['allow_development_packages' => true]);
28+
$settings->validate();
29+
$this->assert_is_true($settings->allow_development_packages->value);
30+
31+
# Disable the setting again so we leave the system in a clean state
32+
$settings = new RESTAPISettings(data: ['allow_development_packages' => false]);
33+
$settings->validate();
34+
$this->assert_is_false($settings->allow_development_packages->value);
35+
}
36+
37+
/**
38+
* Checks that the `allow_development_packages` field is represented as a boolean in the API response.
39+
*/
40+
public function test_allow_development_packages_is_boolean_field(): void {
41+
$settings = new RESTAPISettings();
42+
$this->assert_type(value: $settings->allow_development_packages, type: 'RESTAPI\\Fields\\BooleanField');
43+
}
44+
}

0 commit comments

Comments
 (0)