Skip to content

Commit 7c46fb3

Browse files
Created API model to update API configuration, updated system API endpoint to allow PUT requests, added unit test to test PUT requests on /api/v1/system/api endpoint, updated docs with instructions for PUT requests on /api/v1/system/api
1 parent bf5f8e9 commit 7c46fb3

8 files changed

Lines changed: 259 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1668,4 +1668,4 @@ URL: https://{{$hostname}}/api/v1/user
16681668

16691669
---
16701670
[Back to top](#pfsense-rest-api-documentation)
1671-
> Made with ♥ by [thedevsaddam](https://github.com/thedevsaddam) | Generated at: 2021-01-31 12:08:59 by [docgen](https://github.com/thedevsaddam/docgen)
1671+
> Made with ♥ by [thedevsaddam](https://github.com/thedevsaddam) | Generated at: 2021-02-03 17:53:41 by [docgen](https://github.com/thedevsaddam/docgen)

docs/documentation.json

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4349,6 +4349,79 @@
43494349
},
43504350
"response": []
43514351
},
4352+
{
4353+
"name": "Update System API Configuration",
4354+
"request": {
4355+
"method": "PUT",
4356+
"header": [],
4357+
"body": {
4358+
"mode": "raw",
4359+
"raw": "{\n \"persist\": false, \n \"jwt_exp\": 86400, \n \"authmode\": \"token\",\n \"hashalgo\": \"sha512\", \n \"keybytes\": 64, \n \"allowed_interfaces\": [\"WAN\"]\n}",
4360+
"options": {
4361+
"raw": {
4362+
"language": "json"
4363+
}
4364+
}
4365+
},
4366+
"url": {
4367+
"raw": "https://{{$hostname}}/api/v1/system/api?enable=boolean&persist=boolean&readonly=boolean&available_interfaces=array&authmode=string&jwt_exp=integer&keyhash=string&keybytes=integer",
4368+
"protocol": "https",
4369+
"host": [
4370+
"{{$hostname}}"
4371+
],
4372+
"path": [
4373+
"api",
4374+
"v1",
4375+
"system",
4376+
"api"
4377+
],
4378+
"query": [
4379+
{
4380+
"key": "enable",
4381+
"value": "boolean",
4382+
"description": "Disable the API. If set to `false`, the API will be disable and no further API requests can be made. In most cases this Is not necessary. (optional)"
4383+
},
4384+
{
4385+
"key": "persist",
4386+
"value": "boolean",
4387+
"description": "Enable/disable persistant API configuration. If set to `true`, pfSense API will store a copy of the API configuration In the case a system update or package update Is needed and/or the API configuration must be restored. If set to `false`, all API configuration will be lost whenever the system updates, the package Is updated, or the package Is deleted. It Is recommended to keep this feature enabled. (optional)"
4388+
},
4389+
{
4390+
"key": "readonly",
4391+
"value": "boolean",
4392+
"description": "Enable read only mode. If set to `true`, the API will only answer read (GET) requests. This also means you will not be able to disable read only mode from the API. "
4393+
},
4394+
{
4395+
"key": "available_interfaces",
4396+
"value": "array",
4397+
"description": "Update the Interfaces that are allowed to answer API requests. Each Item In the array must be a valid physical Interface ID (e.g. `\"em0\"`), pfSense Interface ID, (e.g. `\"opt1\"`), or descriptive Interface name (e.g. `\"WAN\"`). Additionally you may add `\"localhost\"` to allow local API requests, or add `\"any\"` to allow any Interface to answer API requests. It Is best practice to only allow Inside Interfaces to answer API requests, or use firewall rules to filter requests made to outside Interfaces. (optional)"
4398+
},
4399+
{
4400+
"key": "authmode",
4401+
"value": "string",
4402+
"description": "Update the API authentication mode. Choices are `\"local\"` for local database authentication, `\"jwt\"` for JWT bearer token authentication, and `\"token\"` for standalone API token authentication. (optional)"
4403+
},
4404+
{
4405+
"key": "jwt_exp",
4406+
"value": "integer",
4407+
"description": "Update the JWT expiration interval (in seconds). Value must be an Integer greater or equal to `300` and less than or equal to `86400`. This Is only applicable when the `authmode` setting Is set to `jwt`. (optional)"
4408+
},
4409+
{
4410+
"key": "keyhash",
4411+
"value": "string",
4412+
"description": "Update the hashing algorithm to use when generating API tokens. Choices are `\"sha256\"`, `\"sha384\"`, `\"sha512\"`, and `\"md5\"`. This Is only applicable when the `authmode` setting Is set to `token`. (optional)"
4413+
},
4414+
{
4415+
"key": "keybytes",
4416+
"value": "integer",
4417+
"description": "Update the key byte strength to use when generating API tokens. Choices are `16`, `32` and `64`. This Is only applicable when the `authmode` setting Is set to `token`. (optional)"
4418+
}
4419+
]
4420+
},
4421+
"description": "Update the API configuration.<br><br>\n\n_Requires at least one of the following privileges:_ [`page-all`, `page-system-api`]"
4422+
},
4423+
"response": []
4424+
},
43524425
{
43534426
"name": "Read System API Error Library",
43544427
"protocolProfileBehavior": {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@ class APISystemAPI extends APIEndpoint {
2323
protected function get() {
2424
return (new APISystemAPIRead())->call();
2525
}
26+
27+
protected function put() {
28+
return (new APISystemAPIUpdate())->call();
29+
}
2630
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,36 @@ function get($id, $data=[], $all=false) {
212212
"return" => $id,
213213
"message" => "Table name does not exist"
214214
],
215+
1020 => [
216+
"status" => "bad request",
217+
"code" => 400,
218+
"return" => $id,
219+
"message" => "Unknown API interface specified in allowed interfaces"
220+
],
221+
1021 => [
222+
"status" => "bad request",
223+
"code" => 400,
224+
"return" => $id,
225+
"message" => "Unknown API authentication mode"
226+
],
227+
1022 => [
228+
"status" => "bad request",
229+
"code" => 400,
230+
"return" => $id,
231+
"message" => "API JWT expiration threshold must be between 300 and 86400"
232+
],
233+
1023 => [
234+
"status" => "bad request",
235+
"code" => 400,
236+
"return" => $id,
237+
"message" => "Unknown API token hash algorithm"
238+
],
239+
1024 => [
240+
"status" => "bad request",
241+
"code" => 400,
242+
"return" => $id,
243+
"message" => "Unsupport API token bytes count"
244+
],
215245

216246
// 2000-2999 reserved for /services API calls
217247
2000 => [
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
// Copyright 2021 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 APISystemAPIUpdate extends APIModel {
21+
# Create our method constructor
22+
public function __construct() {
23+
parent::__construct();
24+
$this->privileges = ["page-all", "page-system-api"];
25+
$this->change_note = "Modified API settings via API";
26+
$this->validated_data = APITools\get_api_config()[1];
27+
}
28+
29+
public function action() {
30+
$this->config["installedpackages"]["package"][APITools\get_api_config()[0]]["conf"] = $this->validated_data;
31+
$this->write_config();
32+
return APIResponse\get(0, $this->validated_data);
33+
}
34+
35+
private function __validate_enable() {
36+
# Check for our optional 'enable' payload value
37+
if ($this->initial_data['enable'] === true) {
38+
$this->validated_data["enable"] = "";
39+
} elseif ($this->initial_data['enable'] === false) {
40+
unset($this->validated_data["enable"]);
41+
}
42+
}
43+
44+
private function __validate_persist() {
45+
# Check for our optional 'persist' payload value
46+
if ($this->initial_data['persist'] === true) {
47+
$this->validated_data["persist"] = "";
48+
} elseif ($this->initial_data['persist'] === false) {
49+
unset($this->validated_data["persist"]);
50+
}
51+
}
52+
53+
private function __validate_readonly() {
54+
# Check for our optional 'readonly' payload value
55+
if ($this->initial_data['readonly'] === true) {
56+
$this->validated_data["readonly"] = "";
57+
} elseif ($this->initial_data['readonly'] === false) {
58+
unset($this->validated_data["readonly"]);
59+
}
60+
}
61+
62+
private function __validate_allowed_interfaces() {
63+
# Local variables
64+
$non_config_ifs = array("any" => "Any", "localhost" => "Link-local");
65+
$available_ifs = array_merge($non_config_ifs, get_configured_interface_with_descr(true));
66+
67+
# Check for our optional 'allowed_interfaces' payload value
68+
if (isset($this->initial_data['allowed_interfaces'])) {
69+
$this->validated_data["allowed_interfaces"] = [];
70+
# Loop through each requested interface and ensure it is valid
71+
foreach ($this->initial_data["allowed_interfaces"] as $if) {
72+
# Convert the interface to the pfSense interface ID if it exists, otherwise leave original input.
73+
$if = (APITools\get_pfsense_if_id($if)) ? APITools\get_pfsense_if_id($if) : $if;
74+
75+
# Check that this interface exists
76+
if (array_key_exists($if, $available_ifs)) {
77+
$this->validated_data["allowed_interfaces"][] = $if;
78+
} else {
79+
$this->errors[] = APIResponse\get(1020);
80+
}
81+
}
82+
83+
# Convert value to internal XML value
84+
$this->validated_data["allowed_interfaces"] = implode(",", $this->validated_data["allowed_interfaces"]);
85+
}
86+
}
87+
88+
private function __validate_authmode() {
89+
# Check for our option 'authmode' payload value
90+
if (isset($this->initial_data["authmode"])) {
91+
# Ensure it is an available option
92+
if (in_array($this->initial_data["authmode"], ["local", "jwt", "token"])) {
93+
$this->validated_data["authmode"] = $this->initial_data["authmode"];
94+
} else {
95+
$this->errors[] = APIResponse\get(1021);
96+
}
97+
}
98+
}
99+
100+
private function __validate_jwt_exp() {
101+
# Check for our option 'jwt_exp' payload value 86400
102+
if (isset($this->initial_data["jwt_exp"])) {
103+
# Ensure it is within range
104+
if ($this->initial_data["jwt_exp"] >= 300 and $this->initial_data["jwt_exp"] <= 86400) {
105+
$this->validated_data["jwt_exp"] = $this->initial_data["jwt_exp"];
106+
} else {
107+
$this->errors[] = APIResponse\get(1022);
108+
}
109+
}
110+
}
111+
112+
private function __validate_keyhash() {
113+
# Check for our option 'keyhash' payload value
114+
if (isset($this->initial_data["keyhash"])) {
115+
# Ensure it is an available option
116+
if (in_array($this->initial_data["keyhash"], ["sha256", "sha384", "sha512", "md5"])) {
117+
$this->validated_data["keyhash"] = $this->initial_data["keyhash"];
118+
} else {
119+
$this->errors[] = APIResponse\get(1023);
120+
}
121+
}
122+
}
123+
124+
private function __validate_keybytes() {
125+
# Check for our option 'keybytes' payload value
126+
if (isset($this->initial_data["keybytes"])) {
127+
# Ensure it is an available option
128+
if (in_array($this->initial_data["keybytes"], ["16", "32", "64", 16, 32, 64])) {
129+
$this->validated_data["keybytes"] = $this->initial_data["keybytes"];
130+
} else {
131+
$this->errors[] = APIResponse\get(1024);
132+
}
133+
}
134+
}
135+
136+
public function validate_payload() {
137+
$this->__validate_enable();
138+
$this->__validate_persist();
139+
$this->__validate_readonly();
140+
$this->__validate_allowed_interfaces();
141+
$this->__validate_authmode();
142+
$this->__validate_jwt_exp();
143+
$this->__validate_keyhash();
144+
$this->__validate_keybytes();
145+
}
146+
}

pfSense-pkg-API/files/usr/local/www/api/documentation/index.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@
798798
function IsJsonString(str){try{JSON.parse(str);}catch(e){return false;}
799799
return true;}
800800
String.prototype.replaceAll=function(replaceThis,withThis){var re=new RegExp(RegExp.quote(replaceThis),"g");return this.replace(re,withThis);};RegExp.quote=function(str){return str.replace(/([.?*+^$[\]\\(){}-])/g,"\\$1");};function syntaxHighlight(json){json=json.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,function(match){var cls='number';if(/^"/.test(match)){if(/:$/.test(match)){cls='key';}else{cls='string';}}else if(/true|false/.test(match)){cls='boolean';}else if(/null/.test(match)){cls='null';}
801-
return '<span class="'+cls+'">'+match+'</span>';});}</script><br><br><footer class="navbar-default navbar-fixed-bottom"><div class=container-fluid><div class="span12 text-center"><span data-toggle=tooltip title="If the application help you, please feel free to give a star to the project in github. Your star inspire me to work more on open-source projects like this!">Made with <em class=love-color>&#9829;</em> by <a href=https://github.com/thedevsaddam target=_blank class=text-muted>thedevsaddam</a> | Generated at: 2021-01-31 12:08:59 by <a href=https://github.com/thedevsaddam/docgen target=_blank class=text-muted>docgen</a></span></div></div></footer>
801+
return '<span class="'+cls+'">'+match+'</span>';});}</script><br><br><footer class="navbar-default navbar-fixed-bottom"><div class=container-fluid><div class="span12 text-center"><span data-toggle=tooltip title="If the application help you, please feel free to give a star to the project in github. Your star inspire me to work more on open-source projects like this!">Made with <em class=love-color>&#9829;</em> by <a href=https://github.com/thedevsaddam target=_blank class=text-muted>thedevsaddam</a> | Generated at: 2021-02-03 17:53:41 by <a href=https://github.com/thedevsaddam/docgen target=_blank class=text-muted>docgen</a></span></div></div></footer>
802802
<script type="text/javascript">
803803
$(document).ready(function() {
804804
document.title = 'pfSense REST API Documentation';

tests/test_api_v1_system_api.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33
class APIUnitTestSystemAPI(unit_test_framework.APIUnitTest):
44
url = "/api/v1/system/api"
55
get_payloads = [{}]
6+
put_payloads = [
7+
{"persist": False, "jwt_exp": 86400, "hashalgo": "sha512", "keybytes": 64, "allowed_interfaces": ["WAN"]},
8+
{"persist": True, "jwt_exp": 300, "hashalgo": "sha256", "keybytes": 16, "allowed_interfaces": ["any"]}
9+
]
610

711
APIUnitTestSystemAPI()
6.76 KB
Binary file not shown.

0 commit comments

Comments
 (0)