Skip to content

Commit 48fa2bb

Browse files
Merge pull request #230 from jaredhendrickson13/v142
v1.4.2 Fixes & Features
2 parents 04947b1 + 338b0f9 commit 48fa2bb

14 files changed

+607
-11
lines changed

docs/CONTRIBUTING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,13 @@ Often times you will need to create functions to condense redundant tasks. You c
366366

367367
`$some_variable = APITools\your_custom_tool_function();`
368368

369+
As a general rule, functions should be kept within the API model they relate closest to. For example, a function that
370+
checks for the existence of an alias by name should be kept in the APIFirewallAlias* models, even if multiple models
371+
will use the function. Tool functions should only be used in one of the following situations:
372+
- Multiple models, endpoints or tools use the function and the function does not directly relate to any of the existing
373+
API models, or it directly relates to multiple models.
374+
- Use of the function causes an `include` or `require` loop.
375+
369376

370377
## Writing API E2E Tests ##
371378
E2E tests are written using Python3. pfSense API includes a an e2e_test_framework module in the `tests` directory to make

pfSense-pkg-API/files/etc/inc/api/endpoints/APIFirewallRuleFlush.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ class APIFirewallRuleFlush extends APIEndpoint {
2020
$this->url = "/api/v1/firewall/rule/flush";
2121
}
2222

