Преглед изворни кода

Hello web interface (user accounts with bcrypt)

markseu пре 11 година
родитељ
комит
3d4ca1421d
4 измењених фајлова са 126 додато и 63 уклоњено
  1. 4 5
      README.md
  2. 1 1
      system/config/user.ini
  3. 38 27
      system/core/core-webinterface.php
  4. 83 30
      system/core/core.php

+ 4 - 5
README.md

@@ -11,13 +11,12 @@ How do I install this?
 2. Copy all files to your web hosting.  
 2. Copy all files to your web hosting.  
 3. Open your website in a browser.
 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?
 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
 License and thanks
 ------------------
 ------------------

+ 1 - 1
system/config/user.ini

@@ -1,3 +1,3 @@
 // Yellow user accounts
 // Yellow user accounts
-// Format: Email, password (sha256 with email prefix as salt), name, language, home
+// Format: Email, password hash, name, language, home
 
 

+ 38 - 27
system/core/core-webinterface.php

@@ -5,7 +5,7 @@
 // Web interface core plugin
 // Web interface core plugin
 class YellowWebinterface
 class YellowWebinterface
 {
 {
-	const Version = "0.2.8";
+	const Version = "0.2.9";
 	var $yellow;				//access to API
 	var $yellow;				//access to API
 	var $users;					//web interface users
 	var $users;					//web interface users
 	var $active;				//web interface is active location? (boolean)
 	var $active;				//web interface is active location? (boolean)
@@ -19,12 +19,14 @@ class YellowWebinterface
 		$this->yellow->config->setDefault("webinterfaceLocation", "/edit/");
 		$this->yellow->config->setDefault("webinterfaceLocation", "/edit/");
 		$this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
 		$this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
 		$this->yellow->config->setDefault("webinterfaceUserHome", "/");
 		$this->yellow->config->setDefault("webinterfaceUserHome", "/");
+		$this->yellow->config->setDefault("webinterfaceUserHashAlgorithm", "bcrypt");
+		$this->yellow->config->setDefault("webinterfaceUserHashCost", "10");
 		$this->users = new YellowWebinterfaceUsers($yellow);
 		$this->users = new YellowWebinterfaceUsers($yellow);
 		$this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile"));
 		$this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile"));
 	}
 	}
 
 
 	// Handle web interface location
 	// Handle web interface location
-	function onRequest($location)
+	function onRequest($serverName, $serverBase, $location, $fileName)
 	{
 	{
 		$statusCode = 0;
 		$statusCode = 0;
 		if($this->checkLocation($location))
 		if($this->checkLocation($location))
@@ -141,8 +143,17 @@ class YellowWebinterface
 		if(!empty($email) && !empty($password) && (empty($home) || $home[0]=='/'))
 		if(!empty($email) && !empty($password) && (empty($home) || $home[0]=='/'))
 		{
 		{
 			$fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile");
 			$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 "Yellow $command: User account ".($statusCode!=200 ? "not " : "");
 			echo ($this->users->isExisting($email) ? "updated" : "created")."\n";
 			echo ($this->users->isExisting($email) ? "updated" : "created")."\n";
 		} else {
 		} else {
@@ -230,10 +241,10 @@ class YellowWebinterface
 				$this->loginFailed = true;
 				$this->loginFailed = true;
 			}
 			}
 		} else if(isset($_COOKIE["login"])) {
 		} 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 {
 			} else {
 				$this->loginFailed = true;
 				$this->loginFailed = true;
 			}
 			}
@@ -315,22 +326,21 @@ class YellowWebinterfaceUsers
 	}
 	}
 	
 	
 	// Set user data
 	// Set user data
-	function set($email, $password, $name, $language, $home)
+	function set($email, $hash, $name, $language, $home)
 	{
 	{
 		$this->users[$email] = array();
 		$this->users[$email] = array();
 		$this->users[$email]["email"] = $email;
 		$this->users[$email]["email"] = $email;
-		$this->users[$email]["password"] = $password;
+		$this->users[$email]["hash"] = $hash;
 		$this->users[$email]["name"] = $name;
 		$this->users[$email]["name"] = $name;
 		$this->users[$email]["language"] = $language;
 		$this->users[$email]["language"] = $language;
 		$this->users[$email]["home"] = $home;
 		$this->users[$email]["home"] = $home;
-		$this->users[$email]["session"] = hash("sha256", $email.$password.$password.$email);
 	}
 	}
 	
 	
 	// Create or update user in file
 	// 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);
 		$email = strreplaceu(',', '-', $email);
