Skip to content

Commit c55ccd5

Browse files
Merge pull request #99 from jaredhendrickson13/user_passwd_fix
Enhanced User Endpoints
2 parents 095d70b + 5755860 commit c55ccd5

7 files changed

Lines changed: 356 additions & 115 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/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
}

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

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,68 @@ class APIUserPrivilegeCreate extends APIModel {
2525
}
2626

2727
public function action() {
28-
local_user_set($this->validated_data["user_config"]); // Set user backend parameters
28+
$this->config["system"]["user"][$this->id]["priv"] = $this->validated_data["priv"];
2929
$this->write_config();
30-
return APIResponse\get(0, $this->validated_data["user_config"]["priv"]);
30+
local_user_set($this->validated_data);
31+
return APIResponse\get(0, $this->validated_data["priv"]);
3132
}
3233

3334
public function validate_payload() {
34-
global $priv_list;
35+
$this->__validate_username();
36+
$this->__validate_priv();
37+
}
38+
39+
private function __validate_username() {
40+
# Check for our required `username` payload value
3541
if (isset($this->initial_data['username'])) {
36-
$this->validated_data["username"] = trim($this->initial_data['username']);
37-
$this->validated_data["user_config"] =& getUserEntry($this->validated_data["username"]);
38-
if (!array_key_exists("uid", $this->validated_data["user_config"])) {
42+
# Loop through each configured user and check if this user exists
43+
foreach ($this->config["system"]["user"] as $id=>$user) {
44+
if ($this->initial_data["username"] === $user["name"]) {
45+
$this->validated_data = $user;
46+
$this->id = intval($id);
47+
}
48+
}
49+
# Set an error if no user was found
50+
if (!isset($this->validated_data["uid"])) {
3951
$this->errors[] = APIResponse\get(5001);
4052
}
4153
} else {
4254
$this->errors[] = APIResponse\get(5000);
4355
}
44-
if (isset($this->initial_data['priv'])) {
45-
// Ensure our new priv is array, if it is a string create an array containing the string
46-
if (is_string($this->initial_data["priv"])) {
56+
}
57+
58+
private function __validate_priv() {
59+
global $priv_list;
60+
$this->__init_config();
61+
62+
# Check for our optional `priv` payload value
63+
if ($this->initial_data["priv"]) {
64+
# Ensure value is an array
65+
if (!is_array($this->initial_data["priv"])) {
4766
$this->initial_data["priv"] = array($this->initial_data["priv"]);
4867
}
49-
if (is_array($this->initial_data["priv"])) {
50-
// Loop through our new priv list and check that the privs are valid
51-
foreach ($this->initial_data["priv"] as $np) {
52-
if (!array_key_exists($np, $priv_list)) {
53-
$this->errors[] = APIResponse\get(5006);
54-
}
55-
if (!in_array($np, $this->validated_data["user_config"]["priv"])) {
56-
$this->validated_data["user_config"]["priv"][] = $np;
57-
}
68+
69+
# Loop through each requested privilege and ensure it exists
70+
foreach ($this->initial_data["priv"] as $priv) {
71+
if (array_key_exists($priv, $priv_list)) {
72+
$this->validated_data["priv"][] = $priv;
73+
$this->validated_data["priv"] = array_unique($this->validated_data["priv"]);
74+
} else {
75+
$this->errors[] = APIResponse\get(5006);
76+
break;
5877
}
59-
} else {
60-
$this->errors[] = APIResponse\get(5005);
6178
}
62-
} else {
63-
$this->errors[] = APIResponse\get(5004);
79+
}
80+
}
81+
82+
private function __init_config() {
83+
# Initialize the priv array if the user does not already have one
84+
if (empty($this->validated_data["priv"])) {
85+
$this->validated_data["priv"] = [];
86+
}
87+
# If the user has a priv set, but as a string, convert it to an array
88+
elseif (is_string($this->validated_data["priv"])) {
89+
$this->validated_data["priv"] = array($this->validated_data["priv"]);
6490
}
6591
}
6692
}

0 commit comments

Comments
 (0)