From 1271317eb951f86b73cf01275cc3e51fc8f39fc3 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Fri, 7 Sep 2018 15:03:42 -0600 Subject: [PATCH] Rewrite to use classes, aligning with AccountHub 2.0 --- api.php | 6 +- app.php | 8 +- composer.lock | 28 +-- index.php | 101 +++++---- lang/en_us.php | 35 --- langs/en/core.json | 26 +++ langs/en/titles.json | 4 + {lang => langs}/messages.php | 0 lib/Exceptions.lib.php | 13 ++ lib/IPUtils.lib.php | 135 ++++++++++++ lib/Login.lib.php | 129 +++++++++++ lib/Notifications.lib.php | 65 ++++++ lib/Session.lib.php | 19 ++ lib/Strings.lib.php | 118 ++++++++++ lib/User.lib.php | 352 ++++++++++++++++++++++++++++++ lib/iputils.php | 131 ------------ lib/login.php | 402 ----------------------------------- lib/userinfo.php | 127 ----------- mobile/index.php | 19 +- pages/404.php | 2 +- required.php | 57 +---- tests/User.test.php | 96 +++++++++ 22 files changed, 1051 insertions(+), 822 deletions(-) delete mode 100644 lang/en_us.php create mode 100644 langs/en/core.json create mode 100644 langs/en/titles.json rename {lang => langs}/messages.php (100%) create mode 100644 lib/Exceptions.lib.php create mode 100644 lib/IPUtils.lib.php create mode 100644 lib/Login.lib.php create mode 100644 lib/Notifications.lib.php create mode 100644 lib/Session.lib.php create mode 100644 lib/Strings.lib.php create mode 100644 lib/User.lib.php delete mode 100644 lib/iputils.php delete mode 100644 lib/login.php delete mode 100644 lib/userinfo.php create mode 100644 tests/User.test.php diff --git a/api.php b/api.php index f3490fb..03178ea 100644 --- a/api.php +++ b/api.php @@ -12,17 +12,15 @@ * user passwords. */ require __DIR__ . '/required.php'; -require_once __DIR__ . '/lib/login.php'; -require_once __DIR__ . '/lib/userinfo.php'; header("Content-Type: application/json"); $username = $VARS['username']; $password = $VARS['password']; -if (user_exists($username) !== true || authenticate_user($username, $password, $errmsg) !== true) { +$user = User::byUsername($username); +if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) { header("HTTP/1.1 403 Unauthorized"); die("\"403 Unauthorized\""); } -$userinfo = getUserByUsername($username); // query max results $max = 20; diff --git a/app.php b/app.php index ab06c6e..b7402dd 100644 --- a/app.php +++ b/app.php @@ -69,9 +69,9 @@ header("Link: ; rel=preload; as=script", false); if (isset($_GET['msg']) && !is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) { // optional string generation argument if (!isset($_GET['arg']) || is_empty($_GET['arg'])) { - $alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false); + $alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false); } else { - $alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false); + $alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false); } $alerttype = MESSAGES[$_GET['msg']]['type']; $alerticon = "square-o"; @@ -146,7 +146,7 @@ END; if (isset($pg['icon'])) { ?> get($pg['title']); ?> @@ -163,7 +163,7 @@ END; -   +  get("sign out") ?> diff --git a/composer.lock b/composer.lock index f6f5538..8d36028 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "577921e9d14ff39571692f88476151ee", - "content-hash": "1c8b61c5d506ae016285b99b20040cf0", + "hash": "5c7439c6e041764f2f6b0270a95ab3ae", + "content-hash": "e4e700119f47d2f68b0ed82abaf8c5c6", "packages": [ { "name": "catfan/medoo", - "version": "v1.5.3", + "version": "v1.5.7", "source": { "type": "git", "url": "https://github.com/catfan/Medoo.git", - "reference": "1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07" + "reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/catfan/Medoo/zipball/1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07", - "reference": "1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07", + "url": "https://api.github.com/repos/catfan/Medoo/zipball/8d90cba0e8ff176028847527d0ea76fe41a06ecf", + "reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf", "shasum": "" }, "require": { @@ -64,20 +64,20 @@ "sql", "sqlite" ], - "time": "2017-12-25 17:02:41" + "time": "2018-06-14 18:59:08" }, { "name": "guzzlehttp/guzzle", - "version": "6.3.0", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", - "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", "shasum": "" }, "require": { @@ -87,7 +87,7 @@ }, "require-dev": { "ext-curl": "*", - "phpunit/phpunit": "^4.0 || ^5.0", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", "psr/log": "^1.0" }, "suggest": { @@ -96,7 +96,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.2-dev" + "dev-master": "6.3-dev" } }, "autoload": { @@ -129,7 +129,7 @@ "rest", "web service" ], - "time": "2017-06-22 18:50:49" + "time": "2018-04-22 15:46:56" }, { "name": "guzzlehttp/promises", diff --git a/index.php b/index.php index 59a8242..576edf4 100644 --- a/index.php +++ b/index.php @@ -5,80 +5,91 @@ require_once __DIR__ . "/required.php"; -require_once __DIR__ . "/lib/login.php"; - // if we're logged in, we don't need to be here. if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) { header('Location: app.php'); } if (isset($_GET['permissionerror'])) { - $alert = lang("no access permission", false); + $alert = $Strings->get("no access permission", false); } /* Authenticate user */ $userpass_ok = false; $multiauth = false; -if (checkLoginServer()) { - if (!empty($VARS['progress']) && $VARS['progress'] == "1") { - if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && verifyCaptcheck($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) { - $errmsg = ""; - if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) { - switch (get_account_status($VARS['username'])) { +if (Login::checkLoginServer()) { + if (empty($VARS['progress'])) { + // Easy way to remove "undefined" warnings. + } else if ($VARS['progress'] == "1") { + if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && Login::verifyCaptcha($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) { + $autherror = ""; + $user = User::byUsername($VARS['username']); + if ($user->exists()) { + $status = $user->getStatus()->getString(); + switch ($status) { case "LOCKED_OR_DISABLED": - $alert = lang("account locked", false); + $alert = $Strings->get("account locked", false); break; case "TERMINATED": - $alert = lang("account terminated", false); + $alert = $Strings->get("account terminated", false); break; case "CHANGE_PASSWORD": - $alert = lang("password expired", false); + $alert = $Strings->get("password expired", false); + break; case "NORMAL": - $userpass_ok = true; + $username_ok = true; break; case "ALERT_ON_ACCESS": - sendLoginAlertEmail($VARS['username']); - $userpass_ok = true; + $mail_resp = $user->sendAlertEmail(); + if (DEBUG) { + var_dump($mail_resp); + } + $username_ok = true; + break; + default: + if (!is_empty($error)) { + $alert = $error; + } else { + $alert = $Strings->get("login error", false); + } break; } - if ($userpass_ok) { - $_SESSION['passok'] = true; // stop logins using only username and authcode - if (userHasTOTP($VARS['username'])) { - $multiauth = true; + if ($username_ok) { + if ($user->checkPassword($VARS['password'])) { + $_SESSION['passok'] = true; // stop logins using only username and authcode + if ($user->has2fa()) { + $multiauth = true; + } else { + Session::start($user); + header('Location: app.php'); + die("Logged in, go to app.php"); + } } else { - doLoginUser($VARS['username'], $VARS['password']); - header('Location: app.php'); - die("Logged in, go to app.php"); + $alert = $Strings->get("login incorrect", false); } } - } else { - if (!is_empty($errmsg)) { - $alert = lang2("login server error", ['arg' => $errmsg], false); - } else { - $alert = lang("login incorrect", false); - } + } else { // User does not exist anywhere + $alert = $Strings->get("login incorrect", false); } } else { - $alert = lang("captcha error", false); + $alert = $Strings->get("captcha error", false); } - } else if (!empty($VARS['progress']) && $VARS['progress'] == "2") { + } else if ($VARS['progress'] == "2") { + $user = User::byUsername($VARS['username']); if ($_SESSION['passok'] !== true) { // stop logins using only username and authcode sendError("Password integrity check failed!"); } - if (verifyTOTP($VARS['username'], $VARS['authcode'])) { - if (doLoginUser($VARS['username'])) { - header('Location: app.php'); - die("Logged in, go to app.php"); - } else { - $alert = lang("login server user data error", false); - } + if ($user->check2fa($VARS['authcode'])) { + Session::start($user); + header('Location: app.php'); + die("Logged in, go to app.php"); } else { - $alert = lang("2fa incorrect", false); + $alert = $Strings->get("2fa incorrect", false); } } } else { - $alert = lang("login server unavailable", false); + $alert = $Strings->get("login server unavailable", false); } header("Link: ; rel=preload; as=style", false); header("Link: ; rel=preload; as=style", false); @@ -114,7 +125,7 @@ header("Link: ; rel=preload; as=script", false);
-
+
get("sign in"); ?>
; rel=preload; as=script", false); if ($multiauth != true) { ?> - " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus />
- " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
+ " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus />
+ " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />

