diff --git a/Core/Frameworks/Baikal/Core/BcryptAuth.php b/Core/Frameworks/Baikal/Core/BcryptAuth.php new file mode 100755 index 00000000..4a53a639 --- /dev/null +++ b/Core/Frameworks/Baikal/Core/BcryptAuth.php @@ -0,0 +1,84 @@ + + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class BcryptAuth extends \Sabre\DAV\Auth\Backend\AbstractBasic { + + /** + * Reference to PDO connection + * + * @var PDO + */ + protected $pdo; + + /** + * PDO table name we'll be using + * + * @var string + */ + protected $tableName; + + /** + * Authentication realm + * + * @var string + */ + protected $authRealm; + + /** + * Creates the backend object. + * + * If the filename argument is passed in, it will parse out the specified file fist. + * + * @param PDO $pdo + * @param string $tableName The PDO table name to use + */ + function __construct(\PDO $pdo, $authRealm, $tableName = 'users') { + + $this->pdo = $pdo; + $this->tableName = $tableName; + $this->authRealm = $authRealm; + } + + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * @return bool + */ + function validateUserPass($username, $password) { + + $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM ' . $this->tableName . ' WHERE username = ?'); + $stmt->execute([$username]); + $result = $stmt->fetchAll(); + if (!count($result)) return false; + + if (substr($result[0]['digesta1'], 0, 4) == '$2y$') { + $check = password_verify($password, $result[0]['digesta1']); + } else { + $hash = md5($username . ':' . $this->authRealm . ':' . $password); + if ($result[0]['digesta1'] === $hash) { + $check = true; + } + } + if ($check == true) { + return true; + } + return false; + + } + +} diff --git a/Core/Frameworks/Baikal/Core/Server.php b/Core/Frameworks/Baikal/Core/Server.php old mode 100644 new mode 100755 index b5792bad..eccc633d --- a/Core/Frameworks/Baikal/Core/Server.php +++ b/Core/Frameworks/Baikal/Core/Server.php @@ -132,10 +132,10 @@ function start() { protected function initServer() { if ($this->authType === 'Basic') { - $authBackend = new \Baikal\Core\PDOBasicAuth($this->pdo, $this->authRealm); + $authBackend = new \Baikal\Core\BcryptAuth($this->pdo, $this->authRealm); } else { - $authBackend = new \Sabre\DAV\Auth\Backend\PDO($this->pdo); - $authBackend->setRealm($this->authRealm); + $authBackend = new \Sabre\DAV\Auth\Backend\PDO($this->pdo); + $authBackend->setRealm($this->authRealm); } $principalBackend = new \Sabre\DAVACL\PrincipalBackend\PDO($this->pdo); diff --git a/Core/Frameworks/Baikal/Core/Tools.php b/Core/Frameworks/Baikal/Core/Tools.php index 9c1caae0..6b1fa30d 100644 --- a/Core/Frameworks/Baikal/Core/Tools.php +++ b/Core/Frameworks/Baikal/Core/Tools.php @@ -27,15 +27,18 @@ namespace Baikal\Core; -class Tools { - static function &db() { - return $GLOBALS["pdo"]; +class Tools +{ + static function &db() + { + return $GLOBALS['pdo']; } - static function assertEnvironmentIsOk() { + static function assertEnvironmentIsOk() + { # Asserting Baikal Context - if (!defined("BAIKAL_CONTEXT") || BAIKAL_CONTEXT !== true) { - die("Bootstrap.php may not be included outside the Baikal context"); + if (!defined('BAIKAL_CONTEXT') || BAIKAL_CONTEXT !== true) { + die('Bootstrap.php may not be included outside the Baikal context'); } # Asserting PDO @@ -50,21 +53,24 @@ static function assertEnvironmentIsOk() { } } - static function configureEnvironment() { + static function configureEnvironment() + { set_exception_handler('\Baikal\Core\Tools::handleException'); - ini_set("error_reporting", E_ALL); + ini_set('error_reporting', E_ALL); } - static function handleException($exception) { - echo "
" . $exception . ""; + static function handleException($exception) + { + echo '' . $exception . ''; } - static function assertBaikalIsOk() { + static function assertBaikalIsOk() + { # DB connexion has not been asserted earlier by Flake, to give us a chance to trigger the install tool # We assert it right now - if (!\Flake\Framework::isDBInitialized() && (!defined("BAIKAL_CONTEXT_INSTALL") || BAIKAL_CONTEXT_INSTALL === false)) { - throw new \Exception("Fatal error: no connection to a database is available."); + if (!\Flake\Framework::isDBInitialized() && (!defined('BAIKAL_CONTEXT_INSTALL') || BAIKAL_CONTEXT_INSTALL === false)) { + throw new \Exception('Fatal error: no connection to a database is available.'); } # Asserting that the database is structurally complete @@ -73,51 +79,52 @@ static function assertBaikalIsOk() { #} # Asserting config file exists - if (!file_exists(PROJECT_PATH_SPECIFIC . "config.php")) { - throw new \Exception("Specific/config.php does not exist. Please use the Install tool to create it."); + if (!file_exists(PROJECT_PATH_SPECIFIC . 'config.php')) { + throw new \Exception('Specific/config.php does not exist. Please use the Install tool to create it.'); } # Asserting config file is readable - if (!is_readable(PROJECT_PATH_SPECIFIC . "config.php")) { + if (!is_readable(PROJECT_PATH_SPECIFIC . 'config.php')) { throw new \Exception("Specific/config.php is not readable. Please give read permissions to httpd user on file 'Specific/config.php'."); } # Asserting config file is writable - if (!is_writable(PROJECT_PATH_SPECIFIC . "config.php")) { + if (!is_writable(PROJECT_PATH_SPECIFIC . 'config.php')) { throw new \Exception("Specific/config.php is not writable. Please give write permissions to httpd user on file 'Specific/config.php'."); } # Asserting system config file exists - if (!file_exists(PROJECT_PATH_SPECIFIC . "config.system.php")) { - throw new \Exception("Specific/config.system.php does not exist. Please use the Install tool to create it."); + if (!file_exists(PROJECT_PATH_SPECIFIC . 'config.system.php')) { + throw new \Exception('Specific/config.system.php does not exist. Please use the Install tool to create it.'); } # Asserting system config file is readable - if (!is_readable(PROJECT_PATH_SPECIFIC . "config.system.php")) { + if (!is_readable(PROJECT_PATH_SPECIFIC . 'config.system.php')) { throw new \Exception("Specific/config.system.php is not readable. Please give read permissions to httpd user on file 'Specific/config.system.php'."); } # Asserting system config file is writable - if (!is_writable(PROJECT_PATH_SPECIFIC . "config.system.php")) { + if (!is_writable(PROJECT_PATH_SPECIFIC . 'config.system.php')) { throw new \Exception("Specific/config.system.php is not writable. Please give write permissions to httpd user on file 'Specific/config.system.php'."); } } - static function getRequiredTablesList() { + static function getRequiredTablesList() + { return [ - "addressbooks", - "calendarobjects", - "calendars", - "cards", - "groupmembers", - "locks", - "principals", - "users", + 'addressbooks', + 'calendarobjects', + 'calendars', + 'cards', + 'groupmembers', + 'locks', + 'principals', + 'users', ]; } - static function isDBStructurallyComplete(\Flake\Core\Database $oDB) { - + static function isDBStructurallyComplete(\Flake\Core\Database $oDB) + { $aRequiredTables = self::getRequiredTablesList(); $aPresentTables = $oDB->tables(); @@ -129,19 +136,23 @@ static function isDBStructurallyComplete(\Flake\Core\Database $oDB) { return true; } - static function bashPrompt($prompt) { + static function bashPrompt($prompt) + { echo $prompt; @flush(); @ob_flush(); $confirmation = @trim(fgets(STDIN)); + return $confirmation; } - static function bashPromptSilent($prompt = "Enter Password:") { + static function bashPromptSilent($prompt = 'Enter Password:') + { $command = "/usr/bin/env bash -c 'echo OK'"; if (rtrim(shell_exec($command)) !== 'OK') { trigger_error("Can't invoke bash"); + return; } @@ -151,20 +162,21 @@ static function bashPromptSilent($prompt = "Enter Password:") { $password = rtrim(shell_exec($command)); echo "\n"; + return $password; } - static function getCopyrightNotice($sLinePrefixChar = "#", $sLineSuffixChar = "", $sOpening = false, $sClosing = false) { - + static function getCopyrightNotice($sLinePrefixChar = '#', $sLineSuffixChar = '', $sOpening = false, $sClosing = false) + { if ($sOpening === false) { - $sOpening = str_repeat("#", 78); + $sOpening = str_repeat('#', 78); } if ($sClosing === false) { - $sClosing = str_repeat("#", 78); + $sClosing = str_repeat('#', 78); } - $iYear = date("Y"); + $iYear = date('Y'); $sCode = <<[ "type" => "string", - "comment" => "HTTP authentication type for WebDAV; default Digest" + "comment" => "HTTP authentication type for WebDAV; default Basic" + ], + "BAIKAL_USER_AUTH_TYPE" => [ + "type" => "string", + "comment" => "Authentication mechanism for user accounts" ], "BAIKAL_ADMIN_PASSWORDHASH" => [ "type" => "string", @@ -62,7 +66,8 @@ class Standard extends \Baikal\Model\Config { "BAIKAL_CARD_ENABLED" => true, "BAIKAL_CAL_ENABLED" => true, "BAIKAL_INVITE_FROM" => "", - "BAIKAL_DAV_AUTH_TYPE" => "Digest", + "BAIKAL_DAV_AUTH_TYPE" => "Basic", + "BAIKAL_USER_AUTH_TYPE" => "Bcrypt", "BAIKAL_ADMIN_PASSWORDHASH" => "" ]; @@ -96,7 +101,14 @@ function formMorphologyForThisModelInstance() { $oMorpho->add(new \Formal\Element\Listbox([ "prop" => "BAIKAL_DAV_AUTH_TYPE", "label" => "WebDAV authentication type", - "options" => ["Digest", "Basic"] + "options" => ["Basic", "Digest"] + ])); + + $oMorpho->add(new \Formal\Element\Listbox([ + "prop" => "BAIKAL_USER_AUTH_TYPE", + "label" => "Password Storage Hash Type", + "options" => ["Bcrypt", "MD5"], + "help" => "If set to BCrypt, WebDAV must be set to BASIC." ])); $oMorpho->add(new \Formal\Element\Password([ @@ -132,14 +144,13 @@ function set($sProp, $sValue) { # Special handling for password and passwordconfirm if ($sProp === "BAIKAL_ADMIN_PASSWORDHASH" && $sValue !== "") { - parent::set( - "BAIKAL_ADMIN_PASSWORDHASH", - \BaikalAdmin\Core\Auth::hashAdminPassword($sValue) - ); - } - + parent::set( + "BAIKAL_ADMIN_PASSWORDHASH", + password_hash($sValue, PASSWORD_BCRYPT) + ); + } return $this; - } + } parent::set($sProp, $sValue); } @@ -191,8 +202,12 @@ protected static function getDefaultConfig() { # CalDAV invite From: mail address (comment or leave blank to disable notifications) define("BAIKAL_INVITE_FROM", "noreply@$_SERVER[SERVER_NAME]"); -# WebDAV authentication type; default Digest -define("BAIKAL_DAV_AUTH_TYPE", "Digest"); +# WebDAV authentication type; default Basic +define("BAIKAL_DAV_AUTH_TYPE", "Basic"); + + +# Baikal user password hash method; default bcrypt +define("BAIKAL_USER_AUTH_TYPE", "Bcrypt"); # Baïkal Web admin password hash; Set via Baïkal Web Admin define("BAIKAL_ADMIN_PASSWORDHASH", ""); diff --git a/Core/Frameworks/Baikal/Model/User.php b/Core/Frameworks/Baikal/Model/User.php old mode 100644 new mode 100755 index f744f998..ebcb6596 --- a/Core/Frameworks/Baikal/Model/User.php +++ b/Core/Frameworks/Baikal/Model/User.php @@ -104,10 +104,17 @@ function set($sPropName, $sPropValue) { # Special handling for password and passwordconfirm if ($sPropName === "password" && $sPropValue !== "") { - parent::set( - "digesta1", - $this->getPasswordHashForPassword($sPropValue) - ); + if (BAIKAL_USER_AUTH_TYPE === "Bcrypt") { + parent::set( + "digesta1", + password_hash($sPropValue, PASSWORD_BCRYPT) + ); + } else { + parent::set( + "digesta1", + $this->getPasswordHashForPassword($sPropValue) + ); + } } return $this; diff --git a/Core/Frameworks/BaikalAdmin/Core/Auth.php b/Core/Frameworks/BaikalAdmin/Core/Auth.php old mode 100644 new mode 100755 index 5f4e81c7..49367194 --- a/Core/Frameworks/BaikalAdmin/Core/Auth.php +++ b/Core/Frameworks/BaikalAdmin/Core/Auth.php @@ -45,9 +45,18 @@ static function authenticate() { $sUser = \Flake\Util\Tools::POST("login"); $sPass = \Flake\Util\Tools::POST("password"); + if (substr(BAIKAL_ADMIN_PASSWORDHASH, 0, 4) == "$2y$") { + $sPassHash = password_verify($sPass, BAIKAL_ADMIN_PASSWORDHASH); + } else { $sPassHash = self::hashAdminPassword($sPass); + if ($sPassHash === BAIKAL_ADMIN_PASSWORDHASH) { + $sPassHash = true; + } else { + $sPassHash = false; + } + } - if ($sUser === "admin" && $sPassHash === BAIKAL_ADMIN_PASSWORDHASH) { + if ($sUser === "admin" && $sPassHash == true) { $_SESSION["baikaladminauth"] = md5(BAIKAL_ADMIN_PASSWORDHASH); return true; } @@ -66,7 +75,7 @@ static function hashAdminPassword($sPassword) { } else { $sAuthRealm = "BaikalDAV"; # Fallback to default value; useful when initializing App, as all constants are not set yet } - + return md5('admin:' . $sAuthRealm . ':' . $sPassword); }