Make Kanboard compatible with PHP 5.3.3

This commit is contained in:
Frederic Guillot 2015-01-18 21:55:08 -05:00
parent 4b89b90df2
commit 2bdd6a6b35
7 changed files with 242 additions and 48 deletions

5
Vagrantfile vendored
View File

@ -65,6 +65,11 @@ Vagrant.configure("2") do |config|
m.vm.synced_folder ".", "/var/www/html", owner: "apache", group: "apache"
end
config.vm.define "centos65" do |m|
m.vm.box = "chef/centos-6.5"
m.vm.synced_folder ".", "/var/www/html", owner: "apache", group: "apache"
end
config.vm.define "freebsd10" do |m|
m.vm.box = "chef/freebsd-10.0"
m.vm.synced_folder ".", "/usr/local/www/apache24/data", type: "rsync", owner: "www", group: "www"

227
app/Libs/password.php Normal file
View File

@ -0,0 +1,227 @@
<?php
/**
* A Compatibility library with PHP 5.5's simplified password hashing API.
*
* @author Anthony Ferrara <ircmaxell@php.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @copyright 2012 The Authors
*/
if (!defined('PASSWORD_BCRYPT')) {
define('PASSWORD_BCRYPT', 1);
define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
if (version_compare(PHP_VERSION, '5.3.7', '<')) {
define('PASSWORD_PREFIX', '$2a$');
}
else {
define('PASSWORD_PREFIX', '$2y$');
}
/**
* Hash the password using the specified algorithm
*
* @param string $password The password to hash
* @param int $algo The algorithm to use (Defined by PASSWORD_* constants)
* @param array $options The options for the algorithm to use
*
* @return string|false The hashed password, or false on error.
*/
function password_hash($password, $algo, array $options = array()) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
return null;
}
if (!is_string($password)) {
trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
return null;
}
if (!is_int($algo)) {
trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
return null;
}
switch ($algo) {
case PASSWORD_BCRYPT:
// Note that this is a C constant, but not exposed to PHP, so we don't define it here.
$cost = 10;
if (isset($options['cost'])) {
$cost = $options['cost'];
if ($cost < 4 || $cost > 31) {
trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
return null;
}
}
$required_salt_len = 22;
$hash_format = sprintf("%s%02d$", PASSWORD_PREFIX, $cost);
break;
default:
trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
return null;
}
if (isset($options['salt'])) {
switch (gettype($options['salt'])) {
case 'NULL':
case 'boolean':
case 'integer':
case 'double':
case 'string':
$salt = (string) $options['salt'];
break;
case 'object':
if (method_exists($options['salt'], '__tostring')) {
$salt = (string) $options['salt'];
break;
}
case 'array':
case 'resource':
default:
trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
return null;
}
if (strlen($salt) < $required_salt_len) {
trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
return null;
} elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
$salt = str_replace('+', '.', base64_encode($salt));
}
} else {
$buffer = '';
$raw_length = (int) ($required_salt_len * 3 / 4 + 1);
$buffer_valid = false;
if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
$buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
$buffer = openssl_random_pseudo_bytes($raw_length);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && is_readable('/dev/urandom')) {
$f = fopen('/dev/urandom', 'r');
$read = strlen($buffer);
while ($read < $raw_length) {
$buffer .= fread($f, $raw_length - $read);
$read = strlen($buffer);
}
fclose($f);
if ($read >= $raw_length) {
$buffer_valid = true;
}
}
if (!$buffer_valid || strlen($buffer) < $raw_length) {
$bl = strlen($buffer);
for ($i = 0; $i < $raw_length; $i++) {
if ($i < $bl) {
$buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
} else {
$buffer .= chr(mt_rand(0, 255));
}
}
}
$salt = str_replace('+', '.', base64_encode($buffer));
}
$salt = substr($salt, 0, $required_salt_len);
$hash = $hash_format . $salt;
$ret = crypt($password, $hash);
if (!is_string($ret) || strlen($ret) <= 13) {
return false;
}
return $ret;
}
/**
* Get information about the password hash. Returns an array of the information
* that was used to generate the password hash.
*
* array(
* 'algo' => 1,
* 'algoName' => 'bcrypt',
* 'options' => array(
* 'cost' => 10,
* ),
* )
*
* @param string $hash The password hash to extract info from
*
* @return array The array of information about the hash.
*/
function password_get_info($hash) {
$return = array(
'algo' => 0,
'algoName' => 'unknown',
'options' => array(),
);
if (substr($hash, 0, 4) == PASSWORD_PREFIX && strlen($hash) == 60) {
$return['algo'] = PASSWORD_BCRYPT;
$return['algoName'] = 'bcrypt';
list($cost) = sscanf($hash, PASSWORD_PREFIX."%d$");
$return['options']['cost'] = $cost;
}
return $return;
}
/**
* Determine if the password hash needs to be rehashed according to the options provided
*
* If the answer is true, after validating the password using password_verify, rehash it.
*
* @param string $hash The hash to test
* @param int $algo The algorithm used for new password hashes
* @param array $options The options array passed to password_hash
*
* @return boolean True if the password needs to be rehashed.
*/
function password_needs_rehash($hash, $algo, array $options = array()) {
$info = password_get_info($hash);
if ($info['algo'] != $algo) {
return true;
}
switch ($algo) {
case PASSWORD_BCRYPT:
$cost = isset($options['cost']) ? $options['cost'] : 10;
if ($cost != $info['options']['cost']) {
return true;
}
break;
}
return false;
}
/**
* Verify a password against a hash using a timing attack resistant approach
*
* @param string $password The password to verify
* @param string $hash The hash to verify against
*
* @return boolean If the password matches the hash
*/
function password_verify($password, $hash) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
return false;
}
$ret = crypt($password, $hash);
if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
return false;
}
$status = 0;
for ($i = 0; $i < strlen($ret); $i++) {
$status |= (ord($ret[$i]) ^ ord($hash[$i]));
}
return $status === 0;
}
}