@@ -138,16 +149,16 @@ header("Link: ; rel=preload; as=script", false); } else if ($multiauth) { ?>
- + get("2fa prompt"); ?>
- " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus />
+ " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus />
diff --git a/lang/en_us.php b/lang/en_us.php deleted file mode 100644 index f87c6a1..0000000 --- a/lang/en_us.php +++ /dev/null @@ -1,35 +0,0 @@ - "Sign In", - "username" => "Username", - "password" => "Password", - "continue" => "Continue", - "authcode" => "Authentication code", - "2fa prompt" => "Enter the six-digit code from your mobile authenticator app.", - "2fa incorrect" => "Authentication code incorrect.", - "login incorrect" => "Login incorrect.", - "login server unavailable" => "Login server unavailable. Try again later or contact technical support.", - "account locked" => "This account has been disabled. Contact technical support.", - "password expired" => "You must change your password before continuing.", - "account terminated" => "Account terminated. Access denied.", - "account state error" => "Your account state is not stable. Log out, restart your browser, and try again.", - "welcome user" => "Welcome, {user}!", - "sign out" => "Sign out", - "settings" => "Settings", - "options" => "Options", - "404 error" => "404 Error", - "page not found" => "Page not found.", - "invalid parameters" => "Invalid request parameters.", - "login server error" => "The login server returned an error: {arg}", - "login server user data error" => "The login server refused to provide account information. Try again or contact technical support.", - "captcha error" => "There was a problem with the CAPTCHA (robot test). Try again.", - "no access permission" => "You do not have permission to access this system.", - "home" => "Home", - "more" => "More", - "test" => "Test" -]); \ No newline at end of file diff --git a/langs/en/core.json b/langs/en/core.json new file mode 100644 index 0000000..5e55996 --- /dev/null +++ b/langs/en/core.json @@ -0,0 +1,26 @@ +{ + "sign in": "Sign In", + "username": "Username", + "password": "Password", + "continue": "Continue", + "authcode": "Authentication code", + "2fa prompt": "Enter the six-digit code from your mobile authenticator app.", + "2fa incorrect": "Authentication code incorrect.", + "login incorrect": "Login incorrect.", + "login server unavailable": "Login server unavailable. Try again later or contact technical support.", + "account locked": "This account has been disabled. Contact technical support.", + "password expired": "You must change your password before continuing.", + "account terminated": "Account terminated. Access denied.", + "account state error": "Your account state is not stable. Log out, restart your browser, and try again.", + "welcome user": "Welcome, {user}!", + "sign out": "Sign out", + "settings": "Settings", + "options": "Options", + "404 error": "404 Error", + "page not found": "Page not found.", + "invalid parameters": "Invalid request parameters.", + "login server error": "The login server returned an error: {arg}", + "login server user data error": "The login server refused to provide account information. Try again or contact technical support.", + "captcha error": "There was a problem with the CAPTCHA (robot test). Try again.", + "no access permission": "You do not have permission to access this system." +} diff --git a/langs/en/titles.json b/langs/en/titles.json new file mode 100644 index 0000000..6fbf103 --- /dev/null +++ b/langs/en/titles.json @@ -0,0 +1,4 @@ +{ + "home": "Home", + "test": "Test" +} diff --git a/lang/messages.php b/langs/messages.php similarity index 100% rename from lang/messages.php rename to langs/messages.php diff --git a/lib/Exceptions.lib.php b/lib/Exceptions.lib.php new file mode 100644 index 0000000..baec83f --- /dev/null +++ b/lib/Exceptions.lib.php @@ -0,0 +1,13 @@ + + */ + public static function ip4_in_cidr($ip, $cidr) { + if (strpos($cidr, '/') == false) { + $cidr .= '/32'; + } + // $range is in IP/CIDR format eg 127.0.0.1/24 + list( $cidr, $netmask ) = explode('/', $cidr, 2); + $range_decimal = ip2long($cidr); + $ip_decimal = ip2long($ip); + $wildcard_decimal = pow(2, ( 32 - $netmask)) - 1; + $netmask_decimal = ~ $wildcard_decimal; + return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) ); + } + + /** + * Check if a given ipv6 address is in a given cidr + * @param string $ip IP to check in IPV6 format + * @param string $cidr CIDR netmask + * @return boolean true if the IP is in this range, false otherwise. + * @author MW. + */ + public static function ip6_in_cidr($ip, $cidr) { + $address = inet_pton($ip); + $subnetAddress = inet_pton(explode("/", $cidr)[0]); + $subnetMask = explode("/", $cidr)[1]; + + $addr = str_repeat("f", $subnetMask / 4); + switch ($subnetMask % 4) { + case 0: + break; + case 1: + $addr .= "8"; + break; + case 2: + $addr .= "c"; + break; + case 3: + $addr .= "e"; + break; + } + $addr = str_pad($addr, 32, '0'); + $addr = pack("H*", $addr); + + $binMask = $addr; + return ($address & $binMask) == $subnetAddress; + } + + /** + * Check if the REMOTE_ADDR is on Cloudflare's network. + * @return boolean true if it is, otherwise false + */ + public static function validateCloudflare() { + if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + // Using IPv6 + $cloudflare_ips_v6 = [ + "2400:cb00::/32", + "2405:8100::/32", + "2405:b500::/32", + "2606:4700::/32", + "2803:f800::/32", + "2c0f:f248::/32", + "2a06:98c0::/29" + ]; + $valid = false; + foreach ($cloudflare_ips_v6 as $cidr) { + if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) { + $valid = true; + break; + } + } + } else { + // Using IPv4 + $cloudflare_ips_v4 = [ + "103.21.244.0/22", + "103.22.200.0/22", + "103.31.4.0/22", + "104.16.0.0/12", + "108.162.192.0/18", + "131.0.72.0/22", + "141.101.64.0/18", + "162.158.0.0/15", + "172.64.0.0/13", + "173.245.48.0/20", + "188.114.96.0/20", + "190.93.240.0/20", + "197.234.240.0/22", + "198.41.128.0/17" + ]; + $valid = false; + foreach ($cloudflare_ips_v4 as $cidr) { + if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) { + $valid = true; + break; + } + } + } + return $valid; + } + + /** + * Makes a good guess at the client's real IP address. + * + * @return string Client IP or `0.0.0.0` if we can't find anything + */ + public static function getClientIP() { + // If CloudFlare is in the mix, we should use it. + // Check if the request is actually from CloudFlare before trusting it. + if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { + if (validateCloudflare()) { + return $_SERVER["HTTP_CF_CONNECTING_IP"]; + } + } + + if (isset($_SERVER["REMOTE_ADDR"])) { + return $_SERVER["REMOTE_ADDR"]; + } + + return "0.0.0.0"; // This will not happen unless we aren't a web server + } + +} diff --git a/lib/Login.lib.php b/lib/Login.lib.php new file mode 100644 index 0000000..fe22a38 --- /dev/null +++ b/lib/Login.lib.php @@ -0,0 +1,129 @@ +exists()) { + return Login::BAD_USERPASS; + } + if (!$user->checkPassword($password)) { + return Login::BAD_USERPASS; + } + + if ($user->has2fa()) { + if (!$user->check2fa($twofa)) { + return Login::BAD_2FA; + } + } + + switch ($user->getStatus()->get()) { + case AccountStatus::TERMINATED: + return Login::BAD_USERPASS; + case AccountStatus::LOCKED_OR_DISABLED: + return Login::ACCOUNT_DISABLED; + case AccountStatus::NORMAL: + default: + return Login::LOGIN_OK; + } + + return Login::LOGIN_OK; + } + + public static function verifyCaptcha(string $session, string $answer, string $url): bool { + $data = [ + 'session_id' => $session, + 'answer_id' => $answer, + 'action' => "verify" + ]; + $options = [ + 'http' => [ + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + 'method' => 'POST', + 'content' => http_build_query($data) + ] + ]; + $context = stream_context_create($options); + $result = file_get_contents($url, false, $context); + $resp = json_decode($result, TRUE); + if (!$resp['result']) { + return false; + } else { + return true; + } + } + + /** + * Check the login server API for sanity + * @return boolean true if OK, else false + */ + public static function checkLoginServer() { + try { + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "ping" + ] + ]); + + if ($response->getStatusCode() != 200) { + return false; + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + return true; + } else { + return false; + } + } catch (Exception $e) { + return false; + } + } + + /** + * Checks if the given AccountHub API key is valid by attempting to + * access the API with it. + * @param String $key The API key to check + * @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong + */ + function checkAPIKey($key) { + try { + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => $key, + 'action' => "ping" + ] + ]); + + if ($response->getStatusCode() === 200) { + return true; + } + return false; + } catch (Exception $e) { + return false; + } + } + +} diff --git a/lib/Notifications.lib.php b/lib/Notifications.lib.php new file mode 100644 index 0000000..c1d93a9 --- /dev/null +++ b/lib/Notifications.lib.php @@ -0,0 +1,65 @@ +exists()) { + if (empty($title) || empty($content)) { + throw new Exception($Strings->get("invalid parameters", false)); + } + + $timestamp = date("Y-m-d H:i:s"); + if (!empty($timestamp)) { + $timestamp = date("Y-m-d H:i:s", strtotime($timestamp)); + } + + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "addnotification", + 'uid' => $user->getUID(), + 'title' => $title, + 'content' => $content, + 'timestamp' => $timestamp, + 'url' => $url, + 'sensitive' => $sensitive + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + return $resp['id'] * 1; + } else { + return false; + } + } + throw new Exception($Strings->get("user does not exist", false)); + } + +} diff --git a/lib/Session.lib.php b/lib/Session.lib.php new file mode 100644 index 0000000..4987732 --- /dev/null +++ b/lib/Session.lib.php @@ -0,0 +1,19 @@ +getUsername(); + $_SESSION['uid'] = $user->getUID(); + $_SESSION['email'] = $user->getEmail(); + $_SESSION['realname'] = $user->getName(); + $_SESSION['loggedin'] = true; + } + +} diff --git a/lib/Strings.lib.php b/lib/Strings.lib.php new file mode 100644 index 0000000..b094bbf --- /dev/null +++ b/lib/Strings.lib.php @@ -0,0 +1,118 @@ +load("en"); + + if (file_exists(__DIR__ . "/../langs/$language/")) { + $this->language = $language; + $this->load($language); + } else { + trigger_error("Language $language could not be found.", E_USER_WARNING); + } + } + + /** + * Load all JSON files for the specified language. + * @param string $language + */ + private function load(string $language) { + $files = glob(__DIR__ . "/../langs/$language/*.json"); + foreach ($files as $file) { + $strings = json_decode(file_get_contents($file), true); + foreach ($strings as $key => $val) { + if (array_key_exists($key, $this->strings)) { + trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING); + } + $this->strings[$key] = $val; + } + } + } + + /** + * Add language strings dynamically. + * @param array $strings ["key" => "value", ...] + */ + public function addStrings(array $strings) { + foreach ($strings as $key => $val) { + $this->strings[$key] = $val; + } + } + + /** + * I18N string getter. If the key isn't found, it outputs the key itself. + * @param string $key + * @param bool $echo True to echo the result, false to return it. Default is true. + * @return string + */ + public function get(string $key, bool $echo = true): string { + $str = $key; + if (array_key_exists($key, $this->strings)) { + $str = $this->strings[$key]; + } else { + trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING); + } + + if ($echo) { + echo $str; + } + return $str; + } + + /** + * I18N string getter (with builder). If the key doesn't exist, outputs the key itself. + * @param string $key + * @param array $replace key-value array of replacements. + * If the string value is "hello {abc}" and you give ["abc" => "123"], the + * result will be "hello 123". + * @param bool $echo True to echo the result, false to return it. Default is true. + * @return string + */ + public function build(string $key, array $replace, bool $echo = true): string { + $str = $key; + if (array_key_exists($key, $this->strings)) { + $str = $this->strings[$key]; + } else { + trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING); + } + + foreach ($replace as $find => $repl) { + $str = str_replace("{" . $find . "}", $repl, $str); + } + + if ($echo) { + echo $str; + } + return $str; + } + + /** + * Builds and returns a JSON key:value string for the supplied array of keys. + * @param array $keys ["key1", "key2", ...] + */ + public function getJSON(array $keys): string { + $strings = []; + foreach ($keys as $k) { + $strings[$k] = $this->get($k, false); + } + return json_encode($strings); + } + +} diff --git a/lib/User.lib.php b/lib/User.lib.php new file mode 100644 index 0000000..7852e31 --- /dev/null +++ b/lib/User.lib.php @@ -0,0 +1,352 @@ +request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "userexists", + 'uid' => $uid + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK" && $resp['exists'] === true) { + $this->exists = true; + } else { + $this->uid = $uid; + $this->username = $username; + $this->exists = false; + } + + if ($this->exists) { + // Get user info + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "userinfo", + 'uid' => $uid + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + $this->uid = $resp['data']['uid'] * 1; + $this->username = $resp['data']['username']; + $this->email = $resp['data']['email']; + $this->realname = $resp['data']['name']; + } else { + sendError("Login server error: " . $resp['msg']); + } + } + } + + public static function byUsername(string $username): User { + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'username' => $username, + 'action' => "userinfo" + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if (!isset($resp['status'])) { + sendError("Login server error: " . $resp); + } + if ($resp['status'] == "OK") { + return new self($resp['data']['uid'] * 1); + } else { + return new self(-1, $username); + } + } + + public function exists(): bool { + return $this->exists; + } + + public function has2fa(): bool { + if (!$this->exists) { + return false; + } + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "hastotp", + 'username' => $this->username + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + return $resp['otp'] == true; + } else { + return false; + } + } + + function getUsername() { + return $this->username; + } + + function getUID() { + return $this->uid; + } + + function getEmail() { + return $this->email; + } + + function getName() { + return $this->realname; + } + + /** + * Check the given plaintext password against the stored hash. + * @param string $password + * @return bool + */ + function checkPassword(string $password): bool { + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "auth", + 'username' => $this->username, + 'password' => $password + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + return true; + } else { + return false; + } + } + + function check2fa(string $code): bool { + if (!$this->has2fa) { + return true; + } + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "verifytotp", + 'username' => $this->username, + 'code' => $code + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + return $resp['valid']; + } else { + return false; + } + } + + /** + * Check if the given username has the given permission (or admin access) + * @global $database $database + * @param string $code + * @return boolean TRUE if the user has the permission (or admin access), else FALSE + */ + function hasPermission(string $code): bool { + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "permission", + 'username' => $this->username, + 'code' => $code + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + return $resp['has_permission']; + } else { + return false; + } + } + + /** + * Get the account status. + * @return \AccountStatus + */ + function getStatus(): AccountStatus { + + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "acctstatus", + 'username' => $this->username + ] + ]); + + if ($response->getStatusCode() > 299) { + sendError("Login server error: " . $response->getBody()); + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + return AccountStatus::fromString($resp['account']); + } else { + return null; + } + } + + function sendAlertEmail(string $appname = SITE_TITLE) { + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "alertemail", + 'username' => $this->username, + 'appname' => SITE_TITLE + ] + ]); + + if ($response->getStatusCode() > 299) { + return "An unknown error occurred."; + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK") { + return true; + } else { + return $resp['msg']; + } + } + +} + +class AccountStatus { + + const NORMAL = 1; + const LOCKED_OR_DISABLED = 2; + const CHANGE_PASSWORD = 3; + const TERMINATED = 4; + const ALERT_ON_ACCESS = 5; + + private $status; + + public function __construct(int $status) { + $this->status = $status; + } + + public static function fromString(string $status): AccountStatus { + switch ($status) { + case "NORMAL": + return new self(self::NORMAL); + case "LOCKED_OR_DISABLED": + return new self(self::LOCKED_OR_DISABLED); + case "CHANGE_PASSWORD": + return new self(self::CHANGE_PASSWORD); + case "TERMINATED": + return new self(self::TERMINATED); + case "ALERT_ON_ACCESS": + return new self(self::ALERT_ON_ACCESS); + default: + return new self(0); + } + } + + /** + * Get the account status/state as an integer. + * @return int + */ + public function get(): int { + return $this->status; + } + + /** + * Get the account status/state as a string representation. + * @return string + */ + public function getString(): string { + switch ($this->status) { + case self::NORMAL: + return "NORMAL"; + case self::LOCKED_OR_DISABLED: + return "LOCKED_OR_DISABLED"; + case self::CHANGE_PASSWORD: + return "CHANGE_PASSWORD"; + case self::TERMINATED: + return "TERMINATED"; + case self::ALERT_ON_ACCESS: + return "ALERT_ON_ACCESS"; + default: + return "OTHER_" . $this->status; + } + } + +} diff --git a/lib/iputils.php b/lib/iputils.php deleted file mode 100644 index b1c0aa3..0000000 --- a/lib/iputils.php +++ /dev/null @@ -1,131 +0,0 @@ - - */ -function ip4_in_cidr($ip, $cidr) { - if (strpos($cidr, '/') == false) { - $cidr .= '/32'; - } - // $range is in IP/CIDR format eg 127.0.0.1/24 - list( $cidr, $netmask ) = explode('/', $cidr, 2); - $range_decimal = ip2long($cidr); - $ip_decimal = ip2long($ip); - $wildcard_decimal = pow(2, ( 32 - $netmask)) - 1; - $netmask_decimal = ~ $wildcard_decimal; - return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) ); -} - -/** - * Check if a given ipv6 address is in a given cidr - * @param string $ip IP to check in IPV6 format - * @param string $cidr CIDR netmask - * @return boolean true if the IP is in this range, false otherwise. - * @author MW. - */ -function ip6_in_cidr($ip, $cidr) { - $address = inet_pton($ip); - $subnetAddress = inet_pton(explode("/", $cidr)[0]); - $subnetMask = explode("/", $cidr)[1]; - - $addr = str_repeat("f", $subnetMask / 4); - switch ($subnetMask % 4) { - case 0: - break; - case 1: - $addr .= "8"; - break; - case 2: - $addr .= "c"; - break; - case 3: - $addr .= "e"; - break; - } - $addr = str_pad($addr, 32, '0'); - $addr = pack("H*", $addr); - - $binMask = $addr; - return ($address & $binMask) == $subnetAddress; -} - -/** - * Check if the REMOTE_ADDR is on Cloudflare's network. - * @return boolean true if it is, otherwise false - */ -function validateCloudflare() { - if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - // Using IPv6 - $cloudflare_ips_v6 = [ - "2400:cb00::/32", - "2405:8100::/32", - "2405:b500::/32", - "2606:4700::/32", - "2803:f800::/32", - "2c0f:f248::/32", - "2a06:98c0::/29" - ]; - $valid = false; - foreach ($cloudflare_ips_v6 as $cidr) { - if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) { - $valid = true; - break; - } - } - } else { - // Using IPv4 - $cloudflare_ips_v4 = [ - "103.21.244.0/22", - "103.22.200.0/22", - "103.31.4.0/22", - "104.16.0.0/12", - "108.162.192.0/18", - "131.0.72.0/22", - "141.101.64.0/18", - "162.158.0.0/15", - "172.64.0.0/13", - "173.245.48.0/20", - "188.114.96.0/20", - "190.93.240.0/20", - "197.234.240.0/22", - "198.41.128.0/17" - ]; - $valid = false; - foreach ($cloudflare_ips_v4 as $cidr) { - if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) { - $valid = true; - break; - } - } - } - return $valid; -} - -/** - * Makes a good guess at the client's real IP address. - * - * @return string Client IP or `0.0.0.0` if we can't find anything - */ -function getClientIP() { - // If CloudFlare is in the mix, we should use it. - // Check if the request is actually from CloudFlare before trusting it. - if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { - if (validateCloudflare()) { - return $_SERVER["HTTP_CF_CONNECTING_IP"]; - } - } - - if (isset($_SERVER["REMOTE_ADDR"])) { - return $_SERVER["REMOTE_ADDR"]; - } - - return "0.0.0.0"; // This will not happen unless we aren't a web server -} diff --git a/lib/login.php b/lib/login.php deleted file mode 100644 index a9b290f..0000000 --- a/lib/login.php +++ /dev/null @@ -1,402 +0,0 @@ -request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "ping" - ] - ]); - - if ($response->getStatusCode() != 200) { - return false; - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return true; - } else { - return false; - } - } catch (Exception $e) { - return false; - } -} - -/** - * Checks if the given AccountHub API key is valid by attempting to - * access the API with it. - * @param String $key The API key to check - * @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong - */ -function checkAPIKey($key) { - try { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => $key, - 'action' => "ping" - ] - ]); - - if ($response->getStatusCode() === 200) { - return true; - } - return false; - } catch (Exception $e) { - return false; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Account handling // -//////////////////////////////////////////////////////////////////////////////// - -/** - * Checks the given credentials against the API. - * @param string $username - * @param string $password - * @return boolean True if OK, else false - */ -function authenticate_user($username, $password, &$errmsg) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "auth", - 'username' => $username, - 'password' => $password - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return true; - } else { - $errmsg = $resp['msg']; - return false; - } -} - -/** - * Check if a username exists. - * @param String $username - */ -function user_exists($username) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "userexists", - 'username' => $username - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK" && $resp['exists'] === true) { - return true; - } else { - return false; - } -} - -/** - * Check if a UID exists. - * @param String $uid - */ -function uid_exists($uid) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "userexists", - 'uid' => $uid - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK" && $resp['exists'] === true) { - return true; - } else { - return false; - } -} - -/** - * Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED, - * CHANGE_PASSWORD, or ALERT_ON_ACCESS - * @param string $username - * @return string - */ -function get_account_status($username) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "acctstatus", - 'username' => $username - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return $resp['account']; - } else { - return false; - } -} - -/** - * Check if the given username has the given permission (or admin access) - * @param string $username - * @param string $permcode - * @return boolean TRUE if the user has the permission (or admin access), else FALSE - */ -function account_has_permission($username, $permcode) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "permission", - 'username' => $username, - 'code' => $permcode - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return $resp['has_permission']; - } else { - return false; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Login handling // -//////////////////////////////////////////////////////////////////////////////// - -/** - * Setup $_SESSION values with user data and set loggedin flag to true - * @param string $username - */ -function doLoginUser($username) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "userinfo", - 'username' => $username - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - - if ($resp['status'] == "OK") { - $userinfo = $resp['data']; - session_regenerate_id(true); - $newSession = session_id(); - session_write_close(); - session_id($newSession); - session_start(); - $_SESSION['username'] = $username; - $_SESSION['uid'] = $userinfo['uid']; - $_SESSION['email'] = $userinfo['email']; - $_SESSION['realname'] = $userinfo['name']; - $_SESSION['loggedin'] = true; - return true; - } else { - return false; - } -} - -function sendLoginAlertEmail($username) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "alertemail", - 'username' => $username, - 'appname' => SITE_TITLE - ] - ]); - - if ($response->getStatusCode() > 299) { - return "An unknown error occurred."; - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return true; - } else { - return $resp['msg']; - } -} - -function simLogin($username, $password) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "login", - 'username' => $username, - 'password' => $password - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return true; - } else { - return $resp['msg']; - } -} - -function verifyCaptcheck($session, $answer, $url) { - $data = [ - 'session_id' => $session, - 'answer_id' => $answer, - 'action' => "verify" - ]; - $options = [ - 'http' => [ - 'header' => "Content-type: application/x-www-form-urlencoded\r\n", - 'method' => 'POST', - 'content' => http_build_query($data) - ] - ]; - $context = stream_context_create($options); - $result = file_get_contents($url, false, $context); - $resp = json_decode($result, TRUE); - if (!$resp['result']) { - return false; - } else { - return true; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// 2-factor authentication // -//////////////////////////////////////////////////////////////////////////////// - -/** - * Check if a user has TOTP setup - * @param string $username - * @return boolean true if TOTP secret exists, else false - */ -function userHasTOTP($username) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "hastotp", - 'username' => $username - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return $resp['otp']; - } else { - return false; - } -} - -/** - * Verify a TOTP multiauth code - * @global $database - * @param string $username - * @param int $code - * @return boolean true if it's legit, else false - */ -function verifyTOTP($username, $code) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "verifytotp", - 'username' => $username, - 'code' => $code - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return $resp['valid']; - } else { - return false; - } -} diff --git a/lib/userinfo.php b/lib/userinfo.php deleted file mode 100644 index 65f6b38..0000000 --- a/lib/userinfo.php +++ /dev/null @@ -1,127 +0,0 @@ -request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "userinfo", - 'username' => $u - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return $resp['data']; - } else { - // this shouldn't happen, but in case it does just fake it. - return ["name" => $u, "username" => $u, "uid" => $u]; - } -} - -/** - * Get user info for the given UID. - * @param int $u user ID - * @return [string] Array of [uid, username, name] - */ -function getUserByID($u) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "userinfo", - 'uid' => $u - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return $resp['data']; - } else { - // this shouldn't happen, but in case it does just fake it. - return ["name" => $u, "username" => $u, "uid" => $u]; - } -} - -/** - * Check if the first UID is a manager of the second UID. - * @param int $m Manager UID - * @param int $e Employee UID - * @return boolean - */ -function isManagerOf($m, $e) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "ismanagerof", - 'manager' => $m, - 'employee' => $e, - 'uid' => 1 - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return $resp['managerof'] === true; - } else { - // this shouldn't happen, but in case it does just fake it. - return false; - } -} - -/** - * Get an array of UIDs the given UID is a manager of. - * @param int $manageruid The UID of the manager to find employees for. - * @return [int] - */ -function getManagedUIDs($manageruid) { - $client = new GuzzleHttp\Client(); - - $response = $client - ->request('POST', PORTAL_API, [ - 'form_params' => [ - 'key' => PORTAL_KEY, - 'action' => "getmanaged", - 'uid' => $manageruid - ] - ]); - - if ($response->getStatusCode() > 299) { - sendError("Login server error: " . $response->getBody()); - } - - $resp = json_decode($response->getBody(), TRUE); - if ($resp['status'] == "OK") { - return $resp['employees']; - } else { - return []; - } -} diff --git a/mobile/index.php b/mobile/index.php index 4143217..5cc4575 100644 --- a/mobile/index.php +++ b/mobile/index.php @@ -14,8 +14,6 @@ $access_permission = null; require __DIR__ . "/../required.php"; -require __DIR__ . "/../lib/login.php"; - header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); @@ -73,7 +71,7 @@ function mobile_valid($username, $code) { } if (mobile_enabled() !== TRUE) { - exit(json_encode(["status" => "ERROR", "msg" => lang("mobile login disabled", false)])); + exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)])); } // Make sure we have a username and access key @@ -93,20 +91,21 @@ if (!mobile_valid($VARS['username'], $VARS['key'])) { switch ($VARS['action']) { case "start_session": // Do a web login. - if (user_exists($VARS['username'])) { - if (get_account_status($VARS['username']) == "NORMAL") { - if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) { - if (is_null($access_permission) || account_has_permission($VARS['username'], $access_permission)) { - doLoginUser($VARS['username'], $VARS['password']); + $user = User::byUsername($VARS['username']); + if ($user->exists()) { + if ($user->getStatus()->getString() == "NORMAL") { + if ($user->checkPassword($VARS['password'])) { + if (is_null($access_permission) || $user->hasPermission($access_permission)) { + Session::start($user); $_SESSION['mobile'] = true; exit(json_encode(["status" => "OK"])); } else { - exit(json_encode(["status" => "ERROR", "msg" => lang("no admin permission", false)])); + exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("no admin permission", false)])); } } } } - exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)])); + exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)])); default: http_response_code(404); die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."])); diff --git a/pages/404.php b/pages/404.php index 62a0f89..7a2be4e 100644 --- a/pages/404.php +++ b/pages/404.php @@ -5,6 +5,6 @@ ?>
-

+
get("404 error");?>
get("page not found"); ?>
\ No newline at end of file diff --git a/required.php b/required.php index 42a8639..c296dcb 100644 --- a/required.php +++ b/required.php @@ -62,9 +62,14 @@ if ($_SESSION['mobile'] === TRUE) { require __DIR__ . '/vendor/autoload.php'; // List of alert messages -require __DIR__ . '/lang/messages.php'; -// text strings (i18n) -require __DIR__ . '/lang/' . LANGUAGE . ".php"; +require __DIR__ . '/langs/messages.php'; + +$libs = glob(__DIR__ . "/lib/*.lib.php"); +foreach ($libs as $lib) { + require_once $lib; +} + +$Strings = new Strings(LANGUAGE); /** * Kill off the running process and spit out an error message @@ -136,52 +141,6 @@ function is_empty($str) { return (is_null($str) || !isset($str) || $str == ''); } -/** - * I18N string getter. If the key doesn't exist, outputs the key itself. - * @param string $key I18N string key - * @param boolean $echo whether to echo the result or return it (default echo) - */ -function lang($key, $echo = true) { - if (array_key_exists($key, STRINGS)) { - $str = STRINGS[$key]; - } else { - trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING); - $str = $key; - } - - if ($echo) { - echo $str; - } else { - return $str; - } -} - -/** - * I18N string getter (with builder). If the key doesn't exist, outputs the key itself. - * @param string $key I18N string key - * @param array $replace key-value array of replacements. - * If the string value is "hello {abc}" and you give ["abc" => "123"], the - * result will be "hello 123". - * @param boolean $echo whether to echo the result or return it (default echo) - */ -function lang2($key, $replace, $echo = true) { - if (array_key_exists($key, STRINGS)) { - $str = STRINGS[$key]; - } else { - trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING); - $str = $key; - } - - foreach ($replace as $find => $repl) { - $str = str_replace("{" . $find . "}", $repl, $str); - } - - if ($echo) { - echo $str; - } else { - return $str; - } -} function dieifnotloggedin() { if ($_SESSION['loggedin'] != true) { diff --git a/tests/User.test.php b/tests/User.test.php new file mode 100644 index 0000000..a4e92ec --- /dev/null +++ b/tests/User.test.php @@ -0,0 +1,96 @@ +exists()) { + echo "FAIL: Invalid user ID marked as existing\n"; +} else { + echo "OK\n"; +} +if ($user->getUID() != 784587254) { + echo "FAIL: Invalid user has mismatched UID\n"; +} else { + echo "OK\n"; +} + +$user = User::byUsername("r9483yt8934t"); +if ($user->exists()) { + echo "FAIL: Invalid username marked as existing\n"; +} else { + echo "OK\n"; +} + +if ($user->checkPassword("gbirg4wre") != false) { + echo "FAIL: Invalid user and invalid password allowed\n"; +} else { + echo "OK\n"; +} + +if ($user->has2fa() != false) { + echo "FAIL: Invalid user has 2fa\n"; +} else { + echo "OK\n"; +} + +if ($user->getUsername() != "r9483yt8934t") { + echo "FAIL: Invalid user has mismatched username\n"; +} else { + echo "OK\n"; +} + +if ($user->getStatus()->get() != 0) { + echo "FAIL: Invalid user has real account status\n"; +} else { + echo "OK\n"; +} + +if ($user->getStatus()->getString() != "OTHER_0") { + echo "FAIL: Invalid user has wrong account status string\n"; +} else { + echo "OK\n"; +} + +// Test valid user responses + +$user = User::byUsername($valid_user); +if (!$user->exists()) { + echo "FAIL: Valid user does not exist\n"; +} else { + echo "OK\n"; +} + +if ($user->checkPassword($valid_pass) !== true) { + echo "FAIL: Valid user and password not allowed\n"; +} else { + echo "OK\n"; +} + +if ($user->getUsername() != $valid_user) { + echo "FAIL: Valid user has mismatched username\n"; +} else { + echo "OK\n"; +} + +if ($user->getStatus()->getString() != "NORMAL") { + echo "FAIL: Valid user has wrong account status string\n"; +} else { + echo "OK\n"; +} + +exit("ALL OK"); \ No newline at end of file