-		$password = hash("sha256", $email.$password);
+		$hash = strreplaceu(',', '-', $hash);
 		$fileNewUser = true;
 		$fileNewUser = true;
 		$fileData = @file($fileName);
 		$fileData = @file($fileName);
 		if($fileData)
 		if($fileData)
@@ -345,7 +355,7 @@ class YellowWebinterfaceUsers
 						$name = strreplaceu(',', '-', empty($name) ? $matches[3] : $name);
 						$name = strreplaceu(',', '-', empty($name) ? $matches[3] : $name);
 						$language = strreplaceu(',', '-', empty($language) ? $matches[4] : $language);
 						$language = strreplaceu(',', '-', empty($language) ? $matches[4] : $language);
 						$home = strreplaceu(',', '-', empty($home) ? $matches[5] : $home);
 						$home = strreplaceu(',', '-', empty($home) ? $matches[5] : $home);
-						$fileDataNew .= "$email,$password,$name,$language,$home\n";
+						$fileDataNew .= "$email,$hash,$name,$language,$home\n";
 						$fileNewUser = false;
 						$fileNewUser = false;
 						continue;
 						continue;
 					}
 					}
@@ -358,7 +368,7 @@ class YellowWebinterfaceUsers
 			$name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
 			$name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
 			$language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
 			$language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
 			$home = strreplaceu(',', '-', empty($home) ? $this->yellow->config->get("webinterfaceUserHome") : $home);
 			$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);
 		return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
 	}
 	}
@@ -366,7 +376,8 @@ class YellowWebinterfaceUsers
 	// Check user login
 	// Check user login
 	function checkUser($email, $password)
 	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
 	// Create browser cookie
@@ -374,30 +385,30 @@ class YellowWebinterfaceUsers
 	{
 	{
 		if($this->isExisting($email))
 		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
 	// Destroy browser cookie
 	function destroyCookie($cookieName)
 	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
 	// Return user name

+ 83 - 30
system/core/core.php

@@ -5,7 +5,7 @@
 // Yellow main class
 // Yellow main class
 class Yellow
 class Yellow
 {
 {
-	const Version = "0.2.17";
+	const Version = "0.2.18";
 	var $page;				//current page
 	var $page;				//current page
 	var $pages;				//pages from file system
 	var $pages;				//pages from file system
 	var $config;			//configuration
 	var $config;			//configuration
@@ -65,7 +65,7 @@ class Yellow
 			if(method_exists($value["obj"], "onRequest"))
 			if(method_exists($value["obj"], "onRequest"))
 			{
 			{
 				$this->pages->requestHandler = $key;
 				$this->pages->requestHandler = $key;
-				$statusCode = $value["obj"]->onRequest($location);
+				$statusCode = $value["obj"]->onRequest($serverName, $serverBase, $location, $fileName);
 				if($statusCode != 0) break;
 				if($statusCode != 0) break;
 			}
 			}
 		}
 		}
@@ -1527,18 +1527,18 @@ class YellowToolbox
 	{
 	{
 		switch($statusCode)
 		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;
 		return $text;
 	}
 	}
@@ -1733,21 +1733,6 @@ class YellowToolbox
 		if(preg_match("/^.*\/([\w\-]+)/", $text, $matches)) $text = ucfirst($matches[1]);
 		if(preg_match("/^.*\/([\w\-]+)/", $text, $matches)) $text = ucfirst($matches[1]);
 		return $text;
 		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
 	// Detect PNG and JPG image dimensions
 	function detectImageDimensions($fileName)
 	function detectImageDimensions($fileName)
@@ -1788,7 +1773,75 @@ class YellowToolbox
 		}
 		}
 		return array($width, $height);
 		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
 	// Start timer
 	function timerStart(&$time)
 	function timerStart(&$time)
 	{
 	{