View File

@ -1,8 +1,8 @@
<?php
// PHP 5.3.3 minimum
if (version_compare(PHP_VERSION, '5.3.7', '<')) {
die('This software require PHP 5.3.7 minimum');
if (version_compare(PHP_VERSION, '5.3.3', '<')) {
die('This software require PHP 5.3.3 minimum');
}
// Checks for PHP < 5.4

View File

@ -1,7 +1,6 @@
{
"require": {
"ext-mbstring": "*",
"ircmaxell/password-compat": "1.0.3",
"fguillot/simple-validator": "0.0.1",
"swiftmailer/swiftmailer": "@stable",
"fguillot/json-rpc": "0.0.1",
@ -16,7 +15,8 @@
"autoload": {
"psr-0": {"": "app/"},
"files": [
"app/functions.php"
"app/functions.php",
"app/Libs/password.php"
]
},
"require-dev": {

41
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "612189500b5effe5179e83afa3d3256c",
"hash": "b20bc90f39f04bf9d6828329d56cdc75",
"packages": [
{
"name": "erusev/parsedown",
@ -193,45 +193,6 @@
"homepage": "https://github.com/fguillot/simpleLogger",
"time": "2015-01-02 03:40:21"
},
{
"name": "ircmaxell/password-compat",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/password_compat.git",
"reference": "1fc1521b5e9794ea77e4eca30717be9635f1d4f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/1fc1521b5e9794ea77e4eca30717be9635f1d4f4",
"reference": "1fc1521b5e9794ea77e4eca30717be9635f1d4f4",
"shasum": ""
},
"type": "library",
"autoload": {
"files": [
"lib/password.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anthony Ferrara",
"email": "ircmaxell@ircmaxell.com",
"homepage": "http://blog.ircmaxell.com"
}
],
"description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash",
"homepage": "https://github.com/ircmaxell/password_compat",
"keywords": [
"hashing",
"password"
],
"time": "2013-04-30 19:58:08"
},
{
"name": "lusitanian/oauth",
"version": "v0.3.5",

View File

@ -24,8 +24,6 @@ rm kanboard-latest.zip
Debian 6 (Squeeze)
------------------
**Kanboard >= 1.0.10 require at least PHP 5.3.7 and Debian 6 provide PHP 5.3.3 by default**
Install Apache and PHP:
```bash

View File

@ -45,14 +45,17 @@ The project [eAccelerator seems dead and not updated since 2012](https://github.
We recommend to switch to the last version of PHP because it's bundled with [OPcache](http://php.net/manual/en/intro.opcache.php).
Why the minimum requirement is PHP 5.3.7?
Why the minimum requirement is PHP 5.3.3?
-----------------------------------------
Kanboard use the function `password_hash()` to crypt passwords but it's available only for PHP >= 5.5.
However, there is a backport for [older versions of PHP](https://github.com/ircmaxell/password_compat#requirements).
This library require at least PHP 5.3.7 to work correctly.
This library require at least PHP 5.3.7 to work correctly.
Apparently, Centos and Debian backports security patches so PHP 5.3.3 should be ok.
Kanboard v1.0.10 and v1.0.11 requires at least PHP 5.3.7 but this change has been reverted to be compatible with PHP 5.3.3 with Kanboard >= v1.0.12
How to test Kanboard with the PHP built-in web server?
------------------------------------------------------