Skip to content

Commit

Permalink
Merge pull request #230 from jaredhendrickson13/v142
Browse files Browse the repository at this point in the history
v1.4.2 Fixes & Features
  • Loading branch information
jaredhendrickson13 authored May 23, 2022
2 parents 04947b1 + 338b0f9 commit 48fa2bb
Show file tree
Hide file tree
Showing 14 changed files with 607 additions and 11 deletions.
7 changes: 7 additions & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,13 @@ Often times you will need to create functions to condense redundant tasks. You c

`$some_variable = APITools\your_custom_tool_function();`

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


## Writing API E2E Tests ##
E2E tests are written using Python3. pfSense API includes a an e2e_test_framework module in the `tests` directory to make
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class APIFirewallRuleFlush extends APIEndpoint {
$this->url = "/api/v1/firewall/rule/flush";
}

protected function put() {
return (new APIFirewallRuleFlushUpdate())->call();
}

protected function delete() {
return (new APIFirewallRuleFlushDelete())->call();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIEndpoint.inc");

class APIRoutingGatewayDefault extends APIEndpoint {
public function __construct() {
$this->url = "/api/v1/routing/gateway/default";
}

protected function put() {
return (new APIRoutingGatewayDefaultUpdate())->call();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class APIServicesUnboundHostOverrideFlush extends APIEndpoint {
$this->url = "/api/v1/services/unbound/host_override/flush";
}

protected function put() {
return (new APIServicesUnboundHostOverrideFlushUpdate())->call();
}

protected function delete() {
return (new APIServicesUnboundHostOverrideFlushDelete())->call();
}
Expand Down
19 changes: 18 additions & 1 deletion pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,12 @@ function get($id, $data=[], $all=false) {
"return" => $id,
"message" => "DHCPd option with type ip-address must be valid IPv4 address or hostname"
],
2098 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Host overrides must be contained within an array"
],
2999 => [
"status" => "bad request",
"code" => 400,
Expand Down Expand Up @@ -3081,7 +3087,18 @@ function get($id, $data=[], $all=false) {
"return" => $id,
"message" => "Unknown API floating rule direction specified"
],

4240 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Rules must be contained within an array"
],
4241 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Rules must contain at least one item"
],

//5000-5999 reserved for /users API calls
5000 => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ class APIFirewallRuleCreate extends APIModel {
}
}

