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:
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)
{