summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Roach <fisharebest@gmail.com>2014-04-27 23:19:28 +0100
committerGreg Roach <fisharebest@gmail.com>2014-04-27 23:19:28 +0100
commit538464e388dd356cbac367b6864366ebb0c2f35a (patch)
tree0c38e05d122248938d488a9a57260567ccb13293
parentaf5114aed4183d8569bfbe45a9a2a0b4fe96f514 (diff)
downloadwebtrees-538464e388dd356cbac367b6864366ebb0c2f35a.tar.gz
webtrees-538464e388dd356cbac367b6864366ebb0c2f35a.tar.bz2
webtrees-538464e388dd356cbac367b6864366ebb0c2f35a.zip
Create WT_User class, to provide access to user objects/functions
-rw-r--r--composer.json1
-rw-r--r--composer.lock41
-rw-r--r--library/WT/User.php518
-rw-r--r--library/autoload.php2
-rw-r--r--library/composer/autoload_classmap.php7
-rw-r--r--library/composer/autoload_files.php1
-rw-r--r--library/composer/autoload_real.php10
-rw-r--r--library/composer/installed.json61
-rw-r--r--library/ircmaxell/password-compat/.travis.yml8
-rw-r--r--library/ircmaxell/password-compat/LICENSE.md7
-rw-r--r--library/ircmaxell/password-compat/README.md75
-rw-r--r--library/ircmaxell/password-compat/composer.json18
-rw-r--r--library/ircmaxell/password-compat/lib/password.php222
-rw-r--r--library/ircmaxell/password-compat/phpunit.xml.dist29
-rw-r--r--library/ircmaxell/password-compat/test/Unit/PasswordGetInfoTest.php26
-rw-r--r--library/ircmaxell/password-compat/test/Unit/PasswordHashTest.php84
-rw-r--r--library/ircmaxell/password-compat/test/Unit/PasswordNeedsRehashTest.php26
-rw-r--r--library/ircmaxell/password-compat/test/Unit/PasswordVerifyTest.php29
-rw-r--r--library/ircmaxell/password-compat/version-test.php8
19 files changed, 795 insertions, 378 deletions
diff --git a/composer.json b/composer.json
index b56f36005b..17ed793431 100644
--- a/composer.json
+++ b/composer.json
@@ -16,6 +16,7 @@
"require": {
"bombayworks/zendframework1": "*",
"ezyang/htmlpurifier": "*",
+ "ircmaxell/password-compat": "*",
"michelf/php-markdown": "*",
"patchwork/utf8": "*",
"tecnick.com/tcpdf": "*"
diff --git a/composer.lock b/composer.lock
index 5f3c518fcd..96722fbe56 100644
--- a/composer.lock
+++ b/composer.lock
@@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
- "hash": "ee294f74bbc203e88b588cd07880c42c",
+ "hash": "e45b6fb68c9db8d3398bd31d828eed57",
"packages": [
{
"name": "bombayworks/zendframework1",
@@ -104,6 +104,45 @@
"time": "2013-11-30 08:25:19"
},
{
+ "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@php.net",
+ "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": "michelf/php-markdown",
"version": "1.4.0",
"source": {
diff --git a/library/WT/User.php b/library/WT/User.php
index ca7c188744..6b5239426b 100644
--- a/library/WT/User.php
+++ b/library/WT/User.php
@@ -1,5 +1,5 @@
<?php
-// Provide an interface to the wt_gedcom table
+// Provide an interface to the wt_user table
//
// webtrees: Web based Family History software
// Copyright (C) 2014 webtrees development team
@@ -23,409 +23,211 @@ if (!defined('WT_WEBTREES')) {
exit;
}
-class WT_Tree {
- // Tree attributes
- public $tree_id =null; // The "gedcom ID" number
- public $tree_name =null; // The "gedcom name" text
- public $tree_name_url =null;
- public $tree_name_html =null;
- public $tree_title =null; // The "gedcom title" text
- public $tree_title_html =null;
- public $imported =null;
+class WT_User {
+ // Reasons why a user authentication attempt failed
+ const ERROR_ACCOUNT_NOT_VERIFIED = -1;
+ const ERROR_ACCOUNT_NOT_APPROVED = -2;
+ const ERROR_INCORRECT_PASSWORD = -3;
+ const ERROR_NO_SUCH_USER = -4;
+ const ERROR_NO_SESSION_COOKIES = -5;
+ // Reasons why a user account cannot be created
+ const ERROR_DUPLICATE_USER_NAME = -6;
+ const ERROR_DUPLICATE_EMAIL = -7;
- // List of all trees
- private static $trees =null;
+ // Attributes of the user, from the wt_user table
+ private $user_id;
+ private $user_name;
+ private $real_name;
+ private $email;
- // Tree settings
- private $preference =null; // wt_gedcom_setting table
- private $user_preference=array(); // wt_user_gedcom_setting table
+ // Settings for the user, from the wt_user_setting table
+ private $settings;
- // Create a tree object. This is a private constructor - it can only
- // be called from WT_Tree::getAll() to ensure proper initialisation.
- private function __construct($tree_id, $tree_name, $tree_title, $imported) {
- if (strpos($tree_title, '%')===false) {
- // Allow users to translate tree titles.
- //$tree_title=WT_I18N::Translate($tree_title);
- }
- $this->tree_id =$tree_id;
- $this->tree_name =$tree_name;
- $this->tree_name_url =rawurlencode($tree_name);
- $this->tree_name_html =WT_Filter::escapeHtml($tree_name);
- $this->tree_title =$tree_title;
- $this->tree_title_html='<span dir="auto">' . WT_Filter::escapeHtml($tree_title) . '</span>';
- $this->imported =$imported;
+ public function __construct($user_id) {
+ $row = WT_DB::prepare(
+ "SELECT SQL_CACHE user_name, real_name, email FROM `##user` WHERE user_id = ?"
+ )->execute(array($user_id))->fetchOneRow();
+
+ $this->user_id = $user_id;
+ $this->user_name = $row->user_name;
+ $this->real_name = $row->real_name;
+ $this->email = $row->email;
}
- // Get and Set the tree’s configuration settings
- public function preference($setting_name, $setting_value=null) {
- // There are lots of settings, and we need to fetch lots of them on every page
- // so it is quicker to fetch them all in one go.
- if ($this->preference===null) {
- $this->preference=WT_DB::prepare(
- "SELECT SQL_CACHE setting_name, setting_value FROM `##gedcom_setting` WHERE gedcom_id=?"
- )->execute(array($this->tree_id))->fetchAssoc();
- }
+ // Create a new user.
+ //
+ // On success, return the user_id of the account.
+ // On failure, return the reason for failure.
+ public function create($user_name, $real_name, $email, $password) {
+ self::passwordCompatibility();
- // If $setting_value is null, then GET the setting
- if ($setting_value===null) {
- // If parameter two is not specified, GET the setting
- if (!array_key_exists($setting_name, $this->preference)) {
- $this->preference[$setting_name]=null;
- }
- return $this->preference[$setting_name];
- } else {
- // If parameter two is specified, then SET the setting
- if ($this->preference($setting_name)!=$setting_value) {
- $this->preference[$setting_name]=$setting_value;
- // Audit log of changes
- AddToLog('Gedcom setting "'.$setting_name.'" set to "'.$setting_value.'"', 'config');
- }
- WT_DB::prepare(
- "REPLACE INTO `##gedcom_setting` (gedcom_id, setting_name, setting_value) VALUES (?, ?, LEFT(?, 255))"
- )->execute(array($this->tree_id, $setting_name, $setting_value));
- return $this;
+ if (WT_DB::prepare("SELECT 1 FROM `##user` WHERE user_name = ?")->execute(array($user_name))->fetchOne()) {
+ return self::ERROR_DUPLICATE_USER_NAME;
}
- }
- // Get and Set the tree’s configuration settings
- public function userPreference($user_id, $setting_name, $setting_value=null) {
- // There are lots of settings, and we need to fetch lots of them on every page
- // so it is quicker to fetch them all in one go.
- if (!array_key_exists($user_id, $this->user_preference)) {
- $this->user_preference[$user_id]=WT_DB::prepare(
- "SELECT SQL_CACHE setting_name, setting_value FROM `##user_gedcom_setting` WHERE user_id=? AND gedcom_id=?"
- )->execute(array($user_id, $this->tree_id))->fetchAssoc();
+ if (WT_DB::prepare("SELECT 1 FROM `##user` WHERE email = ?")->execute(array($email))->fetchOne()) {
+ return self::ERROR_DUPLICATE_EMAIL;
}
- // If $setting_value is null, then GET the setting
- if ($setting_value===null) {
- // If parameter two is not specified, GET the setting
- if (!array_key_exists($setting_name, $this->user_preference[$user_id])) {
- $this->user_preference[$user_id][$setting_name]=null;
- }
- return $this->user_preference[$user_id][$setting_name];
- } else {
- // If parameter two is specified, then SET the setting.
- if ($this->preference($setting_name)!=$setting_value) {
- // Audit log of changes
- AddToLog('Gedcom setting "'.$setting_name.'" set to "'.$setting_value.'"', 'config');
- }
- WT_DB::prepare(
- "REPLACE INTO `##user_gedcom_setting` (user_id, gedcom_id, setting_name, setting_value) VALUES (?, ?, ?, LEFT(?, 255))"
- )->execute(array($user_id, $this->tree_id, $setting_name, $setting_value));
- return $this;
- }
- }
+ WT_DB::prepare(
+ "INSERT INTO `##user` (user_name, real_name, email, password) VALUES (?, ?, ?, ?)"
+ )->execute(array($user_name, $real_name, $email, password_hash($password, PASSWORD_DEFAULT)));
- // Can a user accept changes for this tree?
- public function canAcceptChanges($user_id) {
- return
- userIsAdmin($user_id) ||
- $this->userPreference($user_id, 'canedit')=='admin' ||
- $this->userPreference($user_id, 'canedit')=='accept';
+ return WT_DB::prepare("SELECT LAST_INSERT_ID()")->fetchOne();
}
- // Fetch all the trees that we have permission to access.
- public static function getAll() {
- if (self::$trees===null) {
- self::$trees=array();
- $rows=WT_DB::prepare(
- "SELECT SQL_CACHE g.gedcom_id AS tree_id, g.gedcom_name AS tree_name, gs1.setting_value AS tree_title, gs2.setting_value AS imported".
- " FROM `##gedcom` g".
- " LEFT JOIN `##gedcom_setting` gs1 ON (g.gedcom_id=gs1.gedcom_id AND gs1.setting_name='title')".
- " LEFT JOIN `##gedcom_setting` gs2 ON (g.gedcom_id=gs2.gedcom_id AND gs2.setting_name='imported')".
- " LEFT JOIN `##gedcom_setting` gs3 ON (g.gedcom_id=gs3.gedcom_id AND gs3.setting_name='REQUIRE_AUTHENTICATION')".
- " LEFT JOIN `##user_gedcom_setting` ugs ON (g.gedcom_id=ugs.gedcom_id AND ugs.setting_name='canedit' AND ugs.user_id=?)".
- " WHERE ".
- " g.gedcom_id>0 AND (". // exclude the "template" tree
- " EXISTS (SELECT 1 FROM `##user_setting` WHERE user_id=? AND setting_name='canadmin' AND setting_value=1)". // Admin sees all
- " ) OR (".
- " gs2.setting_value = 1 AND (". // Allow imported trees, with either:
- " gs3.setting_value <> 1 OR". // visitor access
- " IFNULL(ugs.setting_value, 'none')<>'none'". // explicit access
- " )".
- " )".
- " ORDER BY g.sort_order, 3"
- )->execute(array(WT_USER_ID, WT_USER_ID))->fetchAll();
- foreach ($rows as $row) {
- self::$trees[$row->tree_id]=new WT_Tree($row->tree_id, $row->tree_name, $row->tree_title, $row->imported);
- }
- }
- return self::$trees;
+ // Getters and setters for user attributes
+ public function getUserId() {
+ return $this->user_id;
}
- // Get the tree with a specific ID. TODO - is this function needed long-term, or just while
- // we integrate this class into the rest of the code?
- public static function get($tree_id) {
- $trees=self::getAll();
- return $trees[$tree_id];
+ public function getUserName() {
+ return $this->user_name;
}
- // Create arguments to select_edit_control()
- // Note - these will be escaped later
- public static function getIdList() {
- $list=array();
- foreach (self::getAll() as $tree) {
- $list[$tree->tree_id]=$tree->tree_title;
- }
- return $list;
+ public function setUserName($user_name) {
+ $this->user_name = $user_name;
+ WT_DB::prepare(
+ "UPDATE `##user` SET user_name = ? WHERE user_id = ?"
+ )->execute(array($user_name, $this->user_id));
}
- // Create arguments to select_edit_control()
- // Note - these will be escaped later
- public static function getNameList() {
- $list=array();
- foreach (self::getAll() as $tree) {
- $list[$tree->tree_name]=$tree->tree_title;
- }
- return $list;
+ public function getRealName() {
+ return $this->real_name;
}
- public static function getIdFromName($tree_name) {
- foreach (self::getAll() as $tree_id=>$tree) {
- if ($tree->tree_name==$tree_name) {
- return $tree_id;
- }
- }
- return null;
+ public function setRealName($real_name) {
+ $this->real_name = $real_name;
+ WT_DB::prepare(
+ "UPDATE `##user` SET real_name = ? WHERE user_id = ?"
+ )->execute(array($real_name, $this->user_id));
}
- public static function getNameFromId($tree_id) {
- return self::get($tree_id)->tree_name;
+ public function getEmail() {
+ return $this->email;
}
- // Create a new tree
- public static function create($tree_name) {
- try {
- // Create a new tree
- WT_DB::prepare(
- "INSERT INTO `##gedcom` (gedcom_name) VALUES (?)"
- )->execute(array($tree_name));
- $tree_id=WT_DB::prepare("SELECT LAST_INSERT_ID()")->fetchOne();
- } catch (PDOException $ex) {
- // A tree with that name already exists?
- return;
- }
+ public function setEmail($email) {
+ $this->email = $email;
+ WT_DB::prepare(
+ "UPDATE `##user` SET email = ? WHERE user_id = ?"
+ )->execute(array($email, $this->user_id));
+ }
- // Update the list of trees - to include this new one
- self::$trees=null;
+ public function setPassword($password) {
+ self::passwordCompatibility();
- // Module privacy
- WT_Module::setDefaultAccess($tree_id);
+ WT_DB::prepare(
+ "UPDATE `##user` SET password = ? WHERE user_id = ?"
+ )->execute(array(password_hash($password, PASSWORD_DEFAULT), $this->user_id));
+ }
- // Gedcom and privacy settings
- set_gedcom_setting($tree_id, 'ADVANCED_NAME_FACTS', 'NICK,_AKA');
- set_gedcom_setting($tree_id, 'ADVANCED_PLAC_FACTS', '');
- set_gedcom_setting($tree_id, 'ALLOW_THEME_DROPDOWN', true);
- set_gedcom_setting($tree_id, 'CALENDAR_FORMAT', 'gregorian');
- set_gedcom_setting($tree_id, 'CHART_BOX_TAGS', '');
- set_gedcom_setting($tree_id, 'COMMON_NAMES_ADD', '');
- set_gedcom_setting($tree_id, 'COMMON_NAMES_REMOVE', '');
- set_gedcom_setting($tree_id, 'COMMON_NAMES_THRESHOLD', '40');
- set_gedcom_setting($tree_id, 'CONTACT_USER_ID', WT_USER_ID);
- set_gedcom_setting($tree_id, 'DEFAULT_PEDIGREE_GENERATIONS', '4');
- set_gedcom_setting($tree_id, 'EXPAND_RELATIVES_EVENTS', false);
- set_gedcom_setting($tree_id, 'EXPAND_SOURCES', false);
- set_gedcom_setting($tree_id, 'FAM_FACTS_ADD', 'CENS,MARR,RESI,SLGS,MARR_CIVIL,MARR_RELIGIOUS,MARR_PARTNERS,RESN');
- set_gedcom_setting($tree_id, 'FAM_FACTS_QUICK', 'MARR,DIV,_NMR');
- set_gedcom_setting($tree_id, 'FAM_FACTS_UNIQUE', 'NCHI,MARL,DIV,ANUL,DIVF,ENGA,MARB,MARC,MARS');
- set_gedcom_setting($tree_id, 'FAM_ID_PREFIX', 'F');
- set_gedcom_setting($tree_id, 'FORMAT_TEXT', 'markdown');
- set_gedcom_setting($tree_id, 'FULL_SOURCES', false);
- set_gedcom_setting($tree_id, 'GEDCOM_ID_PREFIX', 'I');
- set_gedcom_setting($tree_id, 'GEDCOM_MEDIA_PATH', '');
- set_gedcom_setting($tree_id, 'GENERATE_UIDS', false);
- set_gedcom_setting($tree_id, 'HIDE_GEDCOM_ERRORS', true);
- set_gedcom_setting($tree_id, 'HIDE_LIVE_PEOPLE', true);
- set_gedcom_setting($tree_id, 'INDI_FACTS_ADD', 'AFN,BIRT,DEAT,BURI,CREM,ADOP,BAPM,BARM,BASM,BLES,CHRA,CONF,FCOM,ORDN,NATU,EMIG,IMMI,CENS,PROB,WILL,GRAD,RETI,DSCR,EDUC,IDNO,NATI,NCHI,NMR,OCCU,PROP,RELI,RESI,SSN,TITL,BAPL,CONL,ENDL,SLGC,_MILI,ASSO,RESN');
- set_gedcom_setting($tree_id, 'INDI_FACTS_QUICK', 'BIRT,BURI,BAPM,CENS,DEAT,OCCU,RESI');
- set_gedcom_setting($tree_id, 'INDI_FACTS_UNIQUE', '');
- set_gedcom_setting($tree_id, 'KEEP_ALIVE_YEARS_BIRTH', '');
- set_gedcom_setting($tree_id, 'KEEP_ALIVE_YEARS_DEATH', '');
- set_gedcom_setting($tree_id, 'LANGUAGE', WT_LOCALE); // Default to the current admin’s language
- set_gedcom_setting($tree_id, 'MAX_ALIVE_AGE', 120);
- set_gedcom_setting($tree_id, 'MAX_DESCENDANCY_GENERATIONS', '15');
- set_gedcom_setting($tree_id, 'MAX_PEDIGREE_GENERATIONS', '10');
- set_gedcom_setting($tree_id, 'MEDIA_DIRECTORY', 'media/');
- set_gedcom_setting($tree_id, 'MEDIA_ID_PREFIX', 'M');
- set_gedcom_setting($tree_id, 'MEDIA_UPLOAD', WT_PRIV_USER);
- set_gedcom_setting($tree_id, 'META_DESCRIPTION', '');
- set_gedcom_setting($tree_id, 'META_TITLE', WT_WEBTREES);
- set_gedcom_setting($tree_id, 'NOTE_FACTS_ADD', 'SOUR,RESN');
- set_gedcom_setting($tree_id, 'NOTE_FACTS_QUICK', '');
- set_gedcom_setting($tree_id, 'NOTE_FACTS_UNIQUE', '');
- set_gedcom_setting($tree_id, 'NOTE_ID_PREFIX', 'N');
- set_gedcom_setting($tree_id, 'NO_UPDATE_CHAN', false);
- set_gedcom_setting($tree_id, 'PEDIGREE_FULL_DETAILS', true);
- set_gedcom_setting($tree_id, 'PEDIGREE_LAYOUT', true);
- set_gedcom_setting($tree_id, 'PEDIGREE_ROOT_ID', '');
- set_gedcom_setting($tree_id, 'PEDIGREE_SHOW_GENDER', false);
- set_gedcom_setting($tree_id, 'PREFER_LEVEL2_SOURCES', '1');
- set_gedcom_setting($tree_id, 'QUICK_REQUIRED_FACTS', 'BIRT,DEAT');
- set_gedcom_setting($tree_id, 'QUICK_REQUIRED_FAMFACTS', 'MARR');
- set_gedcom_setting($tree_id, 'REPO_FACTS_ADD', 'PHON,EMAIL,FAX,WWW,NOTE,SHARED_NOTE,RESN');
- set_gedcom_setting($tree_id, 'REPO_FACTS_QUICK', '');
- set_gedcom_setting($tree_id, 'REPO_FACTS_UNIQUE', 'NAME,ADDR');
- set_gedcom_setting($tree_id, 'REPO_ID_PREFIX', 'R');
- set_gedcom_setting($tree_id, 'REQUIRE_AUTHENTICATION', false);
- set_gedcom_setting($tree_id, 'SAVE_WATERMARK_IMAGE', false);
- set_gedcom_setting($tree_id, 'SAVE_WATERMARK_THUMB', false);
- set_gedcom_setting($tree_id, 'SHOW_AGE_DIFF', false);
- set_gedcom_setting($tree_id, 'SHOW_COUNTER', true);
- set_gedcom_setting($tree_id, 'SHOW_DEAD_PEOPLE', WT_PRIV_PUBLIC);
- set_gedcom_setting($tree_id, 'SHOW_EST_LIST_DATES', false);
- set_gedcom_setting($tree_id, 'SHOW_FACT_ICONS', true);
- set_gedcom_setting($tree_id, 'SHOW_GEDCOM_RECORD', false);
- set_gedcom_setting($tree_id, 'SHOW_HIGHLIGHT_IMAGES', true);
- set_gedcom_setting($tree_id, 'SHOW_LDS_AT_GLANCE', false);
- set_gedcom_setting($tree_id, 'SHOW_LEVEL2_NOTES', true);
- set_gedcom_setting($tree_id, 'SHOW_LIVING_NAMES', WT_PRIV_USER);
- set_gedcom_setting($tree_id, 'SHOW_MEDIA_DOWNLOAD', false);
- set_gedcom_setting($tree_id, 'SHOW_NO_WATERMARK', WT_PRIV_USER);
- set_gedcom_setting($tree_id, 'SHOW_PARENTS_AGE', true);
- set_gedcom_setting($tree_id, 'SHOW_PEDIGREE_PLACES', '9');
- set_gedcom_setting($tree_id, 'SHOW_PEDIGREE_PLACES_SUFFIX', false);
- set_gedcom_setting($tree_id, 'SHOW_PRIVATE_RELATIONSHIPS', true);
- set_gedcom_setting($tree_id, 'SHOW_RELATIVES_EVENTS', '_BIRT_CHIL,_BIRT_SIBL,_MARR_CHIL,_MARR_PARE,_DEAT_CHIL,_DEAT_PARE,_DEAT_GPAR,_DEAT_SIBL,_DEAT_SPOU');
- set_gedcom_setting($tree_id, 'SHOW_STATS', false);
- set_gedcom_setting($tree_id, 'SOURCE_ID_PREFIX', 'S');
- set_gedcom_setting($tree_id, 'SOUR_FACTS_ADD', 'NOTE,REPO,SHARED_NOTE,RESN');
- set_gedcom_setting($tree_id, 'SOUR_FACTS_QUICK', 'TEXT,NOTE,REPO');
- set_gedcom_setting($tree_id, 'SOUR_FACTS_UNIQUE', 'AUTH,ABBR,TITL,PUBL,TEXT');
- set_gedcom_setting($tree_id, 'SUBLIST_TRIGGER_I', '200');
- set_gedcom_setting($tree_id, 'SURNAME_LIST_STYLE', 'style2');
- switch (WT_LOCALE) {
- case 'es': set_gedcom_setting($tree_id, 'SURNAME_TRADITION', 'spanish'); break;
- case 'is': set_gedcom_setting($tree_id, 'SURNAME_TRADITION', 'icelandic'); break;
- case 'lt': set_gedcom_setting($tree_id, 'SURNAME_TRADITION', 'lithuanian'); break;
- case 'pl': set_gedcom_setting($tree_id, 'SURNAME_TRADITION', 'polish'); break;
- case 'pt':
- case 'pt-BR': set_gedcom_setting($tree_id, 'SURNAME_TRADITION', 'portuguese'); break;
- default: set_gedcom_setting($tree_id, 'SURNAME_TRADITION', 'paternal'); break;
+ // Fetch a user option/setting from the wt_user_setting table
+ //
+ // Since we'll fetch several settings for each user, and since there aren’t
+ // that many of them, fetch them all in one database query
+ public function getSetting($setting_name, $default=null) {
+ if ($this->settings === null) {
+ $this->settings = WT_DB::prepare(
+ "SELECT SQL_CACHE setting_name, setting_value FROM `##user_setting` WHERE user_id = ?"
+ )->execute(array($this->user_id))->fetchAssoc();
}
- set_gedcom_setting($tree_id, 'THEME_DIR', 'webtrees');
- set_gedcom_setting($tree_id, 'THUMBNAIL_WIDTH', '100');
- set_gedcom_setting($tree_id, 'USE_RIN', false);
- set_gedcom_setting($tree_id, 'USE_SILHOUETTE', true);
- set_gedcom_setting($tree_id, 'WATERMARK_THUMB', false);
- set_gedcom_setting($tree_id, 'WEBMASTER_USER_ID', WT_USER_ID);
- set_gedcom_setting($tree_id, 'WEBTREES_EMAIL', '');
- set_gedcom_setting($tree_id, 'WORD_WRAPPED_NOTES', false);
- set_gedcom_setting($tree_id, 'imported', 0);
- set_gedcom_setting($tree_id, 'title', /* I18N: Default title for new family trees */ WT_I18N::translate('My family tree'));
- // Default restriction settings
- $statement=WT_DB::prepare(
- "INSERT INTO `##default_resn` (gedcom_id, xref, tag_type, resn) VALUES (?, NULL, ?, ?)"
- );
- $statement->execute(array($tree_id, 'SSN', 'confidential'));
- $statement->execute(array($tree_id, 'SOUR', 'privacy'));
- $statement->execute(array($tree_id, 'REPO', 'privacy'));
- $statement->execute(array($tree_id, 'SUBM', 'confidential'));
- $statement->execute(array($tree_id, 'SUBN', 'confidential'));
-
- // Genealogy data
- // It is simpler to create a temporary/unimported GEDCOM than to populate all the tables...
- $john_doe=/* I18N: This should be a common/default/placeholder name of an individual. Put slashes around the surname. */
- WT_I18N::translate('John /DOE/');
- $note=WT_I18N::translate('Edit this individual and replace their details with your own');
- WT_DB::prepare("INSERT INTO `##gedcom_chunk` (gedcom_id, chunk_data) VALUES (?, ?)")->execute(array(
- $tree_id,
- "0 HEAD\n0 @I1@ INDI\n1 NAME {$john_doe}\n1 SEX M\n1 BIRT\n2 DATE 01 JAN 1850\n2 NOTE {$note}\n0 TRLR\n"
- ));
-
- // Set the initial blocks
- WT_DB::prepare(
- "INSERT INTO `##block` (gedcom_id, location, block_order, module_name)".
- " SELECT ?, location, block_order, module_name".
- " FROM `##block`".
- " WHERE gedcom_id=-1"
- )->execute(array($tree_id));
-
- // Update the list of trees - to include the new configuration settings
- self::$trees=null;
+ if (array_key_exists($setting_name, $this->settings)) {
+ return $this->settings[$setting_name];
+ } else {
+ return $default;
+ }
}
- // Delete everything relating to a tree
- public static function delete($tree_id) {
- // If this is the default tree, then unset
- if (WT_Site::preference('DEFAULT_GEDCOM')==self::getNameFromId($tree_id)) {
- WT_Site::preference('DEFAULT_GEDCOM', '');
+ // Update a setting for the user.
+ public function setSetting($setting_name, $setting_value) {
+ if ($setting_value===null) {
+ WT_DB::prepare("DELETE FROM `##user_setting` WHERE user_id=? AND setting_name=?")
+ ->execute(array($this->user_id, $setting_name));
+ unset($this->settings[$setting_name]);
+ } else {
+ WT_DB::prepare("REPLACE INTO `##user_setting` (user_id, setting_name, setting_value) VALUES (?, ?, LEFT(?, 255))")
+ ->execute(array($this->user_id, $setting_name, $setting_value));
+ $this->settings[$setting_name] = $setting_value;
}
- // Don't delete the logs.
- WT_DB::prepare("UPDATE `##log` SET gedcom_id=NULL WHERE gedcom_id =?")->execute(array($tree_id));
-
- WT_DB::prepare("DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE gedcom_id=?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##block` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##dates` WHERE d_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##families` WHERE f_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##user_gedcom_setting` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##gedcom_setting` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##individuals` WHERE i_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##link` WHERE l_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##media` WHERE m_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##module_privacy` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##name` WHERE n_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##next_id` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##other` WHERE o_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##placelinks` WHERE pl_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##places` WHERE p_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##sources` WHERE s_file =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##hit_counter` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##change` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##default_resn` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##gedcom_chunk` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##log` WHERE gedcom_id =?")->execute(array($tree_id));
- WT_DB::prepare("DELETE FROM `##gedcom` WHERE gedcom_id =?")->execute(array($tree_id));
+ }
- // After updating the database, we need to fetch a new (sorted) copy
- self::$trees=null;
+ // PHP5.5 provides new password hash functions.
+ // For earlier versions, use a compatibility library.
+ private static function passwordCompatibility() {
+ if (!function_exists('password_hash')) {
+ // The compatibility library requires the $2$y salt prefix, which is available in
+ // PHP5.3.7 and *some* earlier/patched versions.
+ $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
+ if (crypt("password", $hash) === $hash) {
+ require 'library/ircmaxell/password-compat/lib/password.php';
+ } else {
+ // For older/unpatched versions of PHP, use the default crypt behaviour.
+ function password_hash($password, $algo, $options=array()) {
+ $salt = '$2a$12$';
+ $salt_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./';
+ for ($i = 0; $i < 22; ++$i) {
+ $salt .= setsubstr($salt_chars, mt_rand(0, 63), 1);
+ }
+ return crypt($password, $salt);
+ }
+ function password_needs_rehash($password, $algo, $options=array()) {
+ return false;
+ }
+ function password_verify($password, $hash) {
+ return crypt($password, $hash) === $hash;
+ }
+ define('PASSWORD_DEFAULT', 1);
+ }
+ }
}
- //////////////////////////////////////////////////////////////////////////////
+ // Authenticate a username/password combination.
//
- // Export the tree to a GEDCOM file
- //
- //////////////////////////////////////////////////////////////////////////////
+ // On success, return the user_id of the account.
+ // On failure, return the reason for failure.
+ public static function authenticate($user_name, $password) {
+ self::passwordCompatibility();
- public function exportGedcom($gedcom_file) {
+ // If no cookies are available, then we cannot log in.
+ if (empty($_COOKIE)) {
+ return self::NO_SESSION_COOKIES;
+ }
- // TODO: these functions need to be moved to the GedcomRecord(?) class
- require_once WT_ROOT.'includes/functions/functions_export.php';
+ $row = WT_DB::prepare(
+ "SELECT SQL_CACHE user_id, password FROM `wt_user` WHERE user_name = ? OR email = ?"
+ )->execute(array($user_name, $user_name))->fetchOneRow();
- // To avoid partial trees on timeout/diskspace/etc, write to a temporary file first
- $tmp_file = $gedcom_file . '.tmp';
+ if (!$row) {
+ // No such user with that username or email address
+ return self::NO_SUCH_USER;
+ }
- $file_pointer = @fopen($tmp_file, 'w');
- if ($file_pointer === false) {
- return false;
+ $user = new WT_User($row->user_id);
+ if (!password_verify($password, $row->password)) {
+ // Incorrect password
+ return self::INCORRECT_PASSWORD;
}
-
- $buffer = reformat_record_export(gedcom_header($this->tree_name));
- $stmt = WT_DB::prepare(
- "SELECT i_gedcom AS gedcom FROM `##individuals` WHERE i_file = ?" .
- " UNION ALL " .
- "SELECT f_gedcom AS gedcom FROM `##families` WHERE f_file = ?" .
- " UNION ALL " .
- "SELECT s_gedcom AS gedcom FROM `##sources` WHERE s_file = ?" .
- " UNION ALL " .
- "SELECT o_gedcom AS gedcom FROM `##other` WHERE o_file = ? AND o_type NOT IN ('HEAD', 'TRLR')" .
- " UNION ALL " .
- "SELECT m_gedcom AS gedcom FROM `##media` WHERE m_file = ?"
- )->execute(array($this->tree_id, $this->tree_id, $this->tree_id, $this->tree_id, $this->tree_id));
+ // Was the password hash created using an old or insecure algorithm?
+ if (password_needs_rehash($row->password, PASSWORD_DEFAULT)) {
+ // Generate a new hash
+ $user->setPassword(password_hash($password, PASSWORD_DEFAULT));
+ }
- while ($row = $stmt->fetch()) {
- $buffer .= reformat_record_export($row->gedcom);
- if (strlen($buffer)>65535) {
- fwrite($file_pointer, $buffer);
- $buffer = '';
- }
+ if (!$user->getSetting('verified') && !$user->getSetting('canadmin')) {
+ // The user has not verified their email address.
+ return self::ACCOUNT_NOT_VERIFIED;
}
- fwrite($file_pointer, $buffer . '0 TRLR' . WT_EOL);
- fclose($file_pointer);
+ if (!$user->getSetting('verified_by_admin') && !$user->getSetting('canadmin')) {
+ // An administrator has not approved the account.
+ // Admins do not need to be approved.
+ return self::ACCOUNT_NOT_APPROVED;
+ }
- return @rename($tmp_file, $gedcom_file);
+ // All checks passed. The user is permitted to log in.
+ return $row->user_id;
}
}
diff --git a/library/autoload.php b/library/autoload.php
index 8171c1e157..f8c9c7e1fb 100644
--- a/library/autoload.php
+++ b/library/autoload.php
@@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
-return ComposerAutoloaderInite259da7efea2bd04744d4783d1236107::getLoader();
+return ComposerAutoloaderInit0f33510d61ebec81dd54187299ee73ec::getLoader();
diff --git a/library/composer/autoload_classmap.php b/library/composer/autoload_classmap.php
index fa9e570a3d..e1bf92e8ea 100644
--- a/library/composer/autoload_classmap.php
+++ b/library/composer/autoload_classmap.php
@@ -341,6 +341,7 @@ return array(
'WT_Source' => $vendorDir . '/WT/Source.php',
'WT_Stats' => $vendorDir . '/WT/Stats.php',
'WT_Tree' => $vendorDir . '/WT/Tree.php',
+ 'WT_User' => $vendorDir . '/WT/User.php',
'Zend_Acl' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Acl.php',
'Zend_Acl_Assert_Interface' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Acl/Assert/Interface.php',
'Zend_Acl_Exception' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Acl/Exception.php',
@@ -2366,10 +2367,10 @@ return array(
'Zend_Tag_Taggable' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Tag/Taggable.php',
'Zend_Test_DbAdapter' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/DbAdapter.php',
'Zend_Test_DbStatement' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/DbStatement.php',
- 'Zend_Test_PHPUnit_Constraint_DomQuery' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/Constraint/DomQuery34.php',
+ 'Zend_Test_PHPUnit_Constraint_DomQuery' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/Constraint/DomQuery37.php',
'Zend_Test_PHPUnit_Constraint_Exception' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/Constraint/Exception.php',
- 'Zend_Test_PHPUnit_Constraint_Redirect' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/Constraint/Redirect37.php',
- 'Zend_Test_PHPUnit_Constraint_ResponseHeader' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/Constraint/ResponseHeader34.php',
+ 'Zend_Test_PHPUnit_Constraint_Redirect' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/Constraint/Redirect34.php',
+ 'Zend_Test_PHPUnit_Constraint_ResponseHeader' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/Constraint/ResponseHeader37.php',
'Zend_Test_PHPUnit_ControllerTestCase' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/ControllerTestCase.php',
'Zend_Test_PHPUnit_DatabaseTestCase' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/DatabaseTestCase.php',
'Zend_Test_PHPUnit_Db_Connection' => $vendorDir . '/bombayworks/zendframework1/library/Zend/Test/PHPUnit/Db/Connection.php',
diff --git a/library/composer/autoload_files.php b/library/composer/autoload_files.php
index 915496bea7..5645a52f0d 100644
--- a/library/composer/autoload_files.php
+++ b/library/composer/autoload_files.php
@@ -7,4 +7,5 @@ $baseDir = dirname($vendorDir);
return array(
$vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+ $vendorDir . '/ircmaxell/password-compat/lib/password.php',
);
diff --git a/library/composer/autoload_real.php b/library/composer/autoload_real.php
index b54e2bf7ec..b934a0ece2 100644
--- a/library/composer/autoload_real.php
+++ b/library/composer/autoload_real.php
@@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
-class ComposerAutoloaderInite259da7efea2bd04744d4783d1236107
+class ComposerAutoloaderInit0f33510d61ebec81dd54187299ee73ec
{
private static $loader;
@@ -19,9 +19,9 @@ class ComposerAutoloaderInite259da7efea2bd04744d4783d1236107
return self::$loader;
}
- spl_autoload_register(array('ComposerAutoloaderInite259da7efea2bd04744d4783d1236107', 'loadClassLoader'), true, true);
+ spl_autoload_register(array('ComposerAutoloaderInit0f33510d61ebec81dd54187299ee73ec', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
- spl_autoload_unregister(array('ComposerAutoloaderInite259da7efea2bd04744d4783d1236107', 'loadClassLoader'));
+ spl_autoload_unregister(array('ComposerAutoloaderInit0f33510d61ebec81dd54187299ee73ec', 'loadClassLoader'));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
@@ -49,14 +49,14 @@ class ComposerAutoloaderInite259da7efea2bd04744d4783d1236107
$includeFiles = require __DIR__ . '/autoload_files.php';
foreach ($includeFiles as $file) {
- composerRequiree259da7efea2bd04744d4783d1236107($file);
+ composerRequire0f33510d61ebec81dd54187299ee73ec($file);
}
return $loader;
}
}
-function composerRequiree259da7efea2bd04744d4783d1236107($file)
+function composerRequire0f33510d61ebec81dd54187299ee73ec($file)
{
require $file;
}
diff --git a/library/composer/installed.json b/library/composer/installed.json
index 74ee6e8ca4..9ac272499b 100644
--- a/library/composer/installed.json
+++ b/library/composer/installed.json
@@ -154,17 +154,17 @@
},
{
"name": "bombayworks/zendframework1",
- "version": "v1.12.5",
- "version_normalized": "1.12.5.0",
+ "version": "v1.12.6",
+ "version_normalized": "1.12.6.0",
"source": {
"type": "git",
"url": "https://github.com/bombayworks/zendframework1.git",
- "reference": "3f8af440199be5eb31cc9e73c196278227bb73af"
+ "reference": "f83796e2fcaf18bea1480988ab61de02a8168535"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/bombayworks/zendframework1/zipball/3f8af440199be5eb31cc9e73c196278227bb73af",
- "reference": "3f8af440199be5eb31cc9e73c196278227bb73af",
+ "url": "https://api.github.com/repos/bombayworks/zendframework1/zipball/f83796e2fcaf18bea1480988ab61de02a8168535",
+ "reference": "f83796e2fcaf18bea1480988ab61de02a8168535",
"shasum": ""
},
"require": {
@@ -174,7 +174,7 @@
"phpunit/dbunit": "1.3.*",
"phpunit/phpunit": "3.7.*"
},
- "time": "2014-03-07 17:08:56",
+ "time": "2014-04-15 17:01:21",
"bin": [
"bin/zf.sh",
"bin/zf.php"
@@ -209,17 +209,17 @@
},
{
"name": "tecnick.com/tcpdf",
- "version": "6.0.062",
- "version_normalized": "6.0.062.0",
+ "version": "6.0.071",
+ "version_normalized": "6.0.071.0",
"source": {
"type": "git",
"url": "git://git.code.sf.net/p/tcpdf/code",
- "reference": "cada5f0ef8c1bb0799f2694ea501db437b02a2ea"
+ "reference": "e85569fc0351501ed51643df1b12f66696727b8a"
},
"require": {
"php": ">=5.3.0"
},
- "time": "2014-03-02 10:22:27",
+ "time": "2014-04-25 18:13:02",
"type": "library",
"installation-source": "source",
"autoload": {
@@ -265,5 +265,46 @@
"pdf417",
"qrcode"
]
+ },
+ {
+ "name": "ircmaxell/password-compat",
+ "version": "1.0.3",
+ "version_normalized": "1.0.3.0",
+ "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": ""
+ },
+ "time": "2013-04-30 19:58:08",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "lib/password.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Anthony Ferrara",
+ "email": "ircmaxell@php.net",
+ "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"
+ ]
}
]
diff --git a/library/ircmaxell/password-compat/.travis.yml b/library/ircmaxell/password-compat/.travis.yml
new file mode 100644
index 0000000000..a960061ca8
--- /dev/null
+++ b/library/ircmaxell/password-compat/.travis.yml
@@ -0,0 +1,8 @@
+language: php
+
+php:
+ - 5.5
+ - 5.4
+ - 5.3
+
+script: phpunit --configuration phpunit.xml.dist \ No newline at end of file
diff --git a/library/ircmaxell/password-compat/LICENSE.md b/library/ircmaxell/password-compat/LICENSE.md
new file mode 100644
index 0000000000..1efc565fcf
--- /dev/null
+++ b/library/ircmaxell/password-compat/LICENSE.md
@@ -0,0 +1,7 @@
+Copyright (c) 2012 Anthony Ferrara
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/library/ircmaxell/password-compat/README.md b/library/ircmaxell/password-compat/README.md
new file mode 100644
index 0000000000..e158f7d0ca
--- /dev/null
+++ b/library/ircmaxell/password-compat/README.md
@@ -0,0 +1,75 @@
+password_compat
+===============
+
+[![Build Status](https://travis-ci.org/ircmaxell/password_compat.png?branch=master)](https://travis-ci.org/ircmaxell/password_compat)
+
+This library is intended to provide forward compatibility with the password_* functions being worked on for PHP 5.5.
+
+See [the RFC](https://wiki.php.net/rfc/password_hash) for more detailed information.
+
+
+Requirements
+============
+
+This library requires `PHP >= 5.3.7` OR a version that has the `$2y` fix backported into it (such as Debian provides).
+
+The runtime checks have been removed due to this version issue. To see if password_compat is available for your system, run the included `version-test.php`. If it outputs "Pass", you can safely use the library. If not, you cannot.
+
+If you attempt to use password-compat on an unsupported version, attempts to create or verify hashes will return `false`. You have been warned!
+
+The reason for this is that PHP prior to 5.3.7 contains a security issue with its BCRYPT implementation. Therefore, it's highly recommended that you upgrade to a newer version of PHP prior to using this layer.
+
+Installation
+============
+
+To install, simply `require` the `password.php` file under `lib`.
+
+You can also install it via `Composer` by using the [Packagist archive](http://packagist.org/packages/ircmaxell/password-compat).
+
+Usage
+=====
+
+**Creating Password Hashes**
+
+To create a password hash from a password, simply use the `password_hash` function.
+
+ $hash = password_hash($password, PASSWORD_BCRYPT);
+
+Note that the algorithm that we chose is `PASSWORD_BCRYPT`. That's the current strongest algorithm supported. This is the `BCRYPT` crypt algorithm. It produces a 60 character hash as the result.
+
+`BCRYPT` also allows for you to define a `cost` parameter in the options array. This allows for you to change the CPU cost of the algorithm:
+
+ $hash = password_hash($password, PASSWORD_BCRYPT, ["cost" => 10]);
+
+That's the same as the default. The cost can range from `4` to `31`. I would suggest that you use the highest cost that you can, while keeping response time reasonable (I target between 0.1 and 0.5 seconds for a hash, depending on use-case).
+
+Another algorithm name is supported:
+
+ PASSWORD_DEFAULT
+
+This will use the strongest algorithm available to PHP at the current time. Presently, this is the same as specifying `PASSWORD_BCRYPT`. But in future versions of PHP, it may be updated to use a stronger algorithm if one is introduced. It can also be changed if a problem is identified with the BCRYPT algorithm. Note that if you use this option, you are **strongly** encouraged to store it in a `VARCHAR(255)` column to avoid truncation issues if a future algorithm increases the length of the generated hash.
+
+It is very important that you should check the return value of `password_hash` prior to storing it, because a `false` may be returned if it encountered an error.
+
+**Verifying Password Hashes**
+
+To verify a hash created by `password_hash`, simply call:
+
+ if (password_verify($password, $hash)) {
+ /* Valid */
+ } else {
+ /* Invalid */
+ }
+
+That's all there is to it.
+
+**Rehashing Passwords**
+
+From time to time you may update your hashing parameters (algorithm, cost, etc). So a function to determine if rehashing is necessary is available:
+
+ if (password_verify($password, $hash)) {
+ if (password_needs_rehash($hash, $algorithm, $options)) {
+ $hash = password_hash($password, $algorithm, $options);
+ /* Store new hash in db */
+ }
+ }
diff --git a/library/ircmaxell/password-compat/composer.json b/library/ircmaxell/password-compat/composer.json
new file mode 100644
index 0000000000..e0d4f14c55
--- /dev/null
+++ b/library/ircmaxell/password-compat/composer.json
@@ -0,0 +1,18 @@
+{
+ "name": "ircmaxell/password-compat",
+ "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash",
+ "version": "1.0.3",
+ "keywords": ["password", "hashing"],
+ "homepage": "https://github.com/ircmaxell/password_compat",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Anthony Ferrara",
+ "email": "ircmaxell@php.net",
+ "homepage": "http://blog.ircmaxell.com"
+ }
+ ],
+ "autoload": {
+ "files": ["lib/password.php"]
+ }
+} \ No newline at end of file
diff --git a/library/ircmaxell/password-compat/lib/password.php b/library/ircmaxell/password-compat/lib/password.php
new file mode 100644
index 0000000000..4d0e8b7a63
--- /dev/null
+++ b/library/ircmaxell/password-compat/lib/password.php
@@ -0,0 +1,222 @@
+<?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);
+
+ /**
+ * 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;
+ }
+ }
+ // The length of salt to generate
+ $raw_salt_len = 16;
+ // The length required in the final serialization
+ $required_salt_len = 22;
+ $hash_format = sprintf("$2y$%02d$", $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 = '';
+ $buffer_valid = false;
+ if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
+ $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
+ if ($buffer) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
+ $buffer = openssl_random_pseudo_bytes($raw_salt_len);
+ if ($buffer) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid && is_readable('/dev/urandom')) {
+ $f = fopen('/dev/urandom', 'r');
+ $read = strlen($buffer);
+ while ($read < $raw_salt_len) {
+ $buffer .= fread($f, $raw_salt_len - $read);
+ $read = strlen($buffer);
+ }
+ fclose($f);
+ if ($read >= $raw_salt_len) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid || strlen($buffer) < $raw_salt_len) {
+ $bl = strlen($buffer);
+ for ($i = 0; $i < $raw_salt_len; $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) == '$2y$' && strlen($hash) == 60) {
+ $return['algo'] = PASSWORD_BCRYPT;
+ $return['algoName'] = 'bcrypt';
+ list($cost) = sscanf($hash, "$2y$%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;
+ }
+}
+
+
+
diff --git a/library/ircmaxell/password-compat/phpunit.xml.dist b/library/ircmaxell/password-compat/phpunit.xml.dist
new file mode 100644
index 0000000000..b2b3afbd97
--- /dev/null
+++ b/library/ircmaxell/password-compat/phpunit.xml.dist
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="true"
+ backupStaticAttributes="false"
+ bootstrap="lib/password.php"
+ colors="false"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ forceCoversAnnotation="false"
+ mapTestClassNameToCoveredClassName="false"
+ processIsolation="false"
+ stopOnError="false"
+ stopOnFailure="false"
+ stopOnIncomplete="false"
+ stopOnSkipped="false"
+ testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader"
+ strict="false"
+ verbose="false">
+ <testsuites>
+ <testsuite name="Unit">
+ <directory>test/Unit</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist>
+ <directory suffix=".php">lib/</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/library/ircmaxell/password-compat/test/Unit/PasswordGetInfoTest.php b/library/ircmaxell/password-compat/test/Unit/PasswordGetInfoTest.php
new file mode 100644
index 0000000000..6aab976adc
--- /dev/null
+++ b/library/ircmaxell/password-compat/test/Unit/PasswordGetInfoTest.php
@@ -0,0 +1,26 @@
+<?php
+
+class PasswordGetInfoTest extends PHPUnit_Framework_TestCase {
+
+ public static function provideInfo() {
+ return array(
+ array('foo', array('algo' => 0, 'algoName' => 'unknown', 'options' => array())),
+ array('$2y$', array('algo' => 0, 'algoName' => 'unknown', 'options' => array())),
+ array('$2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', array('algo' => PASSWORD_BCRYPT, 'algoName' => 'bcrypt', 'options' => array('cost' => 7))),
+ array('$2y$10$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', array('algo' => PASSWORD_BCRYPT, 'algoName' => 'bcrypt', 'options' => array('cost' => 10))),
+
+ );
+ }
+
+ public function testFuncExists() {
+ $this->assertTrue(function_exists('password_get_info'));
+ }
+
+ /**
+ * @dataProvider provideInfo
+ */
+ public function testInfo($hash, $info) {
+ $this->assertEquals($info, password_get_info($hash));
+ }
+
+} \ No newline at end of file
diff --git a/library/ircmaxell/password-compat/test/Unit/PasswordHashTest.php b/library/ircmaxell/password-compat/test/Unit/PasswordHashTest.php
new file mode 100644
index 0000000000..9e5e9ec641
--- /dev/null
+++ b/library/ircmaxell/password-compat/test/Unit/PasswordHashTest.php
@@ -0,0 +1,84 @@
+<?php
+
+class PasswordHashTest extends PHPUnit_Framework_TestCase {
+
+ public function testFuncExists() {
+ $this->assertTrue(function_exists('password_hash'));
+ }
+
+ public function testStringLength() {
+ $this->assertEquals(60, strlen(password_hash('foo', PASSWORD_BCRYPT)));
+ }
+
+ public function testHash() {
+ $hash = password_hash('foo', PASSWORD_BCRYPT);
+ $this->assertEquals($hash, crypt('foo', $hash));
+ }
+
+ public function testKnownSalt() {
+ $hash = password_hash("rasmuslerdorf", PASSWORD_BCRYPT, array("cost" => 7, "salt" => "usesomesillystringforsalt"));
+ $this->assertEquals('$2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', $hash);
+ }
+
+ public function testRawSalt() {
+ $hash = password_hash("test", PASSWORD_BCRYPT, array("salt" => "123456789012345678901" . chr(0)));
+ $this->assertEquals('$2y$10$MTIzNDU2Nzg5MDEyMzQ1Nej0NmcAWSLR.oP7XOR9HD/vjUuOj100y', $hash);
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testInvalidAlgo() {
+ password_hash('foo', array());
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testInvalidAlgo2() {
+ password_hash('foo', 2);
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testInvalidPassword() {
+ password_hash(array(), 1);
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testInvalidSalt() {
+ password_hash('foo', PASSWORD_BCRYPT, array('salt' => array()));
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testInvalidBcryptCostLow() {
+ password_hash('foo', PASSWORD_BCRYPT, array('cost' => 3));
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testInvalidBcryptCostHigh() {
+ password_hash('foo', PASSWORD_BCRYPT, array('cost' => 32));
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testInvalidBcryptCostInvalid() {
+ password_hash('foo', PASSWORD_BCRYPT, array('cost' => 'foo'));
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error
+ */
+ public function testInvalidBcryptSaltShort() {
+ password_hash('foo', PASSWORD_BCRYPT, array('salt' => 'abc'));
+ }
+
+} \ No newline at end of file
diff --git a/library/ircmaxell/password-compat/test/Unit/PasswordNeedsRehashTest.php b/library/ircmaxell/password-compat/test/Unit/PasswordNeedsRehashTest.php
new file mode 100644
index 0000000000..c2932dc6c0
--- /dev/null
+++ b/library/ircmaxell/password-compat/test/Unit/PasswordNeedsRehashTest.php
@@ -0,0 +1,26 @@
+<?php
+
+class PasswordNeedsRehashTest extends PHPUnit_Framework_TestCase {
+
+ public static function provideCases() {
+ return array(
+ array('foo', 0, array(), false),
+ array('foo', 1, array(), true),
+ array('$2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', PASSWORD_BCRYPT, array(), true),
+ array('$2y$07$usesomesillystringfore2udlvp1ii2e./u9c8sbjqp8i90dh6hi', PASSWORD_BCRYPT, array('cost' => 7), false),
+ array('$2y$07$usesomesillystringfore2udlvp1ii2e./u9c8sbjqp8i90dh6hi', PASSWORD_BCRYPT, array('cost' => 5), true),
+ );
+ }
+
+ public function testFuncExists() {
+ $this->assertTrue(function_exists('password_needs_rehash'));
+ }
+
+ /**
+ * @dataProvider provideCases
+ */
+ public function testCases($hash, $algo, $options, $valid) {
+ $this->assertEquals($valid, password_needs_rehash($hash, $algo, $options));
+ }
+
+} \ No newline at end of file
diff --git a/library/ircmaxell/password-compat/test/Unit/PasswordVerifyTest.php b/library/ircmaxell/password-compat/test/Unit/PasswordVerifyTest.php
new file mode 100644
index 0000000000..9f67bb9f7b
--- /dev/null
+++ b/library/ircmaxell/password-compat/test/Unit/PasswordVerifyTest.php
@@ -0,0 +1,29 @@
+<?php
+
+class PasswordVerifyTest extends PHPUnit_Framework_TestCase {
+
+ public function testFuncExists() {
+ $this->assertTrue(function_exists('password_verify'));
+ }
+
+ public function testFailedType() {
+ $this->assertFalse(password_verify(123, 123));
+ }
+
+ public function testSaltOnly() {
+ $this->assertFalse(password_verify('foo', '$2a$07$usesomesillystringforsalt$'));
+ }
+
+ public function testInvalidPassword() {
+ $this->assertFalse(password_verify('rasmusler', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi'));
+ }
+
+ public function testValidPassword() {
+ $this->assertTrue(password_verify('rasmuslerdorf', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi'));
+ }
+
+ public function testInValidHash() {
+ $this->assertFalse(password_verify('rasmuslerdorf', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hj'));
+ }
+
+} \ No newline at end of file
diff --git a/library/ircmaxell/password-compat/version-test.php b/library/ircmaxell/password-compat/version-test.php
new file mode 100644
index 0000000000..f527e30f93
--- /dev/null
+++ b/library/ircmaxell/password-compat/version-test.php
@@ -0,0 +1,8 @@
+<?php
+
+$hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
+$test = crypt("password", $hash);
+$pass = $test == $hash;
+
+echo "Test for functionality of compat library: " . ($pass ? "Pass" : "Fail");
+echo "\n"; \ No newline at end of file