public function validate_payload() {
public function validate_payload($delay_tracker=true) {
$this->__validate_type();
$this->__validate_interface();
$this->__validate_ipprotocol();
Expand All @@ -393,7 +393,7 @@ class APIFirewallRuleCreate extends APIModel {

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIModel.inc");
require_once("api/framework/APIResponse.inc");

class APIFirewallRuleFlushUpdate extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-firewall-rules-edit"];
$this->change_note = "Flushed and replaced firewall rules via API";
}

public function action() {
$this->__randomize_tracker();
$this->config["filter"]["rule"] = $this->validated_data;
APITools\sort_firewall_rules();
$this->write_config();
mark_subsystem_dirty('filter');

# Only reload the firewall filter if it was requested by the client
if ($this->initial_data["apply"] === true) {
APIFirewallApplyCreate::apply();
}

return APIResponse\get(0, $this->config["filter"]["rule"]);
}

public function validate_payload() {
# Require data to be passed in as an array
if (is_array($this->initial_data["rules"])) {
# Require at least one rule to be present
if (count($this->initial_data["rules"]) >= 1) {
# Loop through and validate each rule entry requested
foreach ($this->initial_data["rules"] as $initial_rule) {
# Check if this entry is valid for creation using the APIFirewallRuleCreate class
$ent = new APIFirewallRuleCreate();
$ent->client = $this->client;
$ent->initial_data = $initial_rule;
$ent->validate_payload(false);

# Check if an occurred while validating this entry
if ($ent->errors) {
# Grab the error's return code and raise error including the bad entry in the data field
$rc = $ent->errors[0]["return"];
$this->errors[] = APIResponse\get($rc, $initial_rule);
break;
} # Otherwise, if the entry was valid, add it to our validated host overrides
else {
$this->validated_data[] = $ent->validated_data;
}
}
}
# Raise an error if an empty array was passed in
else {
$this->errors[] = APIResponse\get(4241);
}
}
# Raise an error if rules were not passed in as an array
else {
$this->errors[] = APIResponse\get(4240);
}
}

private function __randomize_tracker() {
# Capture the current microsecond value as a starting point
$tracker = (int)microtime(true);

# Loop through each tracker ID and assign it a unique tracker
foreach($this->validated_data as $id=>$rule) {
$this->validated_data[$id]["tracker"] = $tracker;
$tracker--;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIModel.inc");
require_once("api/framework/APIResponse.inc");


class APIRoutingGatewayDefaultUpdate extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-system-gateways"];
$this->change_note = "Set default gateway via API";
}

public function action() {
# Set the default gateways based on the validated input
$this->config["gateways"]["defaultgw4"] = $this->validated_data["defaultgw4"];
$this->config["gateways"]["defaultgw6"] = $this->validated_data["defaultgw6"];

# Write these changes to config and apply if the client requested
$this->write_config();
$this->apply();
return APIResponse\get(0, $this->validated_data);
}

private function __validate_defaultgw4() {
# Optionally allow clients to update the 'defaultgw4' value
if (isset($this->initial_data["defaultgw4"])) {
# Ensure this is a valid IPv4 gateway
if (APITools\is_gateway($this->initial_data["defaultgw4"], true) === "inet") {
$this->validated_data["defaultgw4"] = $this->initial_data["defaultgw4"];
}
# Allow client to set automatic gateway selection
elseif (in_array($this->initial_data["defaultgw4"], ["", "automatic"])) {
$this->validated_data["defaultgw4"] = "";
}
# Allow client to set no gateway
elseif (in_array($this->initial_data["defaultgw4"], ["-", "none"])) {
$this->validated_data["defaultgw4"] = "-";
}
else {
$this->errors[] = APIResponse\get(6028);
}
}
}

private function __validate_defaultgw6() {
# Optionally allow clients to update the 'defaultgw6' value
if (isset($this->initial_data["defaultgw6"])) {
# Ensure this is a valid IPv6 gateway
if (APITools\is_gateway($this->initial_data["defaultgw6"], true) === "inet6") {
$this->validated_data["defaultgw6"] = $this->initial_data["defaultgw6"];
}
# Allow client to set automatic gateway selection
elseif (in_array($this->initial_data["defaultgw6"], ["", "automatic"])) {
$this->validated_data["defaultgw6"] = "";
}
# Allow client to set no gateway
elseif (in_array($this->initial_data["defaultgw6"], ["-", "none"])) {
$this->validated_data["defaultgw6"] = "-";
}
else {
$this->errors[] = APIResponse\get(6028);
}
}
}

public function validate_payload() {
# Fetch existing default gateway values
$this->validated_data = [
"defaultgw4"=>$this->config["gateways"]["defaultgw4"],
"defaultgw6"=>$this->config["gateways"]["defaultgw6"],
];

# Validate client input
$this->__validate_defaultgw4();
$this->__validate_defaultgw6();
}

public function apply() {
# Mark the routing subsystem as changed, clear if applied
mark_subsystem_dirty("staticroutes");

# Optionally allow clients to apply this route immediately if they passed in a true apply value
# Note: this is a one-off case where this was better to default to true instead of false.
if ($this->initial_data["apply"] !== false) {
system_routing_configure();
system_resolvconf_generate();
filter_configure();
setup_gateways_monitor();
send_event("service reload dyndnsall");
clear_subsystem_dirty("staticroutes");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIModel.inc");
require_once("api/framework/APIResponse.inc");

function unbound_override_flush_host_cmp($a, $b) {
return strcasecmp($a['host'], $b['host']);
}

class APIServicesUnboundHostOverrideFlushUpdate extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-services-dnsresolver-edithost"];
$this->change_note = "Flushed and replaced DNS Resolver host overrides via API";
}

public function action() {
# Replace any existing host overrides with the host overrides validated in this request
$this->config["unbound"]["hosts"] = $this->validated_data;
usort($this->config["unbound"]["hosts"], "unbound_override_flush_host_cmp");
$this->write_config();

# Mark the Unbound subsystem as changed.
mark_subsystem_dirty("unbound");

# Apply the changes if requested
if ($this->initial_data["apply"] === true) {
(new APIServicesUnboundApplyCreate)->action();
}

return APIResponse\get(0, $this->config["unbound"]["hosts"]);
}

public function validate_payload() {
# Require data to be passed in as an array
if (is_array($this->initial_data["host_overrides"])) {
# Loop through each host override entry requested
foreach ($this->initial_data["host_overrides"] as $initial_host_override) {
# Check if this entry is valid for creation using the APIServicesUnboundHostOverrideCreate class
$ent = new APIServicesUnboundHostOverrideCreate();
$ent->initial_data = $initial_host_override;
$ent->validate_payload();

# Check if an occurred while validating this entry
if ($ent->errors) {
# Grab the error's return code and raise error including the bad entry in the data field
$rc = $ent->errors[0]["return"];
$this->errors[] = APIResponse\get($rc, $initial_host_override);
break;
} # Otherwise, if the entry was valid, add it to our validated host overrides
else {
$this->validated_data[] = $ent->validated_data;
}
}
}
# Raise an error if host overrides were not passed in as an array
else {
$this->errors[] = APIResponse\get(2098);
}
}
}
Loading

0 comments on commit 48fa2bb

Please sign in to comment.