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
38 changes: 38 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/endpoints/APIInterfaceGroup.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?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 APIInterfaceGroup extends APIEndpoint {
public function __construct() {
$this->url = "/api/v1/interface/group";
}

protected function get() {
return (new APIInterfaceGroupRead())->call();
}

protected function post() {
return (new APIInterfaceGroupCreate())->call();
}

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

protected function delete() {
return (new APIInterfaceGroupDelete())->call();
}
}
42 changes: 42 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,48 @@ function get($id, $data=[], $all=false) {
"return" => $id,
"message" => "Interface bridge cannot be deleted while it's in use"
],
3074 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Interface group member must be a valid interface"
],
3075 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Interface group name must consist of only letters (A-Z), digits (0-9) and underscores '_'"
],
3076 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Interface group name is already in use by an interface"
],
3077 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Interface group already exists"
],
3078 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Interface group name too long, max 15 characters allowed"
],
3079 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Interface group id is required"
],
3080 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Interface group does not exist"
],

// 4000-4999 reserved for /firewall API calls
4000 => [
Expand Down
7 changes: 6 additions & 1 deletion pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ function sort_nat_rules($top=false, $data=null, $field=null) {
}

# Input a physical interface ID, or a descriptive interface name, and return the pfSense interface ID (lan,wan,optx)
function get_pfsense_if_id($interface, $include_carp=false) {
function get_pfsense_if_id($interface, $include_carp=false, $include_ifgroup=false) {
# Variables
global $config;
# Loop through our config and check each interface for a physical ID match
Expand Down Expand Up @@ -399,6 +399,11 @@ function get_pfsense_if_id($interface, $include_carp=false) {
}
}
}

# Only include interface groups if they are explicitly requested
if(is_ifgroup($interface)) {
return $interface;
}
}

# Check if input is valid for rule source and destination
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class APIFirewallRuleCreate extends APIModel {
private function __validate_interface() {
# Check for our required 'interface' payload value
if (isset($this->initial_data['interface'])) {
$this->initial_data['interface'] = APITools\get_pfsense_if_id($this->initial_data['interface']);
$this->initial_data['interface'] = APITools\get_pfsense_if_id($this->initial_data['interface'], false, true);
# Check that we found the request pfSense interface ID
if (is_string($this->initial_data["interface"])) {
$this->validated_data['interface'] = $this->initial_data['interface'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class APIFirewallRuleUpdate extends APIModel {
private function __validate_interface() {
# Check for our optional 'interface' payload value
if (isset($this->initial_data['interface'])) {
$this->initial_data['interface'] = APITools\get_pfsense_if_id($this->initial_data['interface']);
$this->initial_data['interface'] = APITools\get_pfsense_if_id($this->initial_data['interface'], false, true);
# Check that we found the request pfSense interface ID
if (is_string($this->initial_data["interface"])) {
$this->validated_data['interface'] = $this->initial_data['interface'];
Expand Down
120 changes: 120 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/models/APIInterfaceGroupCreate.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?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 APIInterfaceGroupCreate extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-interfaces-groups-edit"];
$this->change_note = "Added interface group via API";
}

public function action() {
$this->__init_config();
$this->config["ifgroups"]["ifgroupentry"][] = $this->validated_data;
$this->write_config();
//interface_bridge_configure($this->validated_data);
return APIResponse\get(0, $this->validated_data);
}

private function __validate_members() {
# Require clients to specify at least 1 group member
if (isset($this->initial_data['members'])) {
# Default validated members
$this->validated_data["members"] = "";
$members = [];

# If non-array was passed, convert it into an array
if (!is_array($this->initial_data['members'])) {
$this->initial_data['members'] = [$this->initial_data['members']];
}

# Loop through each member and validate it
foreach ($this->initial_data['members'] as $member) {
# Convert the member to the pfSense ID (wan, lan, optx, etc)
$member_if_id = APITools\get_pfsense_if_id($member);

# Ensure the interface exists
if (!$member_if_id) {
$this->errors[] = APIResponse\get(3074, $member);
}
# If no previous conditions were met, the member is valid.
else {
$members[] = $member_if_id;
}
}

# Convert members to a space separated string as expected by pfSense
$this->validated_data["members"] = implode(" ", $members);
} else {
$this->errors[] = APIResponse\get(3074, $member);
}
}

private function __validate_descr() {
# Optionally allow client to specify a group description. Default if not set.
if (isset($this->initial_data['descr'])) {
$this->validated_data["descr"] = $this->initial_data['descr'];
} else {
$this->validated_data["descr"] = "";
}
}

private function __validate_ifname() {
if (isset($this->initial_data['ifname'])) {
if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->initial_data['ifname'])) {
// name is not alpha numerical (including underscores '_')
$this->errors[] = APIResponse\get(3075);
} elseif (strlen($this->initial_data['ifname']) > 15) {
// name is too long
$this->errors[] = APIResponse\get(3078);
} elseif (APITools\is_ifgroup($this->initial_data['ifname'])) {
// group with this ifname already exists
$this->errors[] = APIResponse\get(3077);
}
elseif (APITools\get_pfsense_if_id($this->initial_data['ifname'])) {
// an interface with this name already exists
$this->errors[] = APIResponse\get(3076);
} else {
// all good
$this->validated_data["ifname"] = $this->initial_data['ifname'];
}
} else {
$this->errors[] = APIResponse\get(3079);
}
}

public function validate_payload() {
$this->__validate_ifname();
$this->__validate_members();
$this->__validate_descr();
}

private function __init_config() {
# Ensure our groups config section is an array
if (!is_array($this->config["ifgroups"])) {
$this->config["ifgroups"] = [];
}
# Ensure our groups groupd config section is an array
if (!is_array($this->config["ifgroups"]["ifgroupentry"])) {
$this->config["ifgroups"]["ifgroupentry"] = [];
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?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 APIInterfaceGroupDelete extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-interfaces-groups-edit"];
}

public function action() {
unset($this->config["ifgroups"]["ifgroupentry"][$this->id]);
$this->write_config();
pfSense_interface_destroy($this->validated_data["ifname"]);
return APIResponse\get(0, $this->validated_data);
}

private function __validate_id() {
if (isset($this->initial_data['id'])) {
if (APITools\is_ifgroup($this->initial_data['id'])) {
foreach ($this->config["ifgroups"]["ifgroupentry"] as $id=>$ifgroup) {
# Check if this group matches
if ($this->initial_data["id"] === $ifgroup["ifname"]) {
$this->validated_data = $ifgroup;
$this->id = $id;
}
}
# If no ID was identified, throw a group not found error
if (!is_numeric($this->id)) {
$this->errors[] = APIResponse\get(3080);
}
} else {
$this->errors[] = APIResponse\get(3080);
}
} else {
$this->errors[] = APIResponse\get(3079);
}
}

public function validate_payload() {
$this->__validate_id();
}
}
36 changes: 36 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/models/APIInterfaceGroupRead.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?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 APIInterfaceGroupRead extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-interfaces-groups"];
}

public function action() {
// Check that we have a configuration
if (!empty($this->config['ifgroups']['ifgroupentry'])) {
$group_array = $this->config['ifgroups']['ifgroupentry'];
} else {
$group_array = [];
}
return APIResponse\get(0, $group_array);
}
}
Loading