Skip to content

Commit 3c7a934

Browse files
Converted more endpoints to object-oriented structure, minor adjustments to framework and contributor notes
1 parent 93733a5 commit 3c7a934

63 files changed

Lines changed: 1573 additions & 262 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CONTRIBUTING.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ writing a model that tests user's local database credentials and do not want the
116116
auth mode you would specify `$this->set_auth_mode = "local";` to always force local authentication. Defaults to the
117117
API's configured auth mode in the /api/ webConfigurator page.
118118

119+
- `$this->change_note` : Sets the description of the action that occurred via API. This value will be shown in the
120+
change logs found at Diagnostics > Backup & Restore > Config History. This defaults to "Made unknown change via API".
121+
This is only necessary if your API model writes changes to the configuration.
122+
119123

120124
#### Other Base Model Properties ####
121125
There are other properties inherited from APIBaseModel that are not intended (and shouldn't be) overridden by your
@@ -125,6 +129,19 @@ custom API model:
125129
- `$this->initial_data` : The request data as it was when the object was created
126130
- `$this->validated_data` : An array for validators to use to populate data that has been validated
127131
- `$this->errors` : An array to populate any errors encountered. Should be an array of APIResponse values.
132+
- `$this->config` : Our pfSense configuration array. You may read current configuration values using this array or write
133+
changes to the configuration by updating it's values. If you do make changes to the configuration, you must use call
134+
`$this->write_config()` to apply them.
135+
136+
#### Reading and Writing to pfSense's XML Configuration ####
137+
Included in the API framework are properties and methods to read and write to pfSense's XML configuration. Please note
138+
that other functions are likely required to configure pfSense's backend to use the new configuration. These properties
139+
and methods are available anywhere within your API model:
140+
141+
- `$this->config` : Our pfSense XML configuration in a PHP array format. You may read the current configuration from
142+
this property or update/add new configuration by assigning the corresponding array key new values
143+
- `$this->write_config()` : This method writes any changes made to $this->config to pfSense's XML configuration file.
144+
Any changes made to $this->config will not be applied until this method is executed.
128145

129146
#### Overriding API Model Validation ####
130147
By default, API models do not provide any sort of validation. You are responsible for overriding the class method to
@@ -159,13 +176,13 @@ class NewAPIModel extends APIBaseModel {
159176
}
160177
```
161178

162-
163179
#### Writing API Model Action ####
164180
By default, the API model will return a 'Your API request was valid but no actions were specified for this endpoint'
165181
error after validating input. This is because we need to tell it what to do when our request is valid! We do this by
166182
overriding the APIBaseModel's `action()` method. This method should simply be a set of tasks to perform upon a
167183
successful API call. If you are writing an endpoint to perform a change that is usually performed via the pfSense
168-
webConfigurator, it may be helpful to look at the PHP code for the webConfigurator page.
184+
webConfigurator, it may be helpful to look at the PHP code for the webConfigurator page. This method must return an
185+
APIResponse item.
169186

170187
As a basic example, if I wanted to add our validated input to our pfSense configuration:
171188

@@ -194,16 +211,13 @@ class NewAPIModel extends APIBaseModel {
194211

195212
# Tell our API model what to do after successfully validating the client's request
196213
public function action(){
197-
$_SESSION["Username"] = $this->client->username; // Save our user to session data for logging
198-
$change_note = " Added TEST value via API"; // Add a change note
199-
$config["test"][] = $this->validated_data; // Write a new 'test' item to our master config
200-
write_config(sprintf(gettext($change_note))); // Apply our configuration change
214+
$this->config["test"][] = $this->validated_data; // Write a new 'test' item to our master config
215+
$this->write_config(); // Apply our configuration change
201216
return APIResponse\get(0, $this->validated_data); // Return a success response containing our added data
202217
}
203218
}
204219
```
205220

206-
207221
#### Writing API endpoints ####
208222
API models are not useful if we do not have an API endpoint that calls upon the model. API endpoints are the PHP scripts
209223
that will be placed in pfSense's web path. To create a new API endpoint, add an index.php file in

pfSense-pkg-API/files/etc/inc/api/api_models/APIFirewallAliases.inc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ class APIFirewallAliases extends APIBaseModel {
1212
}
1313

1414
public function action() {
15-
global $config;
15+
1616
// Check that we have a configuration
17-
if (!empty($config["aliases"]["alias"])) {
18-
$alias_array = $config["aliases"]["alias"];
17+
if (!empty($this->config["aliases"]["alias"])) {
18+
$alias_array = $this->config["aliases"]["alias"];
1919
} else {
2020
$alias_array = [];
2121
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<?php
2+
require_once("api/framework/APIBaseModel.inc");
3+
require_once("api/framework/APIResponse.inc");
4+
5+
class APIFirewallAliasesAdd extends APIBaseModel {
6+
# Create our method constructor
7+
public function __construct() {
8+
parent::__construct();
9+
$this->methods = ["POST"];
10+
$this->privileges = ["page-all", "page-firewall-aliases-edit"];
11+
$this->change_note = "Added firewall alias via API";
12+
}
13+
14+
public function action() {
15+
// Add our alias
16+
$this->config["aliases"] = !is_array($this->config["aliases"]) ? array("alias" => []) : $this->config["aliases"];
17+
$this->config["aliases"]["alias"][] = $this->validated_data; // Write our configuration change
18+
$this->write_config(); // Apply our configuration change
19+
send_event("filter reload"); // Ensure our firewall filter is reloaded
20+
return APIResponse\get(0, $this->validated_data);
21+
}
22+
23+
public function validate_payload() {
24+
25+
$allowed_alias_types = array("host", "network", "port"); // Array of allowed alias types
26+
if (isset($this->initial_data['name'])) {
27+
$name = $this->initial_data['name'];
28+
$name = APITools\sanitize_str($name);
29+
} else {
30+
$this->errors[] = APIResponse\get(4050);
31+
}
32+
if (isset($this->initial_data['type'])) {
33+
$type = $this->initial_data['type'];
34+
$type = trim($type);
35+
} else {
36+
$this->errors[] = APIResponse\get(4061);
37+
}
38+
if (isset($this->initial_data['address'])) {
39+
$address = $this->initial_data['address'];
40+
// Convert string to array
41+
if (!is_array($address)) {
42+
$address = array($address);
43+
}
44+
} else {
45+
$this->errors[] = APIResponse\get(4052);
46+
47+
}
48+
if (isset($this->initial_data['descr'])) {
49+
$descr = $this->initial_data['descr'];
50+
}
51+
if (isset($this->initial_data['detail'])) {
52+
$detail = $this->initial_data['detail'];
53+
// Convert string to array
54+
if (!is_array($detail)) {
55+
$detail = array($detail);
56+
}
57+
}
58+
// Check that our input is valid
59+
if (!is_string($name)) {
60+
$this->errors[] = APIResponse\get(4053);
61+
} elseif (!is_string($type)) {
62+
$this->errors[] = APIResponse\get(4062);
63+
} elseif (!in_array($type, $allowed_alias_types)) {
64+
$this->errors[] = APIResponse\get(4057);
65+
} elseif (isset($descr) and !is_string($descr)) {
66+
$this->errors[] = APIResponse\get(4063);
67+
} elseif (!is_array($address)) {
68+
$this->errors[] = APIResponse\get(4054);
69+
} elseif (isset($detail) and !is_array($detail)) {
70+
$this->errors[] = APIResponse\get(4063);
71+
}
72+
if (!isset($type_err)) {
73+
// Loop through our arrays and ensure the values are valid
74+
$a_count = 0; // Define a loop counter
75+
foreach ($address as $ae) {
76+
// Conditions for alias type 'port'
77+
if ($type === "port") {
78+
// Check that our value is numeric
79+
if (is_numeric($ae)) {
80+
if (1 <= intval($ae) and intval($ae) <= 65535) {
81+
$address[$a_count] = strval($ae);
82+
} else {
83+
$this->errors[] = APIResponse\get(4065);
84+
85+
}
86+
} else {
87+
$this->errors[] = APIResponse\get(4066);
88+
89+
}
90+
}
91+
// Conditionals for alias type 'network'
92+
if ($type === "network") {
93+
// Check that values are strings
94+
if (is_string($ae)) {
95+
// Check that string is a network CIDR
96+
if (strpos($ae, "/")) {
97+
$net_ip = explode("/", $ae)[0]; // Save our network IP
98+
$bit_mask = explode("/", $ae)[1]; // Save our subnet bit mask
99+
// Check if our IP is IPv4
100+
if (is_ipaddrv4($net_ip)) {
101+
$max_bits = 32; // Assign our maximum IPv4 bitmask
102+
} elseif (is_ipaddrv6($net_ip)) {
103+
$max_bits = 128; // Assign our maximum IPv4 bitmask
104+
} else {
105+
$this->errors[] = APIResponse\get(4067);
106+
107+
}
108+
// Check if our bitmask is numeric and in range
109+
if (is_numeric($bit_mask)) {
110+
if (1 <= intval($bit_mask) and intval($bit_mask) <= $max_bits) {
111+
continue;
112+
} else {
113+
$this->errors[] = APIResponse\get(4068);
114+
}
115+
} else {
116+
$this->errors[] = APIResponse\get(4069);
117+
}
118+
} else {
119+
$this->errors[] = APIResponse\get(4069);
120+
121+
}
122+
} else {
123+
$this->errors[] = APIResponse\get(4070);
124+
125+
}
126+
}
127+
// Conditions for alias type 'host'
128+
if ($type === "host") {
129+
// Check that values are strings
130+
if (is_string($ae)) {
131+
$address[$a_count] = sanitize_str($ae);
132+
} else {
133+
$this->errors[] = APIResponse\get(4070);
134+
135+
}
136+
}
137+
// Increase our counter
138+
$a_count++;
139+
}
140+
// Check each of our alias details
141+
foreach ($detail as $de) {
142+
if (!is_string($de)) {
143+
$this->errors[] = APIResponse\get(4071);
144+
145+
}
146+
}
147+
}
148+
// Check our existing aliases
149+
if (is_array($this->config["aliases"])) {
150+
$c_count = 0;
151+
// Loop through each alias and see if alias already exists
152+
foreach ($this->config["aliases"]["alias"] as $ce) {
153+
if ($ce["name"] === $name) {
154+
$this->errors[] = APIResponse\get(4056);
155+
156+
}
157+
}
158+
}
159+
$this->validated_data["name"] = $name; // Save our alias name
160+
$this->validated_data["type"] = $type; // Save our type
161+
$this->validated_data["descr"] = $descr; // Save our description
162+
$this->validated_data["address"] = implode(" ", $address); // Join array in to space seperated string
163+
$this->validated_data["detail"] = implode("||", $detail); // Join array in to || seperated string
164+
}
165+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
require_once("api/framework/APIBaseModel.inc");
3+
require_once("api/framework/APIResponse.inc");
4+
5+
class APIFirewallAliasesAddAddress extends APIBaseModel {
6+
# Create our method constructor
7+
public function __construct() {
8+
parent::__construct();
9+
$this->methods = ["POST"];
10+
$this->privileges = ["page-all", "page-firewall-aliases-edit"];
11+
$this->change_note = "Added firewall alias address via API";
12+
}
13+
14+
public function action() {
15+
// Add our alias
16+
$this->config["aliases"]["alias"][$this->initial_data["id"]]["address"] = implode(" ", $this->validated_data["address"]);
17+
$this->config["aliases"]["alias"][$this->initial_data["id"]]["detail"] = implode("||", $this->validated_data["detail"]);
18+
$this->write_config(); // Apply our configuration change
19+
send_event("filter reload"); // Ensure our firewall filter is reloaded
20+
return APIResponse\get(0, $this->config["aliases"]["alias"][$this->initial_data["id"]]);
21+
}
22+
23+
public function validate_payload() {
24+
25+
if (isset($this->initial_data['name'])) {
26+
$name = $this->initial_data['name'];
27+
$name = APITools\sanitize_str($name);
28+
} else {
29+
$this->errors[] = APIResponse\get(4050);
30+
}
31+
if (isset($this->initial_data['address'])) {
32+
$address = $this->initial_data['address'];
33+
// Convert string to array
34+
if (!is_array($address)) {
35+
$address = array($address);
36+
}
37+
} else {
38+
$this->errors[] = APIResponse\get(4052);
39+
40+
}
41+
if (isset($this->initial_data['detail'])) {
42+
$detail = $this->initial_data['detail'];
43+
// Convert string to array
44+
if (!is_array($detail)) {
45+
$detail = array($detail);
46+
}
47+
}
48+
// Check that our input is valid
49+
if (!is_string($name)) {
50+
$this->errors[] = APIResponse\get(4053);
51+
} elseif (!is_array($address)) {
52+
$this->errors[] = APIResponse\get(4054);
53+
} elseif (isset($detail) and !is_array($detail)) {
54+
$this->errors[] = APIResponse\get(4064);
55+
}
56+
// Loop through our existing firewall entries and check for our requested alias
57+
$c_count = 0;
58+
foreach ($this->config["aliases"]["alias"] as $ce) {
59+
if ($name === $ce["name"]) {
60+
$this->initial_data["id"] = $c_count;
61+
$type = $ce["type"];
62+
$curr_addr = explode(" ", $ce["address"]);
63+
$curr_detail = explode("||", $ce["detail"]);
64+
break;
65+
}
66+
$c_count++; // Increase our counter
67+
}
68+
// If we could not find an alias, return error
69+
if (!isset($type)) {
70+
$this->errors[] = APIResponse\get(4055);
71+
}
72+
if (!isset($type_err)) {
73+
// Loop through our arrays and ensure the values are valid
74+
$a_count = 0; // Define a loop counter
75+
foreach ($address as $ae) {
76+
// Conditions for alias type 'port'
77+
if ($type === "port") {
78+
// Check that our value is numeric
79+
if (is_numeric($ae)) {
80+
if (1 <= intval($ae) and intval($ae) <= 65535) {
81+
$address[$a_count] = strval($ae);
82+
} else {
83+
$this->errors[] = APIResponse\get(4065);
84+
85+
}
86+
} else {
87+
$this->errors[] = APIResponse\get(4066);
88+
89+
}
90+
}
91+
// Conditionals for alias type 'network'
92+
if ($type === "network") {
93+
// Check that values are strings
94+
if (is_string($ae)) {
95+
// Check that string is a network CIDR
96+
if (strpos($ae, "/")) {
97+
$net_ip = explode("/", $ae)[0]; // Save our network IP
98+
$bit_mask = explode("/", $ae)[1]; // Save our subnet bit mask
99+
// Check if our IP is IPv4
100+
if (is_ipaddrv4($net_ip)) {
101+
$max_bits = 32; // Assign our maximum IPv4 bitmask
102+
} elseif (is_ipaddrv6($net_ip)) {
103+
$max_bits = 128; // Assign our maximum IPv4 bitmask
104+
} else {
105+
$this->errors[] = APIResponse\get(4067);
106+
}
107+
// Check if our bitmask is numeric and in range
108+
if (is_numeric($bit_mask)) {
109+
if (1 <= intval($bit_mask) and intval($bit_mask) <= $max_bits) {
110+
continue;
111+
} else {
112+
$this->errors[] = APIResponse\get(4068);
113+
}
114+
} else {
115+
$this->errors[] = APIResponse\get(4069);
116+
}
117+
} else {
118+
$this->errors[] = APIResponse\get(4069);
119+
}
120+
} else {
121+
$this->errors[] = APIResponse\get(4070);
122+
}
123+
}
124+
// Conditions for alias type 'host'
125+
if ($type === "host") {
126+
// Check that values are strings
127+
if (is_string($ae)) {
128+
$address[$a_count] = APITools\sanitize_str($ae);
129+
} else {
130+
$this->errors[] = APIResponse\get(4070);
131+
}
132+
}
133+
// Increase our counter
134+
$a_count++;
135+
}
136+
// Check each of our alias details
137+
foreach ($detail as $de) {
138+
if (!is_string($de)) {
139+
$this->errors[] = APIResponse\get(4071);
140+
}
141+
}
142+
$this->validated_data["address"] = array_merge($curr_addr, $address);
143+
$this->validated_data["detail"] = array_merge($curr_detail, $detail);
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)