From a32321ada7a8f38a790cb04ef6667fd0306e927a Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 09:10:18 -0700 Subject: [PATCH 01/10] Restructured APIUserCreateModel to fix #95 bug that prevent password from being set --- .../etc/inc/api/framework/APIResponse.inc | 30 +++++ .../etc/inc/api/models/APIUserCreate.inc | 111 ++++++++++++++---- .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 8765 bytes 3 files changed, 121 insertions(+), 20 deletions(-) create mode 100644 tests/unit_test_framework/__pycache__/__init__.cpython-38.pyc diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc index 441a7b461..8c2836faa 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc @@ -1726,6 +1726,36 @@ function get($id, $data=[], $all=false) { "return" => $id, "message" => "Authentication server name already in use" ], + 5036 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Invalid characters in username" + ], + 5037 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Username is reserved by the system" + ], + 5038 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Username cannot contain more than 32 characters" + ], + 5039 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Invalid characters is IPsec PSK" + ], + 5040 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "User expiration date must be in MM/DD/YYYY format" + ], //6000-6999 reserved for /routing API calls 6000 => [ "status" => "bad request", diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc index 4a93d5bcf..9c83ee2e5 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc @@ -25,57 +25,128 @@ class APIUserCreate extends APIModel { } public function action() { + # Increase the system's next UID by one and add our user to the configuration + $this->config["system"]["nextuid"] = strval(intval($this->validated_data["uid"]) + 1); $this->config['system']['user'][] = $this->validated_data; - $this->config["system"]["nextuid"] = strval(intval($this->validated_data["uid"]) + 1); // Increase our next UID - local_user_set_password($this->validated_data, $this->validated_data["password"]); // Set our new user's password - local_user_set($this->validated_data); + + # Write the user to configuration and set the user on the backend. Return response with created user object. $this->write_config(); - $userindex = index_users(); // Update our user index + local_user_set($this->validated_data); return APIResponse\get(0, $this->validated_data); } - public function validate_payload() { - $this->validated_data["uid"] = $this->config["system"]["nextuid"]; // Save our next UID + private function __validate_username() { + # Check for our required `username` payload value if (isset($this->initial_data['username'])) { - // Check that our user already exists - if (array_key_exists($this->initial_data['username'], index_users())) { - $this->errors[] = APIResponse\get(5002); + # Ensure a user with this username does not already exist + if (!array_key_exists($this->initial_data['username'], index_users())) { + # Ensure the username does not contain invalid characters + if (!preg_match("/[^a-zA-Z0-9\.\-_]/", $this->initial_data['username'])) { + # Ensure username is not reserved by the system + if (!$this->is_username_reserved($this->initial_data["username"])) { + # Ensure username is not longer that 32 characters + if (strlen($this->initial_data["username"]) > 32) { + $this->validated_data["name"] = $this->initial_data['username']; + } else { + $this->errors[] = APIResponse\get(5038); + } + } else { + $this->errors[] = APIResponse\get(5037); + } + } else { + $this->errors[] = APIResponse\get(5036); + } } else { - $this->validated_data["name"] = trim($this->initial_data['username']); + $this->errors[] = APIResponse\get(5002); } } else { $this->errors[] = APIResponse\get(5000); } + } + private function __validate_password() { + # Check for our required `password` payload value if (isset($this->initial_data['password'])) { - $this->validated_data["password"] = trim($this->initial_data['password']); + # Generate the password hash and add it to our validated data + local_user_set_password($this->validated_data, $this->initial_data['password']); } else { $this->errors[] = APIResponse\get(5003); } + } + private function __validate_disabled() { + # Check for our optional `disabled` payload value if ($this->initial_data["disabled"] === true) { - $this->validated_data["disabled"] = ""; // Update our user's disabled value if not false - } elseif ($this->initial_data["disabled"] === false) { - unset($this->validated_data["disabled"]); // Unset our disabled value if not requested + $this->validated_data["disabled"] = ""; } + } + private function __validate_descr() { + # Check for our optional `descr` payload value if (isset($this->initial_data['descr'])) { - $this->validated_data["descr"] = trim($this->initial_data['descr']); // Update our user's full name + $this->validated_data["descr"] = $this->initial_data['descr']; } + } + private function __validate_expires() { + # Check for our optional `expires` payload value if (isset($this->initial_data['expires'])) { - $this->validated_data["expires"] = trim($this->initial_data['expires']); // Update our user's expiration date + # Try to format the date string, return an error if the format is invalid + try { + $this->validated_data["expires"] = (new DateTime($this->initial_data['expires']))->format("m/d/Y"); + } catch (Exception $e) { + $this->errors[] = APIResponse\get(5040); + } } + } + private function __validate_authorizedkeys() { + # Check for our optional `authorizedkeys` payload value if (isset($this->initial_data['authorizedkeys'])) { - $this->validated_data["authorizedkeys"] = trim($this->initial_data['authorizedkeys']); // Update our user's authorized keys + $this->validated_data["authorizedkeys"] = $this->initial_data['authorizedkeys']; } + } + private function __validate_ipsecpsk() { + # Check for our optional `ipsecpsk` payload value if (isset($this->initial_data['ipsecpsk'])) { - $this->validated_data["ipsecpsk"] = trim($this->initial_data['ipsecpsk']); // Update our user's IPsec pre-shared key + # Ensure the PSK does not contain invalid characters + if (preg_match('/^[[:ascii:]]*$/', $_POST['ipsecpsk'])) { + $this->validated_data["ipsecpsk"] = $this->initial_data['ipsecpsk']; + } else { + $this->errors[] = APIResponse\get(5039); + } } + } + + public function validate_payload() { + # Set static object values + $this->validated_data["uid"] = $this->config["system"]["nextuid"]; + $this->validated_data["scope"] = "user"; + $this->validated_data["priv"] = []; - $this->validated_data["scope"] = "user"; // Set our new user's system scope - $this->validated_data["priv"] = []; // Default our privs to empty array + # Run each validation method + $this->__validate_username(); + $this->__validate_password(); + $this->__validate_descr(); + $this->__validate_disabled(); + $this->__validate_expires(); + $this->__validate_authorizedkeys(); + $this->__validate_ipsecpsk(); } + + public function is_username_reserved($user) { + # Open the /etc/passwd file to read all system users + $etc_passwd = explode(PHP_EOL, file_get_contents("/etc/passwd")); + + # Loop through each system user and check if the username is reserved + foreach ($etc_passwd as $sys_user_ent) { + $sys_username = explode(":", $sys_user_ent)[0]; + if ($sys_username == $user) { + return true; + } + } + return false; + } + } \ No newline at end of file diff --git a/tests/unit_test_framework/__pycache__/__init__.cpython-38.pyc b/tests/unit_test_framework/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fddbd945b2018f477866dea9d56eb155fad860de GIT binary patch literal 8765 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 literal 0 HcmV?d00001 From f212b0e3a4a99190890b6debf2e7a77dfb5f600f Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 09:10:39 -0700 Subject: [PATCH 02/10] Removed pycache --- .../__pycache__/__init__.cpython-38.pyc | Bin 8765 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/unit_test_framework/__pycache__/__init__.cpython-38.pyc diff --git a/tests/unit_test_framework/__pycache__/__init__.cpython-38.pyc b/tests/unit_test_framework/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index fddbd945b2018f477866dea9d56eb155fad860de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8765 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 From da6665ad432811c39cd73f63efb63490c1f72951 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 09:11:20 -0700 Subject: [PATCH 03/10] Fixed issue in APIResponse.inc that prevent the default error response from being set --- pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc index 8c2836faa..e95272168 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc @@ -1934,7 +1934,7 @@ function get($id, $data=[], $all=false) { ]; - $response = $responses[(!in_array($id, $responses)) ? $id : 1]; + $response = $responses[(!array_key_exists($id, $responses)) ? $id : 1]; $response["data"] = $data; if ($all === true) { $response = $responses; From f1cbd1e56dbb7379eb96e5d2f3a43e4ec9993598 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 10:32:02 -0700 Subject: [PATCH 04/10] Fixed issue in APITools.inc that prevented the full privilege list from being parsed --- pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc | 2 +- pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc | 1 + pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc index e95272168..81b5084d2 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc @@ -1934,7 +1934,7 @@ function get($id, $data=[], $all=false) { ]; - $response = $responses[(!array_key_exists($id, $responses)) ? $id : 1]; + $response = $responses[(array_key_exists($id, $responses)) ? $id : 1]; $response["data"] = $data; if ($all === true) { $response = $responses; diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc index 1eea4a4e9..847cc9174 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc @@ -23,6 +23,7 @@ require_once("util.inc"); require_once("interfaces.inc"); require_once("interfaces_fast.inc"); require_once("priv.defs.inc"); +require_once("priv.inc"); require_once("service-utils.inc"); require_once("filter.inc"); require_once("shaper.inc"); diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc index 9c83ee2e5..e3d8b5a33 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc @@ -45,7 +45,7 @@ class APIUserCreate extends APIModel { # Ensure username is not reserved by the system if (!$this->is_username_reserved($this->initial_data["username"])) { # Ensure username is not longer that 32 characters - if (strlen($this->initial_data["username"]) > 32) { + if (strlen($this->initial_data["username"]) <= 32) { $this->validated_data["name"] = $this->initial_data['username']; } else { $this->errors[] = APIResponse\get(5038); From 319b3e04a2896aac6c2e1ba3882436bfe7d9740e Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 11:02:00 -0700 Subject: [PATCH 05/10] Added ability to create user with privileges --- .../etc/inc/api/models/APIUserCreate.inc | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc index e3d8b5a33..4bed0df73 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc @@ -74,6 +74,29 @@ class APIUserCreate extends APIModel { } } + private function __validate_priv() { + global $priv_list; + $this->validated_data["priv"] = []; + + # Check for our optional `priv` payload value + if ($this->initial_data["priv"]) { + # Ensure value is an array + if (!is_array($this->initial_data["priv"])) { + $this->initial_data["priv"] = array($this->initial_data["priv"]); + } + + # Loop through each requested privilege and ensure it exists + foreach ($this->initial_data["priv"] as $priv) { + if (array_key_exists($priv, $priv_list)) { + $this->validated_data["priv"][] = $priv; + } else { + $this->errors[] = APIResponse\get(5006); + break; + } + } + } + } + private function __validate_disabled() { # Check for our optional `disabled` payload value if ($this->initial_data["disabled"] === true) { @@ -123,11 +146,11 @@ class APIUserCreate extends APIModel { # Set static object values $this->validated_data["uid"] = $this->config["system"]["nextuid"]; $this->validated_data["scope"] = "user"; - $this->validated_data["priv"] = []; # Run each validation method $this->__validate_username(); $this->__validate_password(); + $this->__validate_priv(); $this->__validate_descr(); $this->__validate_disabled(); $this->__validate_expires(); @@ -137,10 +160,10 @@ class APIUserCreate extends APIModel { public function is_username_reserved($user) { # Open the /etc/passwd file to read all system users - $etc_passwd = explode(PHP_EOL, file_get_contents("/etc/passwd")); + $sys_users = explode(PHP_EOL, file_get_contents("/etc/passwd")); # Loop through each system user and check if the username is reserved - foreach ($etc_passwd as $sys_user_ent) { + foreach ($sys_users as $sys_user_ent) { $sys_username = explode(":", $sys_user_ent)[0]; if ($sys_username == $user) { return true; @@ -148,5 +171,4 @@ class APIUserCreate extends APIModel { } return false; } - } \ No newline at end of file From 8fa65ebdb0be13d4e5a989d29ab221ab08150e0e Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 11:42:18 -0700 Subject: [PATCH 06/10] Restructured APIUserUpdateModel to fix #95 bug that prevent password from being set --- .../etc/inc/api/models/APIUserCreate.inc | 2 +- .../etc/inc/api/models/APIUserUpdate.inc | 126 ++++++++++++++---- 2 files changed, 102 insertions(+), 26 deletions(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc index 4bed0df73..a024e6074 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc @@ -126,7 +126,7 @@ class APIUserCreate extends APIModel { private function __validate_authorizedkeys() { # Check for our optional `authorizedkeys` payload value if (isset($this->initial_data['authorizedkeys'])) { - $this->validated_data["authorizedkeys"] = $this->initial_data['authorizedkeys']; + $this->validated_data["authorizedkeys"] = base64_encode($this->initial_data['authorizedkeys']); } } diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserUpdate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserUpdate.inc index 78e905c8f..c749aad6a 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserUpdate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserUpdate.inc @@ -25,52 +25,128 @@ class APIUserUpdate extends APIModel { } public function action() { - local_user_set($this->validated_data); + # Update our new user in the config and set the user on the backend + $this->config["system"]["user"][$this->id] = $this->validated_data; $this->write_config(); + local_user_set($this->validated_data); return APIResponse\get(0, $this->validated_data); } - public function validate_payload() { + private function __validate_username() { + # Check for our required `username` payload value if (isset($this->initial_data['username'])) { - $this->validated_data =& getUserEntry($this->initial_data['username']); - // Check that our user already exists - if (!array_key_exists("uid", $this->validated_data)) { + # Loop through each configured user and check if this user exists + foreach ($this->config["system"]["user"] as $id=>$user) { + if ($this->initial_data["username"] === $user["name"]) { + $this->validated_data = $user; + $this->id = intval($id); + } + } + # Set an error if no user was found + if (!isset($this->validated_data["uid"])) { $this->errors[] = APIResponse\get(5001); } } else { $this->errors[] = APIResponse\get(5000); } + } + + private function __validate_password() { + # Check for our optional `password` payload value if (isset($this->initial_data['password'])) { - $password = $this->initial_data['password']; - $password = trim($password); - local_user_set_password($this->validated_data, $password); // Set our new user's password + # Generate the password hash and add it to our validated data + local_user_set_password($this->validated_data, $this->initial_data['password']); } - if ($this->initial_data["disabled"] === true) { - $this->validated_data["disabled"] = ""; // Update our user's disabled value if not false - } elseif ($this->initial_data["disabled"] === false) { - unset($this->validated_data["disabled"]); // Unset our disabled value if not requested + } + private function __validate_priv() { + global $priv_list; + + # Check for our optional `priv` payload value + if ($this->initial_data["priv"]) { + # Revert priv array to default + $this->validated_data["priv"] = []; + + # Ensure value is an array + if (!is_array($this->initial_data["priv"])) { + $this->initial_data["priv"] = array($this->initial_data["priv"]); + } + + # Loop through each requested privilege and ensure it exists + foreach ($this->initial_data["priv"] as $priv) { + if (array_key_exists($priv, $priv_list)) { + $this->validated_data["priv"][] = $priv; + } else { + $this->errors[] = APIResponse\get(5006); + break; + } + } } + } + + private function __validate_disabled() { + # Check for our optional `disabled` payload value + if ($this->initial_data["disabled"] === true) { + $this->validated_data["disabled"] = ""; + } elseif($this->initial_data["disabled"] === false) { + unset($this->validated_data["disabled"]); + } + } + + private function __validate_descr() { + # Check for our optional `descr` payload value if (isset($this->initial_data['descr'])) { - $descr = $this->initial_data['descr']; - $descr = trim($descr); - $this->validated_data["descr"] = $descr; // Update our user's full name + $this->validated_data["descr"] = $this->initial_data['descr']; } + } + + private function __validate_expires() { + # Check for our optional `expires` payload value if (isset($this->initial_data['expires'])) { - $expires = $this->initial_data['expires']; - $expires = trim($expires); - $this->validated_data["expires"] = $expires; // Update our user's expiration date + # Attempt to format the expiration date if the value is not empty + if (!empty($this->initial_data["expires"])) { + # Try to format the date string, return an error if the format is invalid + try { + $this->validated_data["expires"] = (new DateTime($this->initial_data['expires']))->format("m/d/Y"); + } catch (Exception $e) { + $this->errors[] = APIResponse\get(5040); + } + } + # Otherwise, if the value was blank, unset the expiration date + else { + unset($this->validated_data["expires"]); + } } + } + + private function __validate_authorizedkeys() { + # Check for our optional `authorizedkeys` payload value if (isset($this->initial_data['authorizedkeys'])) { - $authorizedkeys = $this->initial_data['authorizedkeys']; - $authorizedkeys = trim($authorizedkeys); - $this->validated_data["authorizedkeys"] = $authorizedkeys; // Update our user's authorized keys + $this->validated_data["authorizedkeys"] = base64_encode($this->initial_data['authorizedkeys']); } + } + + private function __validate_ipsecpsk() { + # Check for our optional `ipsecpsk` payload value if (isset($this->initial_data['ipsecpsk'])) { - $ipsecpsk = $this->initial_data['ipsecpsk']; - $ipsecpsk = trim($ipsecpsk); - $this->validated_data["ipsecpsk"] = $ipsecpsk; // Update our user's IPsec pre-shared key + # Ensure the PSK does not contain invalid characters + if (preg_match('/^[[:ascii:]]*$/', $_POST['ipsecpsk'])) { + $this->validated_data["ipsecpsk"] = $this->initial_data['ipsecpsk']; + } else { + $this->errors[] = APIResponse\get(5039); + } } - $this->validated_data["scope"] = "user"; // Set our new user's system scope + } + + public function validate_payload() { + # Run each validation method + $this->__validate_username(); + $this->__validate_password(); + $this->__validate_priv(); + $this->__validate_descr(); + $this->__validate_disabled(); + $this->__validate_expires(); + $this->__validate_authorizedkeys(); + $this->__validate_ipsecpsk(); } } \ No newline at end of file From bf8e9f752f6d0ff2581211b7b73e18fa544c94b8 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 12:15:42 -0700 Subject: [PATCH 07/10] Restructured APIUserPrivilegeCreate and APIUserPrivilegeDelete to better match the other user endpoints --- .../etc/inc/api/models/APIUserCreate.inc | 1 + .../inc/api/models/APIUserPrivilegeCreate.inc | 70 +++++++++++++------ .../inc/api/models/APIUserPrivilegeDelete.inc | 68 +++++++++--------- .../etc/inc/api/models/APIUserUpdate.inc | 1 + 4 files changed, 85 insertions(+), 55 deletions(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc index a024e6074..5a134a236 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserCreate.inc @@ -89,6 +89,7 @@ class APIUserCreate extends APIModel { foreach ($this->initial_data["priv"] as $priv) { if (array_key_exists($priv, $priv_list)) { $this->validated_data["priv"][] = $priv; + $this->validated_data["priv"] = array_unique($this->validated_data["priv"]); } else { $this->errors[] = APIResponse\get(5006); break; diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserPrivilegeCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserPrivilegeCreate.inc index 76ec19af0..0e2eb140d 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserPrivilegeCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserPrivilegeCreate.inc @@ -25,42 +25,68 @@ class APIUserPrivilegeCreate extends APIModel { } public function action() { - local_user_set($this->validated_data["user_config"]); // Set user backend parameters + $this->config["system"]["user"][$this->id]["priv"] = $this->validated_data["priv"]; $this->write_config(); - return APIResponse\get(0, $this->validated_data["user_config"]["priv"]); + local_user_set($this->validated_data); + return APIResponse\get(0, $this->validated_data["priv"]); } public function validate_payload() { - global $priv_list; + $this->__validate_username(); + $this->__validate_priv(); + } + + private function __validate_username() { + # Check for our required `username` payload value if (isset($this->initial_data['username'])) { - $this->validated_data["username"] = trim($this->initial_data['username']); - $this->validated_data["user_config"] =& getUserEntry($this->validated_data["username"]); - if (!array_key_exists("uid", $this->validated_data["user_config"])) { + # Loop through each configured user and check if this user exists + foreach ($this->config["system"]["user"] as $id=>$user) { + if ($this->initial_data["username"] === $user["name"]) { + $this->validated_data = $user; + $this->id = intval($id); + } + } + # Set an error if no user was found + if (!isset($this->validated_data["uid"])) { $this->errors[] = APIResponse\get(5001); } } else { $this->errors[] = APIResponse\get(5000); } - if (isset($this->initial_data['priv'])) { - // Ensure our new priv is array, if it is a string create an array containing the string - if (is_string($this->initial_data["priv"])) { + } + + private function __validate_priv() { + global $priv_list; + $this->__init_config(); + + # Check for our optional `priv` payload value + if ($this->initial_data["priv"]) { + # Ensure value is an array + if (!is_array($this->initial_data["priv"])) { $this->initial_data["priv"] = array($this->initial_data["priv"]); } - if (is_array($this->initial_data["priv"])) { - // Loop through our new priv list and check that the privs are valid - foreach ($this->initial_data["priv"] as $np) { - if (!array_key_exists($np, $priv_list)) { - $this->errors[] = APIResponse\get(5006); - } - if (!in_array($np, $this->validated_data["user_config"]["priv"])) { - $this->validated_data["user_config"]["priv"][] = $np; - } + + # Loop through each requested privilege and ensure it exists + foreach ($this->initial_data["priv"] as $priv) { + if (array_key_exists($priv, $priv_list)) { + $this->validated_data["priv"][] = $priv; + $this->validated_data["priv"] = array_unique($this->validated_data["priv"]); + } else { + $this->errors[] = APIResponse\get(5006); + break; } - } else { - $this->errors[] = APIResponse\get(5005); } - } else { - $this->errors[] = APIResponse\get(5004); + } + } + + private function __init_config() { + # Initialize the priv array if the user does not already have one + if (empty($this->validated_data["priv"])) { + $this->validated_data["priv"] = []; + } + # If the user has a priv set, but as a string, convert it to an array + elseif (is_string($this->validated_data["priv"])) { + $this->validated_data["priv"] = array($this->validated_data["priv"]); } } } \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserPrivilegeDelete.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserPrivilegeDelete.inc index ee94b63e6..8d6a4c319 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserPrivilegeDelete.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserPrivilegeDelete.inc @@ -23,50 +23,52 @@ class APIUserPrivilegeDelete extends APIModel { $this->privileges = ["page-all", "page-system-usermanager-addprivs"]; $this->change_note = "Deleted privileges for user via API"; } - public function action() { - $user_config =& getUserEntry($this->validated_data["username"]); - $user_id = index_users()[$this->validated_data["username"]]; // Save our user's array index ID - local_user_set($user_config); // Set user backend parameters - $this->config["system"]["user"][$user_id] = $user_config; // Add our new config - $this->write_config(); // Write to config + $this->config["system"]["user"][$this->id]["priv"] = $this->validated_data["priv"]; + $this->write_config(); + local_user_set($this->validated_data); return APIResponse\get(0, $this->validated_data["priv"]); } public function validate_payload() { - global $priv_list; + $this->__validate_username(); + $this->__validate_priv(); + } + + private function __validate_username() { + # Check for our required `username` payload value if (isset($this->initial_data['username'])) { - $this->validated_data["username"] = $this->initial_data['username']; - $this->validated_data["username"] = trim($this->validated_data["username"]); + # Loop through each configured user and check if this user exists + foreach ($this->config["system"]["user"] as $id=>$user) { + if ($this->initial_data["username"] === $user["name"]) { + $this->validated_data = $user; + $this->id = intval($id); + } + } + # Set an error if no user was found + if (!isset($this->validated_data["uid"])) { + $this->errors[] = APIResponse\get(5001); + } } else { $this->errors[] = APIResponse\get(5000); } - if (isset($this->initial_data['priv'])) { - $this->validated_data["priv"] = $this->initial_data['priv']; - } else { - $this->errors[] = APIResponse\get(5004); - } - // Check if our user already exists, if so exit on non-zero - $user_config =& getUserEntry($this->validated_data["username"]); - if (!array_key_exists("uid", $user_config)) { - $this->errors[] = APIResponse\get(5002); - } - // Ensure our new priv is array, if it is a string create an array containing the string - if (is_string($this->validated_data["priv"])) { - $this->validated_data["priv"] = array($this->validated_data["priv"]); - } - if (is_array($this->validated_data["priv"])) { - // Loop through our new priv list and check that the privs are valid - foreach ($this->validated_data["priv"] as $dp) { - if (!array_key_exists($dp, $priv_list)) { - $this->errors[] = APIResponse\get(5006); - } - if (in_array($dp, $user_config["priv"])) { - $user_config["priv"] = \array_diff($user_config["priv"], array($dp)); + } + + private function __validate_priv() { + # Check for our optional `priv` payload value + if ($this->initial_data["priv"]) { + # Ensure value is an array + if (!is_array($this->initial_data["priv"])) { + $this->initial_data["priv"] = array($this->initial_data["priv"]); + } + + # Loop through each of the user's stored privileges and remove it if matched + foreach ($this->validated_data["priv"] as $id=>$priv) { + # Check if this privilege is one that is being requested to remove + if (in_array($priv, $this->initial_data["priv"])) { + unset($this->validated_data["priv"][$id]); } } - } else { - $this->errors[] = APIResponse\get(5005); } } } \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserUpdate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserUpdate.inc index c749aad6a..c12bc2572 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserUpdate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserUpdate.inc @@ -76,6 +76,7 @@ class APIUserUpdate extends APIModel { foreach ($this->initial_data["priv"] as $priv) { if (array_key_exists($priv, $priv_list)) { $this->validated_data["priv"][] = $priv; + $this->validated_data["priv"] = array_unique($this->validated_data["priv"]); } else { $this->errors[] = APIResponse\get(5006); break; From 5755860b83473e0c1253f3550d96c5b7810511e1 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 12:46:40 -0700 Subject: [PATCH 08/10] Restructured APIUserDelete model to fix bug that allowed system users to be deleted --- .../etc/inc/api/framework/APIResponse.inc | 2 +- .../etc/inc/api/models/APIUserDelete.inc | 37 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc index 81b5084d2..1e7b5f23f 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc @@ -1598,7 +1598,7 @@ function get($id, $data=[], $all=false) { "status" => "bad request", "code" => 400, "return" => $id, - "message" => "User privilege must be type array or string" + "message" => "System users cannot be deleted" ], 5006 => [ "status" => "bad request", diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIUserDelete.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIUserDelete.inc index e38bdb178..e5d9e634f 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIUserDelete.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIUserDelete.inc @@ -25,26 +25,37 @@ class APIUserDelete extends APIModel { } public function action() { - $index_id = index_users()[$this->validated_data["username"]]; // Save our user's index ID number - $del_user = $this->config["system"]["user"][$index_id]; - local_user_del($this->config["system"]["user"][$index_id]); // Delete our user on the backend - unset($this->config['system']['user'][$index_id]); // Unset our user from config - $this->config['system']['user'] = array_values($this->config['system']['user']); // Reindex our users - $this->write_config(); // Write our new config - return APIResponse\get(0, $del_user); + # Remove user from backend and remove from config + local_user_del($this->config["system"]["user"][$this->id]); + unset($this->config["system"]["user"][$this->id]); + $this->write_config(); + return APIResponse\get(0, $this->validated_data); } - public function validate_payload() { - if (isset($this->initial_data["username"])) { - if (!array_key_exists($this->initial_data["username"], index_users())) { + private function __validate_username() { + # Check for our required `username` payload value + if (isset($this->initial_data['username'])) { + # Loop through each configured user and check if this user exists + foreach ($this->config["system"]["user"] as $id=>$user) { + if ($this->initial_data["username"] === $user["name"]) { + $this->validated_data = $user; + $this->id = intval($id); + } + } + # Set an error if no user was found + if (!isset($this->validated_data["uid"])) { $this->errors[] = APIResponse\get(5001); - } else { - $this->validated_data["username"] = $this->initial_data['username']; - $this->validated_data["username"] = trim($this->validated_data["username"]); + } + # Set an error if this is a system user + if ($this->validated_data["scope"] !== "user") { + $this->errors[] = APIResponse\get(5005); } } else { $this->errors[] = APIResponse\get(5000); } + } + public function validate_payload() { + $this->__validate_username(); } } \ No newline at end of file From 095d70b2272b3fc9ef1b069e5ff9830b38355738 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson <jaredhendrickson13@gmail.com> Date: Wed, 10 Mar 2021 19:42:57 -0700 Subject: [PATCH 09/10] Bumped port version to v1.1.7, fixed update path for pfSense 2.5 on pfsense-api CLI --- .../files/usr/local/share/pfSense-pkg-API/manage.php | 3 ++- tools/templates/Makefile.j2 | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pfSense-pkg-API/files/usr/local/share/pfSense-pkg-API/manage.php b/pfSense-pkg-API/files/usr/local/share/pfSense-pkg-API/manage.php index 958833bfa..dcf55ad55 100644 --- a/pfSense-pkg-API/files/usr/local/share/pfSense-pkg-API/manage.php +++ b/pfSense-pkg-API/files/usr/local/share/pfSense-pkg-API/manage.php @@ -81,8 +81,9 @@ function restore() { } function update() { + $pf_version = substr(file_get_contents("/etc/version"), 0, 3); echo shell_exec("/usr/sbin/pkg delete -y pfSense-pkg-API"); - echo shell_exec("/usr/sbin/pkg add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-2.4-pkg-API.txz"); + echo shell_exec("/usr/sbin/pkg add https://github.com/jaredhendrickson13/pfsense-api/releases/latest/download/pfSense-".$pf_version."-pkg-API.txz"); echo shell_exec("/etc/rc.restart_webgui"); } diff --git a/tools/templates/Makefile.j2 b/tools/templates/Makefile.j2 index 3063bb984..d327d5037 100644 --- a/tools/templates/Makefile.j2 +++ b/tools/templates/Makefile.j2 @@ -2,7 +2,7 @@ PORTNAME=pfSense-pkg-API PORTVERSION=1.1 -PORTREVISION=6 +PORTREVISION=7 CATEGORIES=sysutils MASTER_SITES=# empty DISTFILES=# empty From 0f7d53b82cfe8a83239d06004f4e2032c31f3c9b Mon Sep 17 00:00:00 2001 From: Vincent Caron <vincent@zerodeux.net> Date: Thu, 11 Mar 2021 18:57:26 +0100 Subject: [PATCH 10/10] CARP vhid must be unique _for a given interface_ (or more exactly L2 segment) See https://github.com/jaredhendrickson13/pfsense-api/issues/100 --- .../etc/inc/api/models/APIFirewallVirtualIPCreate.inc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallVirtualIPCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallVirtualIPCreate.inc index 3e5ea732d..f6f53ec2f 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallVirtualIPCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallVirtualIPCreate.inc @@ -105,7 +105,7 @@ class APIFirewallVirtualIPCreate extends APIModel { if ($this->validated_data["mode"] === "carp") { # Check for our optional 'vhid' payload value. Assume default if none was specified. if (isset($this->initial_data['vhid'])) { - if ($this->__vhid_exists($this->initial_data['vhid'])) { + if ($this->__vhid_exists($this->initial_data["interface"], $this->initial_data['vhid'])) { $this->errors[] = APIResponse\get(4027); } elseif (1 > $this->initial_data['vhid'] or $this->initial_data['vhid'] > 255) { $this->errors[] = APIResponse\get(4028); @@ -153,14 +153,14 @@ class APIFirewallVirtualIPCreate extends APIModel { $this->validated_data["type"] = "network"; } - private function __vhid_exists($vhid) { + private function __vhid_exists($interface, $vhid) { # Loop through each virtual IP and ensure it is not using the requested vhid foreach ($this->config["virtualip"]["vip"] as $vip) { - if (intval($vhid) === intval($vip["vhid"])) { + if ($interface === $vip["interface"] && intval($vhid) === intval($vip["vhid"])) { return true; } } return false; } -} \ No newline at end of file +}