mikuli.cz

:)
git clone https://git.sr.ht/~ashymad/mikuli.cz
Log | Files | Refs

commit 3d4ca1421d8382e5f7c509429832a9ee7cb2dae4
parent 2c0ddfca782f572aa050d6e35a20976e441fe663
Author: markseu <mark2011@mayberg.se>
Date:   Sun,  4 May 2014 14:57:52 +0200

Hello web interface (user accounts with bcrypt)

Diffstat:
MREADME.md | 9++++-----
Msystem/config/user.ini | 2+-
Msystem/core/core-webinterface.php | 65++++++++++++++++++++++++++++++++++++++---------------------------
Msystem/core/core.php | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
4 files changed, 126 insertions(+), 63 deletions(-)

diff --git a/README.md b/README.md @@ -11,13 +11,12 @@ How do I install this? 2. Copy all files to your web hosting. 3. Open your website in a browser. -Installation requirements are Apache, mod_rewrite and PHP 5.3. -With Yellow you don't get a lot of extra stuff. There are [Yellow extensions](https://github.com/markseu/yellowcms-extensions/blob/master/README.md). +Installation requirements are Apache, mod_rewrite, mod_ssl and PHP 5.3. How do I get started? ----------------------- -You already have everything you need. Start by editing your website. -That's it. For more information see [Yellow documentation](https://github.com/markseu/yellowcms-extensions/blob/master/documentation/README.md). +--------------------- +You already have everything you need. Start by editing your own website. +There are [Yellow extensions](https://github.com/markseu/yellowcms-extensions). For more information see [Yellow documentation](https://github.com/markseu/yellowcms-extensions/blob/master/documentation/README.md) License and thanks ------------------ diff --git a/system/config/user.ini b/system/config/user.ini @@ -1,3 +1,3 @@ // Yellow user accounts -// Format: Email, password (sha256 with email prefix as salt), name, language, home +// Format: Email, password hash, name, language, home diff --git a/system/core/core-webinterface.php b/system/core/core-webinterface.php @@ -5,7 +5,7 @@ // Web interface core plugin class YellowWebinterface { - const Version = "0.2.8"; + const Version = "0.2.9"; var $yellow; //access to API var $users; //web interface users var $active; //web interface is active location? (boolean) @@ -19,12 +19,14 @@ class YellowWebinterface $this->yellow->config->setDefault("webinterfaceLocation", "/edit/"); $this->yellow->config->setDefault("webinterfaceUserFile", "user.ini"); $this->yellow->config->setDefault("webinterfaceUserHome", "/"); + $this->yellow->config->setDefault("webinterfaceUserHashAlgorithm", "bcrypt"); + $this->yellow->config->setDefault("webinterfaceUserHashCost", "10"); $this->users = new YellowWebinterfaceUsers($yellow); $this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile")); } // Handle web interface location - function onRequest($location) + function onRequest($serverName, $serverBase, $location, $fileName) { $statusCode = 0; if($this->checkLocation($location)) @@ -141,8 +143,17 @@ class YellowWebinterface if(!empty($email) && !empty($password) && (empty($home) || $home[0]=='/')) { $fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile"); - $statusCode = $this->users->createUser($fileName, $email, $password, $name, $language, $home) ? 200 : 500; - if($statusCode != 200) echo "ERROR updating configuration: Can't write file '$fileName'!\n"; + $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm"); + $cost = $this->yellow->config->get("webinterfaceUserHashCost"); + $hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost); + if(empty($hash)) + { + $statusCode = 500; + echo "ERROR creating hash: Algorithm '$algorithm' not supported!\n"; + } else { + $statusCode = $this->users->createUser($fileName, $email, $hash, $name, $language, $home) ? 200 : 500; + if($statusCode != 200) echo "ERROR updating configuration: Can't write file '$fileName'!\n"; + } echo "Yellow $command: User account ".($statusCode!=200 ? "not " : ""); echo ($this->users->isExisting($email) ? "updated" : "created")."\n"; } else { @@ -230,10 +241,10 @@ class YellowWebinterface $this->loginFailed = true; } } else if(isset($_COOKIE["login"])) { - $cookie = $_COOKIE["login"]; - if($this->users->checkCookie($cookie)) + list($email, $session) = $this->users->getCookieInformation($_COOKIE["login"]); + if($this->users->checkCookie($email, $session)) { - $this->users->email = $this->users->getCookieEmail($cookie); + $this->users->email = $email; } else { $this->loginFailed = true; } @@ -315,22 +326,21 @@ class YellowWebinterfaceUsers } // Set user data - function set($email, $password, $name, $language, $home) + function set($email, $hash, $name, $language, $home) { $this->users[$email] = array(); $this->users[$email]["email"] = $email; - $this->users[$email]["password"] = $password; + $this->users[$email]["hash"] = $hash; $this->users[$email]["name"] = $name; $this->users[$email]["language"] = $language; $this->users[$email]["home"] = $home; - $this->users[$email]["session"] = hash("sha256", $email.$password.$password.$email); } // Create or update user in file - function createUser($fileName, $email, $password, $name, $language, $home) + function createUser($fileName, $email, $hash, $name, $language, $home) { $email = strreplaceu(',', '-', $email); - $password = hash("sha256", $email.$password); + $hash = strreplaceu(',', '-', $hash); $fileNewUser = true; $fileData = @file($fileName); if($fileData) @@ -345,7 +355,7 @@ class YellowWebinterfaceUsers $name = strreplaceu(',', '-', empty($name) ? $matches[3] : $name); $language = strreplaceu(',', '-', empty($language) ? $matches[4] : $language); $home = strreplaceu(',', '-', empty($home) ? $matches[5] : $home); - $fileDataNew .= "$email,$password,$name,$language,$home\n"; + $fileDataNew .= "$email,$hash,$name,$language,$home\n"; $fileNewUser = false; continue; } @@ -358,7 +368,7 @@ class YellowWebinterfaceUsers $name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name); $language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language); $home = strreplaceu(',', '-', empty($home) ? $this->yellow->config->get("webinterfaceUserHome") : $home); - $fileDataNew .= "$email,$password,$name,$language,$home\n"; + $fileDataNew .= "$email,$hash,$name,$language,$home\n"; } return $this->yellow->toolbox->createFile($fileName, $fileDataNew); } @@ -366,7 +376,8 @@ class YellowWebinterfaceUsers // Check user login function checkUser($email, $password) { - return $this->isExisting($email) && hash("sha256", $email.$password)==$this->users[$email]["password"]; + $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm"); + return $this->isExisting($email) && $this->yellow->toolbox->verifyHash($password, $algorithm, $this->users[$email]["hash"]); } // Create browser cookie @@ -374,30 +385,30 @@ class YellowWebinterfaceUsers { if($this->isExisting($email)) { - $salt = hash("sha256", uniqid(mt_rand(), true)); - $text = $email.";".$salt.";".hash("sha256", $salt.$this->users[$email]["session"]); - setcookie($cookieName, $text, time()+60*60*24*30*365*10, "/"); + $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation"); + $session = $this->yellow->toolbox->createHash($this->users[$email]["hash"], "sha256"); + if(empty($session)) $session = "error-hash-algorithm-sha256"; + setcookie($cookieName, "$email,$session", time()+60*60*24*30*365, $location); } } // Destroy browser cookie function destroyCookie($cookieName) { - setcookie($cookieName, "", time()-3600, "/"); + $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation"); + setcookie($cookieName, "", time()-3600, $location); } - // Check user login from browser cookie - function checkCookie($cookie) + // Return information from browser cookie + function getCookieInformation($cookie) { - list($email, $salt, $session) = explode(';', $cookie); - return $this->isExisting($email) && hash("sha256", $salt.$this->users[$email]["session"])==$session; + return explode(',', $cookie, 2); } - // Return user email from browser cookie - function getCookieEmail($cookie) + // Check user login from browser cookie + function checkCookie($email, $session) { - list($email, $salt, $session) = explode(';', $cookie); - return $email; + return $this->isExisting($email) && $this->yellow->toolbox->verifyHash($this->users[$email]["hash"], "sha256", $session); } // Return user name diff --git a/system/core/core.php b/system/core/core.php @@ -5,7 +5,7 @@ // Yellow main class class Yellow { - const Version = "0.2.17"; + const Version = "0.2.18"; var $page; //current page var $pages; //pages from file system var $config; //configuration @@ -65,7 +65,7 @@ class Yellow if(method_exists($value["obj"], "onRequest")) { $this->pages->requestHandler = $key; - $statusCode = $value["obj"]->onRequest($location); + $statusCode = $value["obj"]->onRequest($serverName, $serverBase, $location, $fileName); if($statusCode != 0) break; } } @@ -1527,18 +1527,18 @@ class YellowToolbox { switch($statusCode) { - case 0: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode No data"; break; - case 200: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode OK"; break; - case 301: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved permanently"; break; - case 302: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved temporarily"; break; - case 303: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Reload please"; break; - case 304: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not modified"; break; - case 400: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Bad request"; break; - case 401: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unauthorised"; break; - case 404: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not found"; break; - case 424: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Does not exist"; break; - case 500: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Server error"; break; - default: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unknown status"; + case 0: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode No data"; break; + case 200: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode OK"; break; + case 301: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved permanently"; break; + case 302: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved temporarily"; break; + case 303: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Reload please"; break; + case 304: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not modified"; break; + case 400: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Bad request"; break; + case 401: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unauthorised"; break; + case 404: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not found"; break; + case 424: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Does not exist"; break; + case 500: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Server error"; break; + default: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unknown status"; } return $text; } @@ -1733,21 +1733,6 @@ class YellowToolbox if(preg_match("/^.*\/([\w\-]+)/", $text, $matches)) $text = ucfirst($matches[1]); return $text; } - - // Detect web browser language - function detectBrowserLanguage($languagesAllowed, $languageDefault) - { - $language = $languageDefault; - if(isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) - { - foreach(preg_split("/,\s*/", $_SERVER["HTTP_ACCEPT_LANGUAGE"]) as $string) - { - $tokens = explode(';', $string, 2); - if(in_array($tokens[0], $languagesAllowed)) { $language = $tokens[0]; break; } - } - } - return $language; - } // Detect PNG and JPG image dimensions function detectImageDimensions($fileName) @@ -1788,7 +1773,75 @@ class YellowToolbox } return array($width, $height); } - + + // Create random text for cryptography + function createSalt($length, $bcryptFormat = false) + { + $dataBuffer = $salt = ""; + $dataBufferSize = $bcryptFormat ? intval(ceil($length/4) * 3) : intval(ceil($length/2)); + if(empty($dataBuffer) && function_exists("mcrypt_create_iv")) + { + $dataBuffer = @mcrypt_create_iv($dataBufferSize, MCRYPT_DEV_URANDOM); + } + if(empty($dataBuffer) && function_exists("openssl_random_pseudo_bytes")) + { + $dataBuffer = @openssl_random_pseudo_bytes($dataBufferSize); + } + if(strlenb($dataBuffer) == $dataBufferSize) + { + if($bcryptFormat) + { + $salt = substrb(base64_encode($dataBuffer), 0, $length); + $base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + $bcrypt64Chars = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + $salt = strtr($salt, $base64Chars, $bcrypt64Chars); + } else { + $salt = substrb(bin2hex($dataBuffer), 0, $length); + } + } + return $salt; + } + + // Create hash with random salt + function createHash($text, $algorithm, $cost = 0) + { + $hash = ""; + switch($algorithm) + { + case "bcrypt": $prefix = sprintf("$2y$%02d$", $cost); + $salt = $this->createSalt(22, true); + $hash = crypt($text, $prefix.$salt); + if(empty($salt) || strlenb($hash)!=60) $hash = ""; + break; + case "sha256": $prefix = "$5y$"; + $salt = $this->createSalt(32); + $hash = "$prefix$salt".hash("sha256", $salt.$text); + if(empty($salt) || strlenb($hash)!=100) $hash = ""; + break; + } + return $hash; + } + + // Verify that text matches hash + function verifyHash($text, $algorithm, $hash) + { + $hashCalculated = ""; + switch($algorithm) + { + case "bcrypt": if(substrb($hash, 0, 4) == "$2y$") $hashCalculated = crypt($text, $hash); break; + case "sha256": if(substrb($hash, 0, 4) == "$5y$") + { + $prefix = substrb($hash, 0, 4); + $salt = substrb($hash, 4, 32); + $hashCalculated = "$prefix$salt".hash("sha256", $salt.$text); + } + break; + } + $ok = !empty($hashCalculated) && strlenb($hashCalculated)==strlenb($hash); + if($ok) for($i=0; $i<strlenb($hashCalculated); ++$i) $ok &= $hashCalculated[$i] == $hash[$i]; + return $ok; + } + // Start timer function timerStart(&$time) {