Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
site_name: pfSense REST API Guide
repo_url: https://github.com/jaredhendrickson13/pfsense-api
nav:
- General:
- Home: index.md
Expand Down
11 changes: 6 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"devDependencies": {
"prettier": "^3.6.2",
"@prettier/plugin-php": "^0.24.0",
"@stoplight/spectral-cli": "^6.15.0"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ class ForeignModelField extends Field {
$internal_value = $this->models[0]->{$this->model_field_internal}->_from_internal($internal_value);
}

# If the model_field_internal, and the model_field are the same, return the internal value as-is.
if ($this->model_field_internal === $this->model_field) {
return $internal_value;
}

# Query for the Model object this value relates to.
$query_modelset = $this->__get_matches($this->model_field_internal, $internal_value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use RESTAPI\Fields\IntegerField;
use RESTAPI\Fields\InterfaceField;
use RESTAPI\Fields\StringField;
use RESTAPI\Fields\UIDField;
use RESTAPI\Responses\ConflictError;
use RESTAPI\Responses\ValidationError;
use RESTAPI\Validators\IPAddressValidator;
use RESTAPI\Validators\UniqueFromForeignModelValidator;
Expand Down Expand Up @@ -89,7 +90,6 @@ class VirtualIP extends Model {
);
$this->vhid = new IntegerField(
required: true,
unique: true,
minimum: 1,
maximum: 255,
conditions: ['mode' => 'carp'],
Expand Down Expand Up @@ -178,6 +178,27 @@ class VirtualIP extends Model {
return $subnet_bits;
}

/**
* Adds extra validation to the vhid field.
* @param int $vhid The incoming `vhid` value to be validated.
* @return int The validated `vhid` value to be set.
* @throws ValidationError When the `vhid` value is already used by another CARP virtual IP on the same interface.
*/
public function validate_vhid(int $vhid): int {
# Check for an existing CARP virtual IP with the same VHID on this interface
$vip_q = $this->query(id__except: $this->id, mode: 'carp', interface: $this->interface->value, vhid: $vhid);

# Ensure no other CARP virtual IP on this interface is using the same VHID
if ($vip_q->exists()) {
$vip = $vip_q->first();
throw new ConflictError(
message: "Virtual IP with ID '$vip->id' is already using VHID '$vhid' on interface '{$this->interface->value}'",
response_id: 'VIRTUALIP_VHID_ALREADY_IN_USE',
);
}
return $vhid;
}

/**
* Obtains the current internal CARP status of this object
* @return string|null Returns a string that indicates the current CARP status of this virtual IP, or null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
namespace RESTAPI\Tests;

use RESTAPI\Core\TestCase;
use RESTAPI\Core\TestCaseRetry;
use RESTAPI\Models\FirewallAlias;

class APIModelsFirewallAliasTestCase extends TestCase {
/**
* Checks that aliases with hostnames correctly populate a pfctl table
*/
public function test_fqdn_alias_populates_pfctl_table() {
#[TestCaseRetry(retries: 3, delay: 1)]
public function test_fqdn_alias_populates_pfctl_table(): void {
# Create an alias that includes dns.google as an alias item
$test_alias = new FirewallAlias(
data: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class APIModelsTableTestCase extends TestCase {
/**
* Checks that we can successfully delete (flush) entrries from a table
*/
#[TestCaseRetry(retries: 3, delay: 1)]
public function test_delete(): void {
# Create a new pf table to test with
$this->add_table(table_name: 'pfrest_test_table', entries: ['1.2.3.4', '4.3.2.1']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,45 @@ class APIModelsVirtualIPTestCase extends TestCase {
$carp_status->update();
$carp_vip->delete(apply: true);
}

public function test_carp_vhid_must_be_unique_per_interface(): void {
# Create a virtual IP to test with
$vip = new VirtualIP(
mode: 'carp',
interface: 'lan',
subnet: '127.1.2.3',
subnet_bits: 32,
password: 'testpasswd',
vhid: 5,
);
$vip->create();

# Ensure we can update the existing VIP with the same VHID without issue
$this->assert_does_not_throw(
callable: function () use ($vip) {
$vip->validate_vhid(vhid: 5);
},
);

# Ensure we cannot create a new VIP with the same VHID on the same interface
$this->assert_throws_response(
response_id: 'VIRTUALIP_VHID_ALREADY_IN_USE',
code: 409,
callable: function () {
$vip = new VirtualIP(mode: 'carp', interface: 'lan');
$vip->validate_vhid(vhid: 5);
},
);

# Ensure we can create a new VIP with the same VHID on a different interface
$this->assert_does_not_throw(
callable: function () {
$vip = new VirtualIP(mode: 'carp', interface: 'wan');
$vip->validate_vhid(vhid: 5);
},
);

# Clean up the VIP we created
$vip->delete();
}
}