Skip to content

Commit 7b277bb

Browse files
Merge pull request #98 from jaredhendrickson13/v117
v1.1.7 Fixes & Features
2 parents f188f36 + 8c2642f commit 7b277bb

10 files changed

Lines changed: 363 additions & 121 deletions

File tree

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,7 +1598,7 @@ function get($id, $data=[], $all=false) {
15981598
"status" => "bad request",
15991599
"code" => 400,
16001600
"return" => $id,
1601-
"message" => "User privilege must be type array or string"
1601+
"message" => "System users cannot be deleted"
16021602
],
16031603
5006 => [
16041604
"status" => "bad request",
@@ -1726,6 +1726,36 @@ function get($id, $data=[], $all=false) {
17261726
"return" => $id,
17271727
"message" => "Authentication server name already in use"
17281728
],
1729+
5036 => [
1730+
"status" => "bad request",
1731+
"code" => 400,
1732+
"return" => $id,
1733+
"message" => "Invalid characters in username"
1734+
],
1735+
5037 => [
1736+
"status" => "bad request",
1737+
"code" => 400,
1738+
"return" => $id,
1739+
"message" => "Username is reserved by the system"
1740+
],
1741+
5038 => [
1742+
"status" => "bad request",
1743+
"code" => 400,
1744+
"return" => $id,
1745+
"message" => "Username cannot contain more than 32 characters"
1746+
],
1747+
5039 => [
1748+
"status" => "bad request",
1749+
"code" => 400,
1750+
"return" => $id,
1751+
"message" => "Invalid characters is IPsec PSK"
1752+
],
1753+
5040 => [
1754+
"status" => "bad request",
1755+
"code" => 400,
1756+
"return" => $id,
1757+
"message" => "User expiration date must be in MM/DD/YYYY format"
1758+
],
17291759
//6000-6999 reserved for /routing API calls
17301760
6000 => [
17311761
"status" => "bad request",
@@ -1904,7 +1934,7 @@ function get($id, $data=[], $all=false) {
19041934

19051935

19061936
];
1907-
$response = $responses[(!in_array($id, $responses)) ? $id : 1];
1937+
$response = $responses[(array_key_exists($id, $responses)) ? $id : 1];
19081938
$response["data"] = $data;
19091939
if ($all === true) {
19101940
$response = $responses;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require_once("util.inc");
2323
require_once("interfaces.inc");
2424
require_once("interfaces_fast.inc");
2525
require_once("priv.defs.inc");
26+
require_once("priv.inc");
2627
require_once("service-utils.inc");
2728
require_once("filter.inc");
2829
require_once("shaper.inc");

pfSense-pkg-API/files/etc/inc/api/models/APIFirewallVirtualIPCreate.inc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class APIFirewallVirtualIPCreate extends APIModel {
105105
if ($this->validated_data["mode"] === "carp") {
106106
# Check for our optional 'vhid' payload value. Assume default if none was specified.
107107
if (isset($this->initial_data['vhid'])) {
108-
if ($this->__vhid_exists($this->initial_data['vhid'])) {
108+
if ($this->__vhid_exists($this->initial_data["interface"], $this->initial_data['vhid'])) {
109109
$this->errors[] = APIResponse\get(4027);
110110
} elseif (1 > $this->initial_data['vhid'] or $this->initial_data['vhid'] > 255) {
111111
$this->errors[] = APIResponse\get(4028);
@@ -153,14 +153,14 @@ class APIFirewallVirtualIPCreate extends APIModel {
153153
$this->validated_data["type"] = "network";
154154
}
155155

156-
private function __vhid_exists($vhid) {
156+
private function __vhid_exists($interface, $vhid) {
157157
# Loop through each virtual IP and ensure it is not using the requested vhid
158158
foreach ($this->config["virtualip"]["vip"] as $vip) {
159-
if (intval($vhid) === intval($vip["vhid"])) {
159+
if ($interface === $vip["interface"] && intval($vhid) === intval($vip["vhid"])) {
160160
return true;
161161
}
162162
}
163163
return false;
164164
}
165165

166-
}
166+
}

pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc

Lines changed: 114 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,57 +25,151 @@ class APIUserCreate extends APIModel {
2525
}
2626

2727
public function action() {
28+
# Increase the system's next UID by one and add our user to the configuration
29+
$this->config["system"]["nextuid"] = strval(intval($this->validated_data["uid"]) + 1);
2830
$this->config['system']['user'][] = $this->validated_data;
29-
$this->config["system"]["nextuid"] = strval(intval($this->validated_data["uid"]) + 1); // Increase our next UID
30-
local_user_set_password($this->validated_data, $this->validated_data["password"]); // Set our new user's password
31-
local_user_set($this->validated_data);
31+
32+
# Write the user to configuration and set the user on the backend. Return response with created user object.
3233
$this->write_config();
33-
$userindex = index_users(); // Update our user index
34+
local_user_set($this->validated_data);
3435
return APIResponse\get(0, $this->validated_data);
3536
}
3637

37-
public function validate_payload() {
38-
$this->validated_data["uid"] = $this->config["system"]["nextuid"]; // Save our next UID
38+
private function __validate_username() {
39+
# Check for our required `username` payload value
3940
if (isset($this->initial_data['username'])) {
40-
// Check that our user already exists
41-
if (array_key_exists($this->initial_data['username'], index_users())) {
42-
$this->errors[] = APIResponse\get(5002);
41+
# Ensure a user with this username does not already exist
42+
if (!array_key_exists($this->initial_data['username'], index_users())) {
43+
# Ensure the username does not contain invalid characters
44+
if (!preg_match("/[^a-zA-Z0-9\.\-_]/", $this->initial_data['username'])) {
45+
# Ensure username is not reserved by the system
46+
if (!$this->is_username_reserved($this->initial_data["username"])) {
47+
# Ensure username is not longer that 32 characters
48+
if (strlen($this->initial_data["username"]) <= 32) {
49+
$this->validated_data["name"] = $this->initial_data['username'];
50+
} else {
51+
$this->errors[] = APIResponse\get(5038);
52+
}
53+
} else {
54+
$this->errors[] = APIResponse\get(5037);
55+
}
56+
} else {
57+
$this->errors[] = APIResponse\get(5036);
58+
}
4359
} else {
44-
$this->validated_data["name"] = trim($this->initial_data['username']);
60+
$this->errors[] = APIResponse\get(5002);
4561
}
4662
} else {
4763
$this->errors[] = APIResponse\get(5000);
4864
}
65+
}
4966

67+
private function __validate_password() {
68+
# Check for our required `password` payload value
5069
if (isset($this->initial_data['password'])) {
51-
$this->validated_data["password"] = trim($this->initial_data['password']);
70+
# Generate the password hash and add it to our validated data
71+
local_user_set_password($this->validated_data, $this->initial_data['password']);
5272
} else {
5373
$this->errors[] = APIResponse\get(5003);
5474
}
75+
}
76+
77+
private function __validate_priv() {
78+
global $priv_list;
79+
$this->validated_data["priv"] = [];
80+
81+
# Check for our optional `priv` payload value
82+
if ($this->initial_data["priv"]) {
83+
# Ensure value is an array
84+
if (!is_array($this->initial_data["priv"])) {
85+
$this->initial_data["priv"] = array($this->initial_data["priv"]);
86+
}
87+
88+
# Loop through each requested privilege and ensure it exists
89+
foreach ($this->initial_data["priv"] as $priv) {
90+
if (array_key_exists($priv, $priv_list)) {
91+
$this->validated_data["priv"][] = $priv;
92+
$this->validated_data["priv"] = array_unique($this->validated_data["priv"]);
93+
} else {
94+
$this->errors[] = APIResponse\get(5006);
95+
break;
96+
}
97+
}
98+
}
99+
}
55100

101+
private function __validate_disabled() {
102+
# Check for our optional `disabled` payload value
56103
if ($this->initial_data["disabled"] === true) {
57-
$this->validated_data["disabled"] = ""; // Update our user's disabled value if not false
58-
} elseif ($this->initial_data["disabled"] === false) {
59-
unset($this->validated_data["disabled"]); // Unset our disabled value if not requested
104+
$this->validated_data["disabled"] = "";
60105
}
106+
}
61107

108+
private function __validate_descr() {
109+
# Check for our optional `descr` payload value
62110
if (isset($this->initial_data['descr'])) {
63-
$this->validated_data["descr"] = trim($this->initial_data['descr']); // Update our user's full name
111+
$this->validated_data["descr"] = $this->initial_data['descr'];
64112
}
113+
}
65114

115+
private function __validate_expires() {
116+
# Check for our optional `expires` payload value
66117
if (isset($this->initial_data['expires'])) {
67-
$this->validated_data["expires"] = trim($this->initial_data['expires']); // Update our user's expiration date
118+
# Try to format the date string, return an error if the format is invalid
119+
try {
120+
$this->validated_data["expires"] = (new DateTime($this->initial_data['expires']))->format("m/d/Y");
121+
} catch (Exception $e) {
122+
$this->errors[] = APIResponse\get(5040);
123+
}
68124
}
125+
}
69126

127+
private function __validate_authorizedkeys() {
128+
# Check for our optional `authorizedkeys` payload value
70129
if (isset($this->initial_data['authorizedkeys'])) {
71-
$this->validated_data["authorizedkeys"] = trim($this->initial_data['authorizedkeys']); // Update our user's authorized keys
130+
$this->validated_data["authorizedkeys"] = base64_encode($this->initial_data['authorizedkeys']);
72131
}
132+
}
73133

134+
private function __validate_ipsecpsk() {
135+
# Check for our optional `ipsecpsk` payload value
74136
if (isset($this->initial_data['ipsecpsk'])) {
75-
$this->validated_data["ipsecpsk"] = trim($this->initial_data['ipsecpsk']); // Update our user's IPsec pre-shared key
137+
# Ensure the PSK does not contain invalid characters
138+
if (preg_match('/^[[:ascii:]]*$/', $_POST['ipsecpsk'])) {
139+
$this->validated_data["ipsecpsk"] = $this->initial_data['ipsecpsk'];
140+
} else {
141+
$this->errors[] = APIResponse\get(5039);
142+
}
76143
}
144+
}
77145

78-
$this->validated_data["scope"] = "user"; // Set our new user's system scope
79-
$this->validated_data["priv"] = []; // Default our privs to empty array
146+
public function validate_payload() {
147+
# Set static object values
148+
$this->validated_data["uid"] = $this->config["system"]["nextuid"];
149+
$this->validated_data["scope"] = "user";
150+
151+
# Run each validation method
152+
$this->__validate_username();
153+
$this->__validate_password();
154+
$this->__validate_priv();
155+
$this->__validate_descr();
156+
$this->__validate_disabled();
157+
$this->__validate_expires();
158+
$this->__validate_authorizedkeys();
159+
$this->__validate_ipsecpsk();
160+
}
161+
162+
public function is_username_reserved($user) {
163+
# Open the /etc/passwd file to read all system users
164+
$sys_users = explode(PHP_EOL, file_get_contents("/etc/passwd"));
165+
166+
# Loop through each system user and check if the username is reserved
167+
foreach ($sys_users as $sys_user_ent) {
168+
$sys_username = explode(":", $sys_user_ent)[0];
169+
if ($sys_username == $user) {
170+
return true;
171+
}
172+
}
173+
return false;
80174
}
81175
}

pfSense-pkg-API/files/etc/inc/api/models/APIUserDelete.inc

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,37 @@ class APIUserDelete extends APIModel {
2525
}
2626

2727
public function action() {
28-
$index_id = index_users()[$this->validated_data["username"]]; // Save our user's index ID number
29-
$del_user = $this->config["system"]["user"][$index_id];
30-
local_user_del($this->config["system"]["user"][$index_id]); // Delete our user on the backend
31-
unset($this->config['system']['user'][$index_id]); // Unset our user from config
32-
$this->config['system']['user'] = array_values($this->config['system']['user']); // Reindex our users
33-
$this->write_config(); // Write our new config
34-
return APIResponse\get(0, $del_user);
28+
# Remove user from backend and remove from config
29+
local_user_del($this->config["system"]["user"][$this->id]);
30+
unset($this->config["system"]["user"][$this->id]);
31+
$this->write_config();
32+
return APIResponse\get(0, $this->validated_data);
3533
}
3634

37-
public function validate_payload() {
38-
if (isset($this->initial_data["username"])) {
39-
if (!array_key_exists($this->initial_data["username"], index_users())) {
35+
private function __validate_username() {
36+
# Check for our required `username` payload value
37+
if (isset($this->initial_data['username'])) {
38+
# Loop through each configured user and check if this user exists
39+
foreach ($this->config["system"]["user"] as $id=>$user) {
40+
if ($this->initial_data["username"] === $user["name"]) {
41+
$this->validated_data = $user;
42+
$this->id = intval($id);
43+
}
44+
}
45+
# Set an error if no user was found
46+
if (!isset($this->validated_data["uid"])) {
4047
$this->errors[] = APIResponse\get(5001);
41-
} else {
42-
$this->validated_data["username"] = $this->initial_data['username'];
43-
$this->validated_data["username"] = trim($this->validated_data["username"]);
48+
}
49+
# Set an error if this is a system user
50+
if ($this->validated_data["scope"] !== "user") {
51+
$this->errors[] = APIResponse\get(5005);
4452
}
4553
} else {
4654
$this->errors[] = APIResponse\get(5000);
4755
}
56+
}
4857

58+
public function validate_payload() {
59+
$this->__validate_username();
4960
}
5061
}

0 commit comments

Comments
 (0)