23+
protected function put() {
24+
return (new APIFirewallRuleFlushUpdate())->call();
25+
}
26+
2327
protected function delete() {
2428
return (new APIFirewallRuleFlushDelete())->call();
2529
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
// Copyright 2022 Jared Hendrickson
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
require_once("api/framework/APIEndpoint.inc");
17+
18+
class APIRoutingGatewayDefault extends APIEndpoint {
19+
public function __construct() {
20+
$this->url = "/api/v1/routing/gateway/default";
21+
}
22+
23+
protected function put() {
24+
return (new APIRoutingGatewayDefaultUpdate())->call();
25+
}
26+
}

pfSense-pkg-API/files/etc/inc/api/endpoints/APIServicesUnboundHostOverrideFlush.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ class APIServicesUnboundHostOverrideFlush extends APIEndpoint {
2020
$this->url = "/api/v1/services/unbound/host_override/flush";
2121
}
2222

23+
protected function put() {
24+
return (new APIServicesUnboundHostOverrideFlushUpdate())->call();
25+
}
26+
2327
protected function delete() {
2428
return (new APIServicesUnboundHostOverrideFlushDelete())->call();
2529
}

pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,12 @@ function get($id, $data=[], $all=false) {
11871187
"return" => $id,
11881188
"message" => "DHCPd option with type ip-address must be valid IPv4 address or hostname"
11891189
],
1190+
2098 => [
1191+
"status" => "bad request",
1192+
"code" => 400,
1193+
"return" => $id,
1194+
"message" => "Host overrides must be contained within an array"
1195+
],
11901196
2999 => [
11911197
"status" => "bad request",
11921198
"code" => 400,
@@ -3081,7 +3087,18 @@ function get($id, $data=[], $all=false) {
30813087
"return" => $id,
30823088
"message" => "Unknown API floating rule direction specified"
30833089
],
3084-
3090+
4240 => [
3091+
"status" => "bad request",
3092+
"code" => 400,
3093+
"return" => $id,
3094+
"message" => "Rules must be contained within an array"
3095+
],
3096+
4241 => [
3097+
"status" => "bad request",
3098+
"code" => 400,
3099+
"return" => $id,
3100+
"message" => "Rules must contain at least one item"
3101+
],
30853102

30863103
//5000-5999 reserved for /users API calls
30873104
5000 => [

pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleCreate.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ class APIFirewallRuleCreate extends APIModel {
368368
}
369369
}
370370

371-
public function validate_payload() {
371+
public function validate_payload($delay_tracker=true) {
372372
$this->__validate_type();
373373
$this->__validate_interface();
374374
$this->__validate_ipprotocol();
@@ -393,7 +393,7 @@ class APIFirewallRuleCreate extends APIModel {
393393

394394
# Delay generating the tracker. Reduces the likelihood of two rules getting the same tracker in looped calls.
395395
# todo: this is a quick fix and still does not guarantee uniqueness, a better solution is needed
396-
if (!$this->errors) {
396+
if (!$this->errors and $delay_tracker) {
397397
sleep(1);
398398
}
399399

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
// Copyright 2022 Jared Hendrickson
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
require_once("api/framework/APIModel.inc");
17+
require_once("api/framework/APIResponse.inc");
18+
19+
class APIFirewallRuleFlushUpdate extends APIModel {
20+
# Create our method constructor
21+
public function __construct() {
22+
parent::__construct();
23+
$this->privileges = ["page-all", "page-firewall-rules-edit"];
24+
$this->change_note = "Flushed and replaced firewall rules via API";
25+
}
26+
27+
public function action() {
28+
$this->__randomize_tracker();
29+
$this->config["filter"]["rule"] = $this->validated_data;
30+
APITools\sort_firewall_rules();
31+
$this->write_config();
32+
mark_subsystem_dirty('filter');
33+
34+
# Only reload the firewall filter if it was requested by the client
35+
if ($this->initial_data["apply"] === true) {
36+
APIFirewallApplyCreate::apply();
37+
}
38+
39+
return APIResponse\get(0, $this->config["filter"]["rule"]);
40+
}
41+
42+
public function validate_payload() {
43+
# Require data to be passed in as an array
44+
if (is_array($this->initial_data["rules"])) {
45+
# Require at least one rule to be present
46+
if (count($this->initial_data["rules"]) >= 1) {
47+
# Loop through and validate each rule entry requested
48+
foreach ($this->initial_data["rules"] as $initial_rule) {
49+
# Check if this entry is valid for creation using the APIFirewallRuleCreate class
50+
$ent = new APIFirewallRuleCreate();
51+
$ent->client = $this->client;
52+
$ent->initial_data = $initial_rule;
53+
$ent->validate_payload(false);
54+
55+
# Check if an occurred while validating this entry
56+
if ($ent->errors) {
57+
# Grab the error's return code and raise error including the bad entry in the data field
58+
$rc = $ent->errors[0]["return"];
59+
$this->errors[] = APIResponse\get($rc, $initial_rule);
60+
break;
61+
} # Otherwise, if the entry was valid, add it to our validated host overrides
62+
else {
63+
$this->validated_data[] = $ent->validated_data;
64+
}
65+
}
66+
}
67+
# Raise an error if an empty array was passed in
68+
else {
69+
$this->errors[] = APIResponse\get(4241);
70+
}
71+
}
72+
# Raise an error if rules were not passed in as an array
73+
else {
74+
$this->errors[] = APIResponse\get(4240);
75+
}
76+
}
77+
78+
private function __randomize_tracker() {
79+
# Capture the current microsecond value as a starting point
80+
$tracker = (int)microtime(true);
81+
82+
# Loop through each tracker ID and assign it a unique tracker
83+
foreach($this->validated_data as $id=>$rule) {
84+
$this->validated_data[$id]["tracker"] = $tracker;
85+
$tracker--;
86+
}
87+
}
88+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
// Copyright 2022 Jared Hendrickson
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
require_once("api/framework/APIModel.inc");
17+
require_once("api/framework/APIResponse.inc");
18+
19+
20+
class APIRoutingGatewayDefaultUpdate extends APIModel {
21+
# Create our method constructor
22+
public function __construct() {
23+
parent::__construct();
24+
$this->privileges = ["page-all", "page-system-gateways"];
25+
$this->change_note = "Set default gateway via API";
26+
}
27+
28+
public function action() {
29+
# Set the default gateways based on the validated input
30+
$this->config["gateways"]["defaultgw4"] = $this->validated_data["defaultgw4"];
31+
$this->config["gateways"]["defaultgw6"] = $this->validated_data["defaultgw6"];
32+
33+
# Write these changes to config and apply if the client requested
34+
$this->write_config();
35+
$this->apply();
36+
return APIResponse\get(0, $this->validated_data);
37+
}
38+
39+
private function __validate_defaultgw4() {
40+
# Optionally allow clients to update the 'defaultgw4' value
41+
if (isset($this->initial_data["defaultgw4"])) {
42+
# Ensure this is a valid IPv4 gateway
43+
if (APITools\is_gateway($this->initial_data["defaultgw4"], true) === "inet") {
44+
$this->validated_data["defaultgw4"] = $this->initial_data["defaultgw4"];
45+
}
46+
# Allow client to set automatic gateway selection
47+
elseif (in_array($this->initial_data["defaultgw4"], ["", "automatic"])) {
48+
$this->validated_data["defaultgw4"] = "";
49+
}
50+
# Allow client to set no gateway
51+
elseif (in_array($this->initial_data["defaultgw4"], ["-", "none"])) {
52+
$this->validated_data["defaultgw4"] = "-";
53+
}
54+
else {
55+
$this->errors[] = APIResponse\get(6028);
56+
}
57+
}
58+
}
59+
60+
private function __validate_defaultgw6() {
61+
# Optionally allow clients to update the 'defaultgw6' value
62+
if (isset($this->initial_data["defaultgw6"])) {
63+
# Ensure this is a valid IPv6 gateway
64+
if (APITools\is_gateway($this->initial_data["defaultgw6"], true) === "inet6") {
65+
$this->validated_data["defaultgw6"] = $this->initial_data["defaultgw6"];
66+
}
67+
# Allow client to set automatic gateway selection
68+
elseif (in_array($this->initial_data["defaultgw6"], ["", "automatic"])) {
69+
$this->validated_data["defaultgw6"] = "";
70+
}
71+
# Allow client to set no gateway
72+
elseif (in_array($this->initial_data["defaultgw6"], ["-", "none"])) {
73+
$this->validated_data["defaultgw6"] = "-";
74+
}
75+
else {
76+
$this->errors[] = APIResponse\get(6028);
77+
}
78+
}
79+
}
80+
81+
public function validate_payload() {
82+
# Fetch existing default gateway values
83+
$this->validated_data = [
84+
"defaultgw4"=>$this->config["gateways"]["defaultgw4"],
85+
"defaultgw6"=>$this->config["gateways"]["defaultgw6"],
86+
];
87+
88+
# Validate client input
89+
$this->__validate_defaultgw4();
90+
$this->__validate_defaultgw6();
91+
}
92+
93+
public function apply() {
94+
# Mark the routing subsystem as changed, clear if applied
95+
mark_subsystem_dirty("staticroutes");
96+
97+
# Optionally allow clients to apply this route immediately if they passed in a true apply value
98+
# Note: this is a one-off case where this was better to default to true instead of false.
99+
if ($this->initial_data["apply"] !== false) {
100+
system_routing_configure();
101+
system_resolvconf_generate();
102+
filter_configure();
103+
setup_gateways_monitor();
104+
send_event("service reload dyndnsall");
105+
clear_subsystem_dirty("staticroutes");
106+
}
107+
}
108+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
// Copyright 2022 Jared Hendrickson
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
require_once("api/framework/APIModel.inc");
17+
require_once("api/framework/APIResponse.inc");
18+
19+
function unbound_override_flush_host_cmp($a, $b) {
20+
return strcasecmp($a['host'], $b['host']);
21+
}
22+
23+
class APIServicesUnboundHostOverrideFlushUpdate extends APIModel {
24+
# Create our method constructor
25+
public function __construct() {
26+
parent::__construct();
27+
$this->privileges = ["page-all", "page-services-dnsresolver-edithost"];
28+
$this->change_note = "Flushed and replaced DNS Resolver host overrides via API";
29+
}
30+
31+
public function action() {
32+
# Replace any existing host overrides with the host overrides validated in this request
33+
$this->config["unbound"]["hosts"] = $this->validated_data;
34+
usort($this->config["unbound"]["hosts"], "unbound_override_flush_host_cmp");
35+
$this->write_config();
36+
37+
# Mark the Unbound subsystem as changed.
38+
mark_subsystem_dirty("unbound");
39+
40+
# Apply the changes if requested
41+
if ($this->initial_data["apply"] === true) {
42+
(new APIServicesUnboundApplyCreate)->action();
43+
}
44+
45+
return APIResponse\get(0, $this->config["unbound"]["hosts"]);
46+
}
47+
48+
public function validate_payload() {
49+
# Require data to be passed in as an array
50+
if (is_array($this->initial_data["host_overrides"])) {
51+
# Loop through each host override entry requested
52+
foreach ($this->initial_data["host_overrides"] as $initial_host_override) {
53+
# Check if this entry is valid for creation using the APIServicesUnboundHostOverrideCreate class
54+
$ent = new APIServicesUnboundHostOverrideCreate();
55+
$ent->initial_data = $initial_host_override;
56+
$ent->validate_payload();
57+
58+
# Check if an occurred while validating this entry
59+
if ($ent->errors) {
60+
# Grab the error's return code and raise error including the bad entry in the data field
61+
$rc = $ent->errors[0]["return"];
62+
$this->errors[] = APIResponse\get($rc, $initial_host_override);
63+
break;
64+
} # Otherwise, if the entry was valid, add it to our validated host overrides
65+
else {
66+
$this->validated_data[] = $ent->validated_data;
67+
}
68+
}
69+
}
70+
# Raise an error if host overrides were not passed in as an array
71+
else {
72+
$this->errors[] = APIResponse\get(2098);
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)