From fb25c4395a2a9cd6e16f0dc900ad6f99f541c26c Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Fri, 14 Dec 2018 20:34:04 -0700 Subject: [PATCH] Make better API system --- api.php | 437 +---------------------------- api/.htaccess | 5 + api/actions/acctstatus.php | 9 + api/actions/addnotification.php | 29 ++ api/actions/alertemail.php | 18 ++ api/actions/auth.php | 30 ++ api/actions/checkpin.php | 24 ++ api/actions/codelogin.php | 15 + api/actions/deletenotification.php | 20 ++ api/actions/getgroups.php | 10 + api/actions/getgroupsbyuser.php | 23 ++ api/actions/getmanaged.php | 23 ++ api/actions/getmanagers.php | 19 ++ api/actions/getnotifications.php | 20 ++ api/actions/getusersbygroup.php | 29 ++ api/actions/groupsearch.php | 13 + api/actions/hastotp.php | 9 + api/actions/ismanagerof.php | 27 ++ api/actions/listapps.php | 16 ++ api/actions/login.php | 36 +++ api/actions/mobileenabled.php | 9 + api/actions/mobilevalid.php | 15 + api/actions/permission.php | 19 ++ api/actions/ping.php | 9 + api/actions/readnotification.php | 25 ++ api/actions/userexists.php | 15 + api/actions/userinfo.php | 20 ++ api/actions/usersearch.php | 13 + api/actions/verifytotp.php | 15 + api/apisettings.php | 215 ++++++++++++++ api/functions.php | 123 ++++++++ api/index.php | 77 +++++ 32 files changed, 931 insertions(+), 436 deletions(-) create mode 100644 api/.htaccess create mode 100644 api/actions/acctstatus.php create mode 100644 api/actions/addnotification.php create mode 100644 api/actions/alertemail.php create mode 100644 api/actions/auth.php create mode 100644 api/actions/checkpin.php create mode 100644 api/actions/codelogin.php create mode 100644 api/actions/deletenotification.php create mode 100644 api/actions/getgroups.php create mode 100644 api/actions/getgroupsbyuser.php create mode 100644 api/actions/getmanaged.php create mode 100644 api/actions/getmanagers.php create mode 100644 api/actions/getnotifications.php create mode 100644 api/actions/getusersbygroup.php create mode 100644 api/actions/groupsearch.php create mode 100644 api/actions/hastotp.php create mode 100644 api/actions/ismanagerof.php create mode 100644 api/actions/listapps.php create mode 100644 api/actions/login.php create mode 100644 api/actions/mobileenabled.php create mode 100644 api/actions/mobilevalid.php create mode 100644 api/actions/permission.php create mode 100644 api/actions/ping.php create mode 100644 api/actions/readnotification.php create mode 100644 api/actions/userexists.php create mode 100644 api/actions/userinfo.php create mode 100644 api/actions/usersearch.php create mode 100644 api/actions/verifytotp.php create mode 100644 api/apisettings.php create mode 100644 api/functions.php create mode 100644 api/index.php diff --git a/api.php b/api.php index c8784ad..b45877d 100644 --- a/api.php +++ b/api.php @@ -4,439 +4,4 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/** - * Simple JSON API to allow other apps to access accounts in this system. - * - * Requests can be sent via either GET or POST requests. POST is recommended - * as it has a lower chance of being logged on the server, exposing unencrypted - * user passwords. - */ -require __DIR__ . '/required.php'; -header("Content-Type: application/json"); - - -if (empty($VARS['key'])) { - die("\"403 Unauthorized\""); -} else { - $key = $VARS['key']; - if ($database->has('apikeys', ['key' => $key]) !== TRUE) { - engageRateLimit(); - http_response_code(403); - Log::insert(LogType::API_BAD_KEY, null, "Key: " . $key); - die("\"403 Unauthorized\""); - } -} - -/** - * Get the API key with most of the characters replaced with *s. - * @global string $key - * @return string - */ -function getCensoredKey() { - global $key; - $resp = $key; - if (strlen($key) > 5) { - for ($i = 2; $i < strlen($key) - 2; $i++) { - $resp[$i] = "*"; - } - } - return $resp; -} - -if (empty($VARS['action'])) { - http_response_code(404); - die(json_encode("No action specified.")); -} - -switch ($VARS['action']) { - case "ping": - exit(json_encode(["status" => "OK"])); - break; - case "auth": - $user = User::byUsername($VARS['username']); - if ($user->checkPassword($VARS['password'])) { - Log::insert(LogType::API_AUTH_OK, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "OK", "msg" => $Strings->get("login successful", false)])); - } else { - Log::insert(LogType::API_AUTH_FAILED, $user->getUID(), "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - if ($user->exists()) { - switch ($user->getStatus()->get()) { - case AccountStatus::LOCKED_OR_DISABLED: - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account locked", false)])); - case AccountStatus::TERMINATED: - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account terminated", false)])); - case AccountStatus::CHANGE_PASSWORD: - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("password expired", false)])); - case AccountStatus::NORMAL: - break; - default: - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account state error", false)])); - } - } - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)])); - } - break; - case "userinfo": - if (!empty($VARS['username'])) { - $user = User::byUsername($VARS['username']); - } else if (!empty($VARS['uid']) && is_numeric($VARS['uid'])) { - $user = new User($VARS['uid']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - if ($user->exists()) { - $data = $database->get("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"], 'pin'], ["uid" => $user->getUID()]); - $data['pin'] = (is_null($data['pin']) || $data['pin'] == "" ? false : true); - exit(json_encode(["status" => "OK", "data" => $data])); - } else { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)])); - } - break; - case "userexists": - if (!empty($VARS['uid']) && is_numeric($VARS['uid'])) { - $user = new User($VARS['uid']); - } else if (!empty($VARS['username'])) { - $user = User::byUsername($VARS['username']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - - exit(json_encode(["status" => "OK", "exists" => $user->exists()])); - break; - case "hastotp": - exit(json_encode(["status" => "OK", "otp" => User::byUsername($VARS['username'])->has2fa()])); - break; - case "verifytotp": - $user = User::byUsername($VARS['username']); - if ($user->check2fa($VARS['code'])) { - exit(json_encode(["status" => "OK", "valid" => true])); - } else { - Log::insert(LogType::API_BAD_2FA, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("2fa incorrect", false), "valid" => false])); - } - break; - case "acctstatus": - exit(json_encode(["status" => "OK", "account" => User::byUsername($VARS['username'])->getStatus()->getString()])); - case "login": - // simulate a login, checking account status and alerts - engageRateLimit(); - $user = User::byUsername($VARS['username']); - if ($user->checkPassword($VARS['password'])) { - switch ($user->getStatus()->getString()) { - case "LOCKED_OR_DISABLED": - Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account locked", false)])); - case "TERMINATED": - Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account terminated", false)])); - case "CHANGE_PASSWORD": - Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("password expired", false)])); - case "NORMAL": - Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "OK"])); - case "ALERT_ON_ACCESS": - $user->sendAlertEmail(); - Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "OK", "alert" => true])); - default: - Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account state error", false)])); - } - } else { - Log::insert(LogType::API_LOGIN_FAILED, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)])); - } - break; - case "ismanagerof": - if ($VARS['uid'] == "1") { - $manager = new User($VARS['manager']); - $employee = new User($VARS['employee']); - } else { - $manager = User::byUsername($VARS['manager']); - $employee = User::byUsername($VARS['employee']); - } - if (!$manager->exists()) { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['manager']])); - } - if (!$employee->exists()) { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['employee']])); - } - - if ($database->has('managers', ['AND' => ['managerid' => $manager->getUID(), 'employeeid' => $employee->getUID()]])) { - exit(json_encode(["status" => "OK", "managerof" => true])); - } else { - exit(json_encode(["status" => "OK", "managerof" => false])); - } - break; - case "getmanaged": - if (!empty($VARS['uid'])) { - $manager = new User($VARS['uid']); - } else if (!empty($VARS['username'])) { - $manager = User::byUsername($VARS['username']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - if (!$manager->exists()) { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)])); - } - if ($VARS['get'] == "username") { - $managed = $database->select('managers', ['[>]accounts' => ['employeeid' => 'uid']], 'username', ['managerid' => $manager->getUID()]); - } else { - $managed = $database->select('managers', 'employeeid', ['managerid' => $manager->getUID()]); - } - exit(json_encode(["status" => "OK", "employees" => $managed])); - break; - case "getmanagers": - if (!empty($VARS['uid'])) { - $emp = new User($VARS['uid']); - } else if (!empty($VARS['username'])) { - $emp = User::byUsername($VARS['username']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - if (!$emp->exists()) { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)])); - } - $managers = $database->select('managers', 'managerid', ['employeeid' => $emp->getUID()]); - exit(json_encode(["status" => "OK", "managers" => $managers])); - break; - case "usersearch": - if (empty($VARS['search']) || strlen($VARS['search']) < 3) { - exit(json_encode(["status" => "OK", "result" => []])); - } - $data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['search'], 'realname[~]' => $VARS['search']], "LIMIT" => 10]); - exit(json_encode(["status" => "OK", "result" => $data])); - break; - case "permission": - if (empty($VARS['code'])) { - http_response_code(400); - die("\"400 Bad Request\""); - } - $perm = $VARS['code']; - if (!empty($VARS['uid'])) { - $user = new User($VARS['uid']); - } else if (!empty($VARS['username'])) { - $user = User::byUsername($VARS['username']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - if (!$user->exists()) { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)])); - } - exit(json_encode(["status" => "OK", "has_permission" => $user->hasPermission($perm)])); - break; - case "mobileenabled": - exit(json_encode(["status" => "OK", "mobile" => MOBILE_ENABLED])); - case "mobilevalid": - if (empty($VARS['username']) || empty($VARS['code'])) { - http_response_code(400); - die("\"400 Bad Request\""); - } - $code = strtoupper($VARS['code']); - $user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $code, 'accounts.username' => strtolower($VARS['username'])]]); - exit(json_encode(["status" => "OK", "valid" => $user_key_valid])); - case "alertemail": - engageRateLimit(); - if (empty($VARS['username']) || !User::byUsername($VARS['username'])->exists()) { - http_response_code(400); - die("\"400 Bad Request\""); - } - $appname = "???"; - if (!empty($VARS['appname'])) { - $appname = $VARS['appname']; - } - $result = User::byUsername($VARS['username'])->sendAlertEmail($appname); - if ($result === TRUE) { - exit(json_encode(["status" => "OK"])); - } - exit(json_encode(["status" => "ERROR", "msg" => $result])); - case "codelogin": - $database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup - if ($database->has("onetimekeys", ["key" => $VARS['code'], "expires[>]" => date("Y-m-d H:i:s")])) { - $user = $database->get("onetimekeys", ["[>]accounts" => ["uid" => "uid"]], ["username", "realname", "accounts.uid"], ["key" => $VARS['code']]); - exit(json_encode(["status" => "OK", "user" => $user])); - } else { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("no such code or code expired", false)])); - } - case "listapps": - $apps = EXTERNAL_APPS; - // Format paths as absolute URLs - foreach ($apps as $k => $v) { - if (strpos($apps[$k]['url'], "http") === FALSE) { - $apps[$k]['url'] = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443 ? ":" . $_SERVER['SERVER_PORT'] : "") . $apps[$k]['url']; - } - } - exit(json_encode(["status" => "OK", "apps" => $apps])); - case "getusersbygroup": - if ($VARS['gid']) { - if ($database->has("groups", ['groupid' => $VARS['gid']])) { - $groupid = $VARS['gid']; - } else { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("group does not exist", false)])); - } - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - if ($VARS['get'] == "username") { - $users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], 'username', ['groupid' => $groupid, "ORDER" => "username"]); - } else if ($VARS['get'] == "detail") { - $users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], ['username', 'realname (name)', 'accounts.uid', 'pin'], ['groupid' => $groupid, "ORDER" => "realname"]); - for ($i = 0; $i < count($users); $i++) { - if (is_null($users[$i]['pin']) || $users[$i]['pin'] == "") { - $users[$i]['pin'] = false; - } else { - $users[$i]['pin'] = true; - } - } - } else { - $users = $database->select('assigned_groups', 'uid', ['groupid' => $groupid]); - } - exit(json_encode(["status" => "OK", "users" => $users])); - break; - case "getgroupsbyuser": - if ($VARS['uid']) { - if ($database->has("accounts", ['uid' => $VARS['uid']])) { - $empid = $VARS['uid']; - } else { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)])); - } - } else if ($VARS['username']) { - if ($database->has("accounts", ['username' => strtolower($VARS['username'])])) { - $empid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['username'])]); - } else { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)])); - } - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - $groups = $database->select('assigned_groups', ["[>]groups" => ["groupid" => "groupid"]], ['groups.groupid (id)', 'groups.groupname (name)'], ['uid' => $empid]); - exit(json_encode(["status" => "OK", "groups" => $groups])); - break; - case "getgroups": - $groups = $database->select('groups', ['groupid (id)', 'groupname (name)']); - exit(json_encode(["status" => "OK", "groups" => $groups])); - break; - case "groupsearch": - if (empty($VARS['search']) || strlen($VARS['search']) < 2) { - exit(json_encode(["status" => "OK", "result" => []])); - } - $data = $database->select('groups', ['groupid (id)', 'groupname (name)'], ['groupname[~]' => $VARS['search'], "LIMIT" => 10]); - exit(json_encode(["status" => "OK", "result" => $data])); - break; - case "checkpin": - $pin = ""; - if (empty($VARS['pin'])) { - http_response_code(400); - die("\"400 Bad Request\""); - } - if (!empty($VARS['username'])) { - $user = User::byUsername($VARS['username']); - } else if (!empty($VARS['uid'])) { - $user = new User($VARS['uid']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - if ($user->exists()) { - $pin = $database->get("accounts", "pin", ["uid" => $user->getUID()]); - } else { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)])); - } - if (is_null($pin) || $pin == "") { - exit(json_encode(["status" => "ERROR", "pinvalid" => false, "nopinset" => true])); - } - exit(json_encode(["status" => "OK", "pinvalid" => ($pin == $VARS['pin'])])); - break; - case "getnotifications": - if (!empty($VARS['username'])) { - $user = User::byUsername($VARS['username']); - } else if (!empty($VARS['uid'])) { - $user = new User($VARS['uid']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - try { - $notifications = Notifications::get($user); - exit(json_encode(["status" => "OK", "notifications" => $notifications])); - } catch (Exception $ex) { - exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()])); - } - break; - case "readnotification": - if (!empty($VARS['username'])) { - $user = User::byUsername($VARS['username']); - } else if (!empty($VARS['uid'])) { - $user = new User($VARS['uid']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - if (empty($VARS['id'])) { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)])); - } - try { - Notifications::read($user, $VARS['id']); - exit(json_encode(["status" => "OK"])); - } catch (Exception $ex) { - exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()])); - } - break; - case "addnotification": - if (!empty($VARS['username'])) { - $user = User::byUsername($VARS['username']); - } else if (!empty($VARS['uid'])) { - $user = new User($VARS['uid']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - - try { - $timestamp = ""; - if (!empty($VARS['timestamp'])) { - $timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp'])); - } - $url = ""; - if (!empty($VARS['url'])) { - $url = $VARS['url']; - } - $nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive'])); - - exit(json_encode(["status" => "OK", "id" => $nid])); - } catch (Exception $ex) { - exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()])); - } - break; - case "deletenotification": - if (!empty($VARS['username'])) { - $user = User::byUsername($VARS['username']); - } else if (!empty($VARS['uid'])) { - $user = new User($VARS['uid']); - } else { - http_response_code(400); - die("\"400 Bad Request\""); - } - - if (empty($VARS['id'])) { - exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)])); - } - try { - Notifications::delete($user, $VARS['id']); - exit(json_encode(["status" => "OK"])); - } catch (Exception $ex) { - exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()])); - } - break; - default: - http_response_code(404); - die(json_encode("404 Not Found: the requested action is not available.")); -} +require __DIR__ . "/api/index.php"; \ No newline at end of file diff --git a/api/.htaccess b/api/.htaccess new file mode 100644 index 0000000..9a4efe4 --- /dev/null +++ b/api/.htaccess @@ -0,0 +1,5 @@ +# Rewrite for Nextcloud Notes API + + RewriteEngine on + RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT] + \ No newline at end of file diff --git a/api/actions/acctstatus.php b/api/actions/acctstatus.php new file mode 100644 index 0000000..8e21659 --- /dev/null +++ b/api/actions/acctstatus.php @@ -0,0 +1,9 @@ + User::byUsername($VARS['username'])->getStatus()->getString()]); \ No newline at end of file diff --git a/api/actions/addnotification.php b/api/actions/addnotification.php new file mode 100644 index 0000000..448bb9b --- /dev/null +++ b/api/actions/addnotification.php @@ -0,0 +1,29 @@ + "OK", "id" => $nid]); +} catch (Exception $ex) { + sendJsonResp($ex->getMessage(), "ERROR"); +} \ No newline at end of file diff --git a/api/actions/alertemail.php b/api/actions/alertemail.php new file mode 100644 index 0000000..f7b7cb6 --- /dev/null +++ b/api/actions/alertemail.php @@ -0,0 +1,18 @@ +sendAlertEmail($appname); +if ($result === TRUE) { + sendJsonResp(); +} +sendJsonResp($result, "ERROR"); diff --git a/api/actions/auth.php b/api/actions/auth.php new file mode 100644 index 0000000..ede643b --- /dev/null +++ b/api/actions/auth.php @@ -0,0 +1,30 @@ +checkPassword($VARS['password'])) { + Log::insert(LogType::API_AUTH_OK, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + sendJsonResp($Strings->get("login successful", false), "OK"); +} else { + Log::insert(LogType::API_AUTH_FAILED, $user->getUID(), "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + if ($user->exists()) { + switch ($user->getStatus()->get()) { + case AccountStatus::LOCKED_OR_DISABLED: + sendJsonResp($Strings->get("account locked", false), "ERROR"); + case AccountStatus::TERMINATED: + sendJsonResp($Strings->get("account terminated", false), "ERROR"); + case AccountStatus::CHANGE_PASSWORD: + sendJsonResp($Strings->get("password expired", false), "ERROR"); + case AccountStatus::NORMAL: + break; + default: + sendJsonResp($Strings->get("account state error", false), "ERROR"); + } + } + sendJsonResp($Strings->get("login incorrect", false), "ERROR"); +} \ No newline at end of file diff --git a/api/actions/checkpin.php b/api/actions/checkpin.php new file mode 100644 index 0000000..ba87750 --- /dev/null +++ b/api/actions/checkpin.php @@ -0,0 +1,24 @@ +exists()) { + $pin = $database->get("accounts", "pin", ["uid" => $user->getUID()]); +} else { + sendJsonResp($Strings->get("login incorrect", false), "ERROR"); +} +if (is_null($pin) || $pin == "") { + exitWithJson(["status" => "ERROR", "pinvalid" => false, "nopinset" => true]); +} +exitWithJson(["status" => "OK", "pinvalid" => ($pin == $VARS['pin'])]); diff --git a/api/actions/codelogin.php b/api/actions/codelogin.php new file mode 100644 index 0000000..b1f845b --- /dev/null +++ b/api/actions/codelogin.php @@ -0,0 +1,15 @@ +delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup +if ($database->has("onetimekeys", ["key" => $VARS['code'], "expires[>]" => date("Y-m-d H:i:s")])) { + $user = $database->get("onetimekeys", ["[>]accounts" => ["uid" => "uid"]], ["username", "realname", "accounts.uid"], ["key" => $VARS['code']]); + exitWithJson(["status" => "OK", "user" => $user]); +} else { + sendJsonResp($Strings->get("no such code or code expired", false), "ERROR"); +} \ No newline at end of file diff --git a/api/actions/deletenotification.php b/api/actions/deletenotification.php new file mode 100644 index 0000000..475359c --- /dev/null +++ b/api/actions/deletenotification.php @@ -0,0 +1,20 @@ +getMessage(), "ERROR"); +} \ No newline at end of file diff --git a/api/actions/getgroups.php b/api/actions/getgroups.php new file mode 100644 index 0000000..e61317d --- /dev/null +++ b/api/actions/getgroups.php @@ -0,0 +1,10 @@ +select('groups', ['groupid (id)', 'groupname (name)']); +exitWithJson(["status" => "OK", "groups" => $groups]); diff --git a/api/actions/getgroupsbyuser.php b/api/actions/getgroupsbyuser.php new file mode 100644 index 0000000..93396e4 --- /dev/null +++ b/api/actions/getgroupsbyuser.php @@ -0,0 +1,23 @@ +has("accounts", ['uid' => $VARS['uid']])) { + $empid = $VARS['uid']; + } else { + sendJsonResp($Strings->get("user does not exist", false), "ERROR"); + } +} else if (!empty($VARS['username'])) { + if ($database->has("accounts", ['username' => strtolower($VARS['username'])])) { + $empid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['username'])]); + } else { + sendJsonResp($Strings->get("user does not exist", false), "ERROR"); + } +} +$groups = $database->select('assigned_groups', ["[>]groups" => ["groupid" => "groupid"]], ['groups.groupid (id)', 'groups.groupname (name)'], ['uid' => $empid]); +exitWithJson(["status" => "OK", "groups" => $groups]); diff --git a/api/actions/getmanaged.php b/api/actions/getmanaged.php new file mode 100644 index 0000000..c7d8a0e --- /dev/null +++ b/api/actions/getmanaged.php @@ -0,0 +1,23 @@ +exists()) { + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]); +} +if (!empty($VARS['get']) && $VARS['get'] == "username") { + $managed = $database->select('managers', ['[>]accounts' => ['employeeid' => 'uid']], 'username', ['managerid' => $manager->getUID()]); +} else { + $managed = $database->select('managers', 'employeeid', ['managerid' => $manager->getUID()]); +} +exitWithJson(["status" => "OK", "employees" => $managed]); diff --git a/api/actions/getmanagers.php b/api/actions/getmanagers.php new file mode 100644 index 0000000..8b77b78 --- /dev/null +++ b/api/actions/getmanagers.php @@ -0,0 +1,19 @@ +exists()) { + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]); +} +$managers = $database->select('managers', 'managerid', ['employeeid' => $emp->getUID()]); +exitWithJson(["status" => "OK", "managers" => $managers]); diff --git a/api/actions/getnotifications.php b/api/actions/getnotifications.php new file mode 100644 index 0000000..b19e3aa --- /dev/null +++ b/api/actions/getnotifications.php @@ -0,0 +1,20 @@ + "OK", "notifications" => $notifications]); +} catch (Exception $ex) { + sendJsonResp($ex->getMessage(), "ERROR"); +} \ No newline at end of file diff --git a/api/actions/getusersbygroup.php b/api/actions/getusersbygroup.php new file mode 100644 index 0000000..77c4388 --- /dev/null +++ b/api/actions/getusersbygroup.php @@ -0,0 +1,29 @@ +has("groups", ['groupid' => $VARS['gid']])) { + $groupid = $VARS['gid']; +} else { + sendJsonResp($Strings->get("group does not exist", false), "ERROR"); +} + +if (!empty($VARS["get"]) && $VARS['get'] == "username") { + $users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], 'username', ['groupid' => $groupid, "ORDER" => "username"]); +} else if (!empty($VARS["get"]) && $VARS['get'] == "detail") { + $users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], ['username', 'realname (name)', 'accounts.uid', 'pin'], ['groupid' => $groupid, "ORDER" => "realname"]); + for ($i = 0; $i < count($users); $i++) { + if (is_null($users[$i]['pin']) || $users[$i]['pin'] == "") { + $users[$i]['pin'] = false; + } else { + $users[$i]['pin'] = true; + } + } +} else { + $users = $database->select('assigned_groups', 'uid', ['groupid' => $groupid]); +} +exitWithJson(["status" => "OK", "users" => $users]); diff --git a/api/actions/groupsearch.php b/api/actions/groupsearch.php new file mode 100644 index 0000000..c54ebd4 --- /dev/null +++ b/api/actions/groupsearch.php @@ -0,0 +1,13 @@ + "OK", "result" => []]); +} +$data = $database->select('groups', ['groupid (id)', 'groupname (name)'], ['groupname[~]' => $VARS['search'], "LIMIT" => 10]); +exitWithJson(["status" => "OK", "result" => $data]); diff --git a/api/actions/hastotp.php b/api/actions/hastotp.php new file mode 100644 index 0000000..17f2745 --- /dev/null +++ b/api/actions/hastotp.php @@ -0,0 +1,9 @@ + User::byUsername($VARS['username'])->has2fa()]); \ No newline at end of file diff --git a/api/actions/ismanagerof.php b/api/actions/ismanagerof.php new file mode 100644 index 0000000..ee6a488 --- /dev/null +++ b/api/actions/ismanagerof.php @@ -0,0 +1,27 @@ +exists()) { + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['manager']]); +} +if (!$employee->exists()) { + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['employee']]); +} + +if ($database->has('managers', ['AND' => ['managerid' => $manager->getUID(), 'employeeid' => $employee->getUID()]])) { + exitWithJson(["status" => "OK", "managerof" => true]); +} else { + exitWithJson(["status" => "OK", "managerof" => false]); +} \ No newline at end of file diff --git a/api/actions/listapps.php b/api/actions/listapps.php new file mode 100644 index 0000000..2947fb3 --- /dev/null +++ b/api/actions/listapps.php @@ -0,0 +1,16 @@ + $v) { + if (strpos($apps[$k]['url'], "http") === FALSE) { + $apps[$k]['url'] = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443 ? ":" . $_SERVER['SERVER_PORT'] : "") . $apps[$k]['url']; + } +} +exitWithJson(["status" => "OK", "apps" => $apps]); diff --git a/api/actions/login.php b/api/actions/login.php new file mode 100644 index 0000000..6372b16 --- /dev/null +++ b/api/actions/login.php @@ -0,0 +1,36 @@ +checkPassword($VARS['password'])) { + switch ($user->getStatus()->getString()) { + case "LOCKED_OR_DISABLED": + Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account locked", false)]); + case "TERMINATED": + Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account terminated", false)]); + case "CHANGE_PASSWORD": + Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("password expired", false)]); + case "NORMAL": + Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + exitWithJson(["status" => "OK"]); + case "ALERT_ON_ACCESS": + $user->sendAlertEmail(); + Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + exitWithJson(["status" => "OK", "alert" => true]); + default: + Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account state error", false)]); + } +} else { + Log::insert(LogType::API_LOGIN_FAILED, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]); +} \ No newline at end of file diff --git a/api/actions/mobileenabled.php b/api/actions/mobileenabled.php new file mode 100644 index 0000000..ffae16b --- /dev/null +++ b/api/actions/mobileenabled.php @@ -0,0 +1,9 @@ + "OK", "mobile" => MOBILE_ENABLED]); \ No newline at end of file diff --git a/api/actions/mobilevalid.php b/api/actions/mobilevalid.php new file mode 100644 index 0000000..1dab0f9 --- /dev/null +++ b/api/actions/mobilevalid.php @@ -0,0 +1,15 @@ +has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $code, 'accounts.username' => strtolower($VARS['username'])]]); +exitWithJson(["status" => "OK", "valid" => $user_key_valid]); diff --git a/api/actions/permission.php b/api/actions/permission.php new file mode 100644 index 0000000..7c9b97e --- /dev/null +++ b/api/actions/permission.php @@ -0,0 +1,19 @@ +exists()) { + exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]); +} +exitWithJson(["status" => "OK", "has_permission" => $user->hasPermission($perm)]); diff --git a/api/actions/ping.php b/api/actions/ping.php new file mode 100644 index 0000000..c764967 --- /dev/null +++ b/api/actions/ping.php @@ -0,0 +1,9 @@ + "ERROR", "msg" => $Strings->get("invalid parameters", false)])); +} +try { + Notifications::read($user, $VARS['id']); + sendJsonResp(); +} catch (Exception $ex) { + sendJsonResp($ex->getMessage(), "ERROR"); +} \ No newline at end of file diff --git a/api/actions/userexists.php b/api/actions/userexists.php new file mode 100644 index 0000000..f98a635 --- /dev/null +++ b/api/actions/userexists.php @@ -0,0 +1,15 @@ + $user->exists()]); diff --git a/api/actions/userinfo.php b/api/actions/userinfo.php new file mode 100644 index 0000000..d92a01a --- /dev/null +++ b/api/actions/userinfo.php @@ -0,0 +1,20 @@ +exists()) { + $data = $database->get("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"], 'pin'], ["uid" => $user->getUID()]); + $data['pin'] = (is_null($data['pin']) || $data['pin'] == "" ? false : true); + sendJsonResp(null, "OK", ["data" => $data]); +} else { + sendJsonResp($Strings->get("login incorrect", false), "ERROR"); +} \ No newline at end of file diff --git a/api/actions/usersearch.php b/api/actions/usersearch.php new file mode 100644 index 0000000..c52e110 --- /dev/null +++ b/api/actions/usersearch.php @@ -0,0 +1,13 @@ + "OK", "result" => []]); +} +$data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['search'], 'realname[~]' => $VARS['search']], "LIMIT" => 10]); +exitWithJson(["status" => "OK", "result" => $data]); diff --git a/api/actions/verifytotp.php b/api/actions/verifytotp.php new file mode 100644 index 0000000..00d98a5 --- /dev/null +++ b/api/actions/verifytotp.php @@ -0,0 +1,15 @@ +check2fa($VARS['code'])) { + sendJsonResp(null, "OK", ["valid" => true]); +} else { + Log::insert(LogType::API_BAD_2FA, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey()); + sendJsonResp($Strings->get("2fa incorrect", false), "ERROR", ["valid" => false]); +} \ No newline at end of file diff --git a/api/apisettings.php b/api/apisettings.php new file mode 100644 index 0000000..b8b8222 --- /dev/null +++ b/api/apisettings.php @@ -0,0 +1,215 @@ + [ + "load" => "ping.php", + "vars" => [ + ], + "permission" => [ + ] + ], + "auth" => [ + "load" => "auth.php", + "vars" => [ + "username" => "string", + "password" => "string" + ] + ], + "userinfo" => [ + "load" => "userinfo.php", + "vars" => [ + "OR" => [ + "username" => "string", + "uid" => "numeric" + ] + ] + ], + "userexists" => [ + "load" => "userexists.php", + "vars" => [ + "OR" => [ + "username" => "string", + "uid" => "numeric" + ] + ] + ], + "hastotp" => [ + "load" => "hastotp.php", + "vars" => [ + "username" => "string" + ] + ], + "verifytotp" => [ + "load" => "verifytotp.php", + "vars" => [ + "username" => "string", + "code" => "string" + ] + ], + "acctstatus" => [ + "load" => "acctstatus.php", + "vars" => [ + "username" => "string" + ] + ], + "login" => [ + "load" => "login.php", + "vars" => [ + "username" => "string", + "password" => "string" + ] + ], + "ismanagerof" => [ + "load" => "ismanagerof.php", + "vars" => [ + "manager" => "string", + "employee" => "string", + "uid (optional)" => "numeric" + ] + ], + "getmanaged" => [ + "load" => "getmanaged.php", + "vars" => [ + "OR" => [ + "username" => "string", + "uid" => "numeric" + ], + "get (optional)" => "string" + ] + ], + "getmanagers" => [ + "load" => "getmanagers.php", + "vars" => [ + "OR" => [ + "username" => "string", + "uid" => "numeric" + ] + ] + ], + "usersearch" => [ + "load" => "usersearch.php", + "vars" => [ + "search" => "string" + ] + ], + "permission" => [ + "load" => "permission.php", + "vars" => [ + "OR" => [ + "username" => "string", + "uid" => "numeric" + ], + "code" => "string" + ] + ], + "mobileenabled" => [ + "load" => "mobileenabled.php" + ], + "mobilevalid" => [ + "load" => "mobilevalid.php", + "vars" => [ + "username" => "string", + "code" => "string" + ] + ], + "alertemail" => [ + "load" => "alertemail.php", + "vars" => [ + "username" => "string", + "appname (optional)" => "string" + ] + ], + "codelogin" => [ + "load" => "codelogin.php", + "vars" => [ + "code" => "string" + ] + ], + "listapps" => [ + "load" => "listapps.php" + ], + "getusersbygroup" => [ + "load" => "getusersbygroup.php", + "vars" => [ + "gid" => "numeric", + "get (optional)" => "string" + ] + ], + "getgroupsbyuser" => [ + "load" => "getgroupsbyuser.php", + "vars" => [ + "OR" => [ + "uid" => "numeric", + "username" => "string" + ] + ] + ], + "getgroups" => [ + "load" => "getgroups.php" + ], + "groupsearch" => [ + "load" => "groupsearch.php", + "vars" => [ + "search" => "string" + ] + ], + "checkpin" => [ + "load" => "checkpin.php", + "vars" => [ + "pin" => "string", + "OR" => [ + "uid" => "numeric", + "username" => "string" + ] + ] + ], + "getnotifications" => [ + "load" => "getnotifications.php", + "vars" => [ + "OR" => [ + "uid" => "numeric", + "username" => "string" + ] + ] + ], + "readnotification" => [ + "load" => "readnotification.php", + "vars" => [ + "OR" => [ + "uid" => "numeric", + "username" => "string" + ], + "id" => "numeric" + ] + ], + "addnotification" => [ + "load" => "addnotification.php", + "vars" => [ + "OR" => [ + "uid" => "numeric", + "username" => "string" + ], + "title" => "string", + "content" => "string", + "timestamp (optional)" => "string", + "url (optional)" => "string", + "sensitive (optional)" => "string" + ] + ], + "deletenotification" => [ + "load" => "deletenotification.php", + "vars" => [ + "OR" => [ + "uid" => "numeric", + "username" => "string" + ], + "id" => "numeric" + ] + ], +]; diff --git a/api/functions.php b/api/functions.php new file mode 100644 index 0000000..78e84c1 --- /dev/null +++ b/api/functions.php @@ -0,0 +1,123 @@ + 5) { + for ($i = 2; $i < strlen($key) - 2; $i++) { + $resp[$i] = "*"; + } + } + return $resp; +} + +/** + * Check if the request is allowed + * @global type $VARS + * @global type $database + * @return bool true if the request should continue, false if the request is bad + */ +function authenticate(): bool { + global $VARS, $database; + if (empty($VARS['key'])) { + return false; + } else { + $key = $VARS['key']; + if ($database->has('apikeys', ['key' => $key]) !== TRUE) { + engageRateLimit(); + http_response_code(403); + Log::insert(LogType::API_BAD_KEY, null, "Key: " . $key); + return false; + } + } + return true; +} + +function checkVars($vars, $or = false) { + global $VARS; + $ok = []; + foreach ($vars as $key => $val) { + if (strpos($key, "OR") === 0) { + checkVars($vars[$key], true); + continue; + } + + // Only check type of optional variables if they're set, and don't + // mark them as bad if they're not set + if (strpos($key, " (optional)") !== false) { + $key = str_replace(" (optional)", "", $key); + if (empty($VARS[$key])) { + continue; + } + } else { + if (empty($VARS[$key])) { + $ok[$key] = false; + continue; + } + } + $checkmethod = "is_$val"; + if ($checkmethod($VARS[$key]) !== true) { + $ok[$key] = false; + } else { + $ok[$key] = true; + } + } + if ($or) { + $success = false; + $bad = ""; + foreach ($ok as $k => $v) { + if ($v) { + $success = true; + break; + } else { + $bad = $k; + } + } + if (!$success) { + http_response_code(400); + die("400 Bad request: variable $bad is missing or invalid"); + } + } else { + foreach ($ok as $key => $bool) { + if (!$bool) { + http_response_code(400); + die("400 Bad request: variable $key is missing or invalid"); + } + } + } +} diff --git a/api/index.php b/api/index.php new file mode 100644 index 0000000..a930798 --- /dev/null +++ b/api/index.php @@ -0,0 +1,77 @@ + 1) { + $VARS["action"] = $route[0]; + } + if (count($route) >= 2 && strpos($route[1], "?") !== 0) { + $VARS["key"] = $route[1]; + + for ($i = 2; $i < count($route); $i++) { + $key = explode("=", $route[$i], 2)[0]; + $val = explode("=", $route[$i], 2)[1]; + $VARS[$key] = $val; + } + } + + if (strpos($route[count($route) - 1], "?") === 0) { + $morevars = explode("&", substr($route[count($route) - 1], 1)); + foreach ($morevars as $var) { + $key = explode("=", $var, 2)[0]; + $val = explode("=", $var, 2)[1]; + $VARS[$key] = $val; + } + } +} + +if (!authenticate()) { + http_response_code(403); + die("403 Unauthorized"); +} + +if (empty($VARS['action'])) { + http_response_code(404); + die("404 No action specified"); +} + +if (!isset($APIS[$VARS['action']])) { + http_response_code(404); + die("404 Action not defined"); +} + +$APIACTION = $APIS[$VARS["action"]]; + +if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) { + http_response_code(404); + die("404 Action not found"); +} + +if (!empty($APIACTION["vars"])) { + checkVars($APIACTION["vars"]); +} + +require_once __DIR__ . "/actions/" . $APIACTION["load"];