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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1668,4 +1668,3 @@ URL: https://{{$hostname}}/api/v1/user

---
[Back to top](#pfsense-rest-api-documentation)
> Made with ♥ by [thedevsaddam](https://github.com/thedevsaddam) | Generated at: 2021-01-31 12:08:59 by [docgen](https://github.com/thedevsaddam/docgen)
73 changes: 73 additions & 0 deletions docs/documentation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4349,6 +4349,79 @@
},
"response": []
},
{
"name": "Update System API Configuration",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"persist\": false, \n \"jwt_exp\": 86400, \n \"authmode\": \"token\",\n \"hashalgo\": \"sha512\", \n \"keybytes\": 64, \n \"allowed_interfaces\": [\"WAN\"]\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"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",
"protocol": "https",
"host": [
"{{$hostname}}"
],
"path": [
"api",
"v1",
"system",
"api"
],
"query": [
{
"key": "enable",
"value": "boolean",
"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)"
},
{
"key": "persist",
"value": "boolean",
"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)"
},
{
"key": "readonly",
"value": "boolean",
"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. "
},
{
"key": "available_interfaces",
"value": "array",
"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)"
},
{
"key": "authmode",
"value": "string",
"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)"
},
{
"key": "jwt_exp",
"value": "integer",
"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)"
},
{
"key": "keyhash",
"value": "string",
"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)"
},
{
"key": "keybytes",
"value": "integer",
"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)"
}
]
},
"description": "Update the API configuration.<br><br>\n\n_Requires at least one of the following privileges:_ [`page-all`, `page-system-api`]"
},
"response": []
},
{
"name": "Read System API Error Library",
"protocolProfileBehavior": {
Expand Down
4 changes: 4 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/endpoints/APISystemAPI.inc
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ class APISystemAPI extends APIEndpoint {
protected function get() {
return (new APISystemAPIRead())->call();
}

protected function put() {
return (new APISystemAPIUpdate())->call();
}
}
30 changes: 30 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 @@ -212,6 +212,36 @@ function get($id, $data=[], $all=false) {
"return" => $id,
"message" => "Table name does not exist"
],
1020 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Unknown API interface specified in allowed interfaces"
],
1021 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Unknown API authentication mode"
],
1022 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "API JWT expiration threshold must be between 300 and 86400"
],
1023 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Unknown API token hash algorithm"
],
1024 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Unsupport API token bytes count"
],

// 2000-2999 reserved for /services API calls
2000 => [
Expand Down
146 changes: 146 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/models/APISystemAPIUpdate.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php
// Copyright 2021 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 APISystemAPIUpdate extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-system-api"];
$this->change_note = "Modified API settings via API";
$this->validated_data = APITools\get_api_config()[1];
}

public function action() {
$this->config["installedpackages"]["package"][APITools\get_api_config()[0]]["conf"] = $this->validated_data;
$this->write_config();
return APIResponse\get(0, $this->validated_data);
}

private function __validate_enable() {
# Check for our optional 'enable' payload value
if ($this->initial_data['enable'] === true) {
$this->validated_data["enable"] = "";
} elseif ($this->initial_data['enable'] === false) {
unset($this->validated_data["enable"]);
}
}

private function __validate_persist() {
# Check for our optional 'persist' payload value
if ($this->initial_data['persist'] === true) {
$this->validated_data["persist"] = "";
} elseif ($this->initial_data['persist'] === false) {
unset($this->validated_data["persist"]);
}
}

private function __validate_readonly() {
# Check for our optional 'readonly' payload value
if ($this->initial_data['readonly'] === true) {
$this->validated_data["readonly"] = "";
} elseif ($this->initial_data['readonly'] === false) {
unset($this->validated_data["readonly"]);
}
}

private function __validate_allowed_interfaces() {
# Local variables
$non_config_ifs = array("any" => "Any", "localhost" => "Link-local");
$available_ifs = array_merge($non_config_ifs, get_configured_interface_with_descr(true));

# Check for our optional 'allowed_interfaces' payload value
if (isset($this->initial_data['allowed_interfaces'])) {
$this->validated_data["allowed_interfaces"] = [];
# Loop through each requested interface and ensure it is valid
foreach ($this->initial_data["allowed_interfaces"] as $if) {
# Convert the interface to the pfSense interface ID if it exists, otherwise leave original input.
$if = (APITools\get_pfsense_if_id($if)) ? APITools\get_pfsense_if_id($if) : $if;

# Check that this interface exists
if (array_key_exists($if, $available_ifs)) {
$this->validated_data["allowed_interfaces"][] = $if;
} else {
$this->errors[] = APIResponse\get(1020);
}
}

# Convert value to internal XML value
$this->validated_data["allowed_interfaces"] = implode(",", $this->validated_data["allowed_interfaces"]);
}
}

private function __validate_authmode() {
# Check for our option 'authmode' payload value
if (isset($this->initial_data["authmode"])) {
# Ensure it is an available option
if (in_array($this->initial_data["authmode"], ["local", "jwt", "token"])) {
$this->validated_data["authmode"] = $this->initial_data["authmode"];
} else {
$this->errors[] = APIResponse\get(1021);
}
}
}

private function __validate_jwt_exp() {
# Check for our option 'jwt_exp' payload value 86400
if (isset($this->initial_data["jwt_exp"])) {
# Ensure it is within range
if ($this->initial_data["jwt_exp"] >= 300 and $this->initial_data["jwt_exp"] <= 86400) {
$this->validated_data["jwt_exp"] = $this->initial_data["jwt_exp"];
} else {
$this->errors[] = APIResponse\get(1022);
}
}
}

private function __validate_keyhash() {
# Check for our option 'keyhash' payload value
if (isset($this->initial_data["keyhash"])) {
# Ensure it is an available option
if (in_array($this->initial_data["keyhash"], ["sha256", "sha384", "sha512", "md5"])) {
$this->validated_data["keyhash"] = $this->initial_data["keyhash"];
} else {
$this->errors[] = APIResponse\get(1023);
}
}
}

private function __validate_keybytes() {
# Check for our option 'keybytes' payload value
if (isset($this->initial_data["keybytes"])) {
# Ensure it is an available option
if (in_array($this->initial_data["keybytes"], ["16", "32", "64", 16, 32, 64])) {
$this->validated_data["keybytes"] = $this->initial_data["keybytes"];
} else {
$this->errors[] = APIResponse\get(1024);
}
}
}

public function validate_payload() {
$this->__validate_enable();
$this->__validate_persist();
$this->__validate_readonly();
$this->__validate_allowed_interfaces();
$this->__validate_authmode();
$this->__validate_jwt_exp();
$this->__validate_keyhash();
$this->__validate_keybytes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@
function IsJsonString(str){try{JSON.parse(str);}catch(e){return false;}
return true;}
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';}
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>
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>
<script type="text/javascript">
$(document).ready(function() {
document.title = 'pfSense REST API Documentation';
Expand Down
4 changes: 4 additions & 0 deletions tests/test_api_v1_system_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
class APIUnitTestSystemAPI(unit_test_framework.APIUnitTest):
url = "/api/v1/system/api"
get_payloads = [{}]
put_payloads = [
{"persist": False, "jwt_exp": 86400, "hashalgo": "sha512", "keybytes": 64, "allowed_interfaces": ["WAN"]},
{"persist": True, "jwt_exp": 300, "hashalgo": "sha256", "keybytes": 16, "allowed_interfaces": ["any"]}
]

APIUnitTestSystemAPI()