Hello web interface (user accounts with bcrypt)

This commit is contained in:
markseu 2014-05-04 14:57:52 +02:00
parent 2c0ddfca78
commit 3d4ca1421d
4 changed files with 128 additions and 65 deletions

View file

@ -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
------------------

View file

@ -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

View file

@ -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);
}
// Return information from browser cookie
function getCookieInformation($cookie)
{
return explode(',', $cookie, 2);
}
// Check user login from browser cookie
function checkCookie($cookie)
function checkCookie($email, $session)
{
list($email, $salt, $session) = explode(';', $cookie);
return $this->isExisting($email) && hash("sha256", $salt.$this->users[$email]["session"])==$session;
}
// Return user email from browser cookie
function getCookieEmail($cookie)
{
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

View file

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