Skip to content

Commit 058259e

Browse files
author
Jared Hendrickson
committed
Adding concept of object oriented structure and microframework
1 parent 14091f2 commit 058259e

6 files changed

Lines changed: 468 additions & 0 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
namespace APIResponse;
3+
4+
# Pulls a assoc array API response from our response library. Optionally formats descriptive data into messages.
5+
function get($id, $data=[]) {
6+
$responses = [
7+
// 0-999 reserved for system API level responses
8+
0 => [
9+
"status" => "ok",
10+
"code" => 200,
11+
"return" => $id,
12+
"message" => "Success",
13+
],
14+
1 => [
15+
"status" => "server error",
16+
"code" => 500,
17+
"return" => $id,
18+
"message" => "Process encountered unexpected error",
19+
],
20+
2 => [
21+
"status" => "method not allowed",
22+
"code" => 405,
23+
"return" => $id,
24+
"message" => "Invalid HTTP method",
25+
],
26+
3 => [
27+
"status" => "unauthorized",
28+
"code" => 401,
29+
"return" => $id,
30+
"message" => "Authentication failed",
31+
],
32+
4 => [
33+
"status" => "forbidden",
34+
"code" => 401,
35+
"return" => $id,
36+
"message" => "Authorization failed",
37+
],
38+
5 => [
39+
"status" => "not implemented",
40+
"code" => 501,
41+
"return" => $id,
42+
"message" => "Incompatible pfSense version",
43+
],
44+
6 => [
45+
"status" => "forbidden",
46+
"code" => 403,
47+
"return" => $id,
48+
"message" => "Requested action is not allowed",
49+
],
50+
7 => [
51+
"status" => "not found",
52+
"code" => 404,
53+
"return" => $id,
54+
"message" => "Search attribute not found",
55+
],
56+
8 => [
57+
"status" => "not found",
58+
"code" => 404,
59+
"return" => $id,
60+
"message" => "Could not identify pfSense version",
61+
],
62+
9 => [
63+
"status" => "forbidden",
64+
"code" => 403,
65+
"return" => $id,
66+
"message" => "Authentication mode must be set to JWT to enable access token authentication",
67+
],
68+
];
69+
70+
$response = $responses[$id];
71+
$response["data"] = $data;
72+
return $response;
73+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
namespace APITools;
3+
require_once("apiresp.inc");
4+
require_once("php-jwt/src/JWT.php");
5+
require_once("php-jwt/src/ExpiredException.php");
6+
require_once("php-jwt/src/SignatureInvalidException.php");
7+
require_once("php-jwt/src/BeforeValidException.php");
8+
require_once("config.inc");
9+
require_once("util.inc");
10+
require_once("interfaces.inc");
11+
require_once("interfaces_fast.inc");
12+
require_once("service-utils.inc");
13+
require_once("filter.inc");
14+
require_once("shaper.inc");
15+
require_once("auth.inc");
16+
require_once("functions.inc");
17+
use Firebase\JWT\JWT;
18+
use Firebase\JWT\ExpiredException;
19+
use Firebase\JWT\SignatureInvalidException;
20+
use Firebase\JWT\BeforeValidException;
21+
22+
# Gathers our URL form encoded data or JSON body data from our request and places them in a single array
23+
function get_request_data() {
24+
$data = $_GET; // Accept HTTP requests in URL encoded format
25+
// Check if our URL encoded parameters are empty, if so try JSON encoded parameters
26+
if (empty($data)) {
27+
$data = json_decode(file_get_contents('php://input'), true);
28+
}
29+
return $data;
30+
}
31+
32+
# Locates our API configuration from pfSense's XML configuration. Returns
33+
function get_api_config() {
34+
global $config;
35+
$api_pkg_name = "API";
36+
$pkg_conf = $config["installedpackages"]["package"];
37+
// Check that our configuration is an array
38+
if (is_array($pkg_conf)) {
39+
// Loop through our packages and find our API package config
40+
foreach ($pkg_conf as $id => $pkg) {
41+
if ($pkg["name"] === $api_pkg_name) {
42+
return array($id, $pkg["conf"]);
43+
}
44+
}
45+
}
46+
}
47+
48+
# Checks if a specified user is disabled
49+
function is_user_disabled($username) {
50+
global $config;
51+
$users = index_users();
52+
if (array_key_exists("disabled", $config["system"]["user"][$users[$username]])) {
53+
return true;
54+
}
55+
return false;
56+
}
57+
58+
# Creates JWT server key if one does not exist, or optionally allows rotation of the JWT server key
59+
function create_jwt_server_key($rotate=false) {
60+
global $config;
61+
$pkg_index = get_api_configuration()[0]; // Save our current API configs pkg index
62+
$api_config = get_api_configuration()[1]; // Save our current API config
63+
# Create a new server key if one is not set
64+
if (empty($api_config["server_key"]) or $rotate === true) {
65+
$config["installedpackages"]["package"][$pkg_index]["conf"]["server_key"] = bin2hex(random_bytes(32));
66+
write_config();
67+
}
68+
}
69+
70+
# Creates a JWT to use for JWT authentication
71+
function create_jwt($data) {
72+
global $config;
73+
$api_config = get_api_config()[1]; // Save our current API config
74+
$token_exp = $api_config["jwt_exp"]; // Expire token in one hours
75+
create_jwt_server_key(); // Ensure we have a JWT server key
76+
$payload = array(
77+
"iss" => $config["system"]["hostname"],
78+
"exp" => time() + $token_exp,
79+
"nbf" => time(),
80+
"data" => $data
81+
);
82+
return JWT::encode($payload, $api_config["server_key"]);
83+
}
84+
85+
# Decodes a JWT using our store server key
86+
function decode_jwt($token) {
87+
$key = get_api_config()[1]["server_key"]; // Save our current server key
88+
try {
89+
$decoded = (array) JWT::decode($token, $key, array('HS256'));
90+
} catch (Exception $e) {
91+
$decoded = false;
92+
}
93+
return $decoded;
94+
}
95+
96+
# Get our API tokens for a given username
97+
function get_existing_tokens($username) {
98+
// Local variables
99+
$api_config = get_api_config()[1];
100+
$key_user = bin2hex($username); // Save our user's dedicated API client-ID
101+
$user_keys = [];
102+
foreach ($api_config["keys"]["key"] as $id => $key) {
103+
if ($key["client_id"] === $key_user) {
104+
$user_keys[$id] = array("client_token" => $key["client_token"], "algo" => $key["algo"]);
105+
}
106+
}
107+
return $user_keys;
108+
}
109+
110+
# Authenticate using an API token
111+
function authenticate_token($cid, $ctoken) {
112+
$authenticated = false;
113+
$hex_to_user = pack("H*", $cid);
114+
// First check if our hex decoded user exists
115+
if (in_array($hex_to_user, index_users())) {
116+
// Loop through each of our users API tokens and check if key matches
117+
foreach (get_existing_tokens($hex_to_user) as $id => $data) {
118+
$hash_input_key = hash($data["algo"], $ctoken); // Hash our key using our configured algos
119+
if ($hash_input_key === $data["client_token"]) {
120+
$authenticated = true;
121+
break;
122+
}
123+
}
124+
}
125+
return $authenticated;
126+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
require_once("api/APITools.inc");
3+
require_once("api/APIResponse.inc");
4+
require_once("api/APIAuth.inc");
5+
6+
class APIAccessToken {
7+
private $client;
8+
private $methods;
9+
private $req_privs;
10+
public $errors;
11+
public $valid_data;
12+
13+
# Create our method constructor
14+
public function __construct() {
15+
$this->req_privs = [];
16+
$this->methods = ["GET"];
17+
$this->client = new APIAuth($this->req_privs);
18+
$this->errors = [];
19+
$this->valid_data = [];
20+
}
21+
22+
# Validate our API configurations auth mode (must be JWT)
23+
private function validateAuthMode() {
24+
$api_config = APITools\get_api_config()[1];
25+
26+
# Add error if our auth mode is invalid
27+
if ($api_config["authmode"] !== "jwt") {
28+
$this->errors[] = APIResponse\get(9);
29+
}
30+
}
31+
32+
# Validate our request
33+
public function validate($validate_auth=true, $validate_http_method=true) {
34+
# Validate authentication
35+
if ($validate_auth === true) {
36+
# Add error if user is not authenticated
37+
if (!$this->client->is_authenticated) {
38+
$this->errors[] = APIResponse\get(3);
39+
}
40+
# Add error if user is not authorized
41+
if (!$this->client->is_authorized) {
42+
$this->errors[] = APIResponse\get(4);
43+
}
44+
}
45+
46+
# Validate HTTP method
47+
if ($validate_http_method === true) {
48+
49+
}
50+
51+
# Run our field/conditional validators
52+
$this->validateAuthMode();
53+
54+
# Check if we have errors in our error array
55+
if (count($this->errors) === 0) {
56+
return true;
57+
} else {
58+
return false;
59+
}
60+
}
61+
62+
# Run our call. This method will return an assoc array containing the API response results
63+
public function call() {
64+
# Check if our request is valid
65+
if ($this->validate()) {
66+
$jwt = api_create_jwt($this->client->username);
67+
return ApiResponse\get(0, [["token" => $jwt]]);
68+
} else {
69+
return $this->errors[0];
70+
}
71+
}
72+
73+
# Listen for client requests. This method should executed on the API endpoint.
74+
public function listen() {
75+
# RUN API CALL
76+
$resp = $this->call();
77+
http_response_code($resp["code"]);
78+
echo json_encode($resp) . PHP_EOL;
79+
exit();
80+
}
81+
}

0 commit comments

Comments
 (0)