124cd6f55SDamien Regad<?php 224cd6f55SDamien Regad/** 324cd6f55SDamien Regad * DokuWiki Plugin authwordpress (Auth Component) 424cd6f55SDamien Regad * 535dd80b8SDamien Regad * Provides authentication against a WordPress MySQL database backend 635dd80b8SDamien Regad * 735dd80b8SDamien Regad * This program is free software; you can redistribute it and/or modify 835dd80b8SDamien Regad * it under the terms of the GNU General Public License as published by 935dd80b8SDamien Regad * the Free Software Foundation; version 2 of the License 1035dd80b8SDamien Regad * 1135dd80b8SDamien Regad * This program is distributed in the hope that it will be useful, 1235dd80b8SDamien Regad * but WITHOUT ANY WARRANTY; without even the implied warranty of 1335dd80b8SDamien Regad * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1435dd80b8SDamien Regad * GNU General Public License for more details. 1535dd80b8SDamien Regad * 1635dd80b8SDamien Regad * See the COPYING file in your DokuWiki folder for details 1735dd80b8SDamien Regad * 1824cd6f55SDamien Regad * @author Damien Regad <dregad@mantisbt.org> 1935dd80b8SDamien Regad * @copyright 2015 Damien Regad 2035dd80b8SDamien Regad * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 21b5b72c15SDamien Regad * @version 1.1 2235dd80b8SDamien Regad * @link https://github.com/dregad/dokuwiki-authwordpress 2324cd6f55SDamien Regad */ 2424cd6f55SDamien Regad 2535dd80b8SDamien Regad 2624cd6f55SDamien Regad// must be run within Dokuwiki 27b741ff68SDamien Regadif (!defined('DOKU_INC')) { 28b741ff68SDamien Regad die(); 29b741ff68SDamien Regad} 3024cd6f55SDamien Regad 3135dd80b8SDamien Regad/** 3235dd80b8SDamien Regad * WordPress password hashing framework 3335dd80b8SDamien Regad */ 3435dd80b8SDamien Regadrequire_once('class-phpass.php'); 3535dd80b8SDamien Regad 3635dd80b8SDamien Regad/** 3735dd80b8SDamien Regad * Authentication class 3835dd80b8SDamien Regad */ 39b741ff68SDamien Regad// @codingStandardsIgnoreLine 40b741ff68SDamien Regadclass auth_plugin_authwordpress extends DokuWiki_Auth_Plugin 41b741ff68SDamien Regad{ 4224cd6f55SDamien Regad 4335dd80b8SDamien Regad /** 4435dd80b8SDamien Regad * SQL statement to retrieve User data from WordPress DB 4535dd80b8SDamien Regad * (including group memberships) 46b5b72c15SDamien Regad * '%prefix%' will be replaced by the actual prefix (from plugin config) 47*5d099ac1SDamien Regad * @var string $sql_wp_user_data 4835dd80b8SDamien Regad */ 490341717fSDamien Regad protected $sql_wp_user_data = "SELECT 5035dd80b8SDamien Regad id, user_login, user_pass, user_email, display_name, 5135dd80b8SDamien Regad meta_value AS groups 52b5b72c15SDamien Regad FROM %prefix%users u 532ef3e6a1SDamien Regad JOIN %prefix%usermeta m ON u.id = m.user_id AND meta_key = '%prefix%capabilities'"; 5424cd6f55SDamien Regad 5524cd6f55SDamien Regad /** 56015f33b2SDamien Regad * Wordpress database connection 57*5d099ac1SDamien Regad * @var PDO $db 58015f33b2SDamien Regad */ 590341717fSDamien Regad protected $db; 60015f33b2SDamien Regad 61d35aa3ecSDamien Regad /** 62d35aa3ecSDamien Regad * Users cache 63*5d099ac1SDamien Regad * @var array $users 64d35aa3ecSDamien Regad */ 65d35aa3ecSDamien Regad protected $users; 66d35aa3ecSDamien Regad 67d35aa3ecSDamien Regad /** 68d35aa3ecSDamien Regad * True if all users have been loaded in the cache 69d35aa3ecSDamien Regad * @see $users 70*5d099ac1SDamien Regad * @var bool $usersCached 71d35aa3ecSDamien Regad */ 72d35aa3ecSDamien Regad protected $usersCached = false; 73d35aa3ecSDamien Regad 749f5692a3SDamien Regad /** 759f5692a3SDamien Regad * Filter pattern 76*5d099ac1SDamien Regad * @var array $filter 779f5692a3SDamien Regad */ 789f5692a3SDamien Regad protected $filter; 79015f33b2SDamien Regad 80015f33b2SDamien Regad /** 8124cd6f55SDamien Regad * Constructor. 8224cd6f55SDamien Regad */ 83b741ff68SDamien Regad public function __construct() 84b741ff68SDamien Regad { 8535dd80b8SDamien Regad parent::__construct(); 8624cd6f55SDamien Regad 87aa69c0cfSDamien Regad // Plugin capabilities 8816ceb666SDamien Regad $this->cando['getUsers'] = true; 89aa69c0cfSDamien Regad $this->cando['getUserCount'] = true; 9016ceb666SDamien Regad 9135dd80b8SDamien Regad // Try to establish a connection to the WordPress DB 9235dd80b8SDamien Regad // abort in case of failure 9335dd80b8SDamien Regad try { 94b741ff68SDamien Regad $this->connectWordpressDb(); 95b741ff68SDamien Regad } catch (Exception $e) { 9635dd80b8SDamien Regad msg(sprintf($this->getLang('error_connect_failed'), $e->getMessage())); 9735dd80b8SDamien Regad $this->success = false; 9835dd80b8SDamien Regad return; 9935dd80b8SDamien Regad } 10024cd6f55SDamien Regad 101b5b72c15SDamien Regad // Initialize SQL query with configured prefix 102b5b72c15SDamien Regad $this->sql_wp_user_data = str_replace( 103b5b72c15SDamien Regad '%prefix%', 104b5b72c15SDamien Regad $this->getConf('prefix'), 105b5b72c15SDamien Regad $this->sql_wp_user_data 106b5b72c15SDamien Regad ); 107b5b72c15SDamien Regad 10824cd6f55SDamien Regad $this->success = true; 10924cd6f55SDamien Regad } 11024cd6f55SDamien Regad 11124cd6f55SDamien Regad 11224cd6f55SDamien Regad /** 11324cd6f55SDamien Regad * Check user+password 11424cd6f55SDamien Regad * 11524cd6f55SDamien Regad * @param string $user the user name 11624cd6f55SDamien Regad * @param string $pass the clear text password 11724cd6f55SDamien Regad * @return bool 11835dd80b8SDamien Regad * 11935dd80b8SDamien Regad * @uses PasswordHash::CheckPassword WordPress password hasher 12024cd6f55SDamien Regad */ 121b741ff68SDamien Regad public function checkPass($user, $pass) 122b741ff68SDamien Regad { 12335dd80b8SDamien Regad $data = $this->getUserData($user); 12435dd80b8SDamien Regad if ($data === false) { 12535dd80b8SDamien Regad return false; 12624cd6f55SDamien Regad } 12724cd6f55SDamien Regad 12835dd80b8SDamien Regad $hasher = new PasswordHash(8, true); 129eed09871SDamien Regad $check = $hasher->CheckPassword($pass, $data['pass']); 130eed09871SDamien Regad dbglog("Password " . ($check ? 'OK' : 'Invalid')); 131eed09871SDamien Regad 132eed09871SDamien Regad return $check; 13335dd80b8SDamien Regad } 13435dd80b8SDamien Regad 13516ceb666SDamien Regad /** 13616ceb666SDamien Regad * Bulk retrieval of user data 13716ceb666SDamien Regad * 13816ceb666SDamien Regad * @param int $start index of first user to be returned 13916ceb666SDamien Regad * @param int $limit max number of users to be returned 14016ceb666SDamien Regad * @param array $filter array of field/pattern pairs 14116ceb666SDamien Regad * @return array userinfo (refer getUserData for internal userinfo details) 14216ceb666SDamien Regad */ 143b741ff68SDamien Regad public function retrieveUsers($start = 0, $limit = 0, $filter = array()) 144b741ff68SDamien Regad { 14516ceb666SDamien Regad msg($this->getLang('user_list_use_wordpress')); 1462ef3e6a1SDamien Regad 147d35aa3ecSDamien Regad $this->cacheAllUsers(); 1489f5692a3SDamien Regad 1499f5692a3SDamien Regad // Apply filter and pagination 1509f5692a3SDamien Regad $this->setFilter($filter); 1519f5692a3SDamien Regad $list = array(); 1529f5692a3SDamien Regad foreach ($this->users as $user => $info) { 1539f5692a3SDamien Regad if ($this->applyFilter($user, $info)) { 1549f5692a3SDamien Regad if ($i >= $start) { 1559f5692a3SDamien Regad $list[$user] = $info; 1569f5692a3SDamien Regad $count++; 1579f5692a3SDamien Regad if ($limit > 0 && $count >= $limit) { 1589f5692a3SDamien Regad break; 1599f5692a3SDamien Regad } 1609f5692a3SDamien Regad } 1619f5692a3SDamien Regad $i++; 1629f5692a3SDamien Regad } 1639f5692a3SDamien Regad } 1649f5692a3SDamien Regad 1659f5692a3SDamien Regad return $list; 16616ceb666SDamien Regad } 16716ceb666SDamien Regad 168aa69c0cfSDamien Regad /** 169aa69c0cfSDamien Regad * Return a count of the number of user which meet $filter criteria 170aa69c0cfSDamien Regad * 171aa69c0cfSDamien Regad * @param array $filter 172aa69c0cfSDamien Regad * @return int 173aa69c0cfSDamien Regad */ 174b741ff68SDamien Regad public function getUserCount($filter = array()) 175b741ff68SDamien Regad { 176aa69c0cfSDamien Regad $this->cacheAllUsers(); 1779f5692a3SDamien Regad 1789f5692a3SDamien Regad if (empty($filter)) { 1799f5692a3SDamien Regad $count = count($this->users); 1809f5692a3SDamien Regad } else { 1819f5692a3SDamien Regad $this->setFilter($filter); 1829f5692a3SDamien Regad foreach ($this->users as $user => $info) { 1839f5692a3SDamien Regad $count += (int)$this->applyFilter($user, $info); 1849f5692a3SDamien Regad } 1859f5692a3SDamien Regad } 1869f5692a3SDamien Regad return $count; 187aa69c0cfSDamien Regad } 188aa69c0cfSDamien Regad 18935dd80b8SDamien Regad 19024cd6f55SDamien Regad /** 19135dd80b8SDamien Regad * Returns info about the given user 19224cd6f55SDamien Regad * 19324cd6f55SDamien Regad * @param string $user the user name 194*5d099ac1SDamien Regad * @param bool $requireGroups defaults to true 195*5d099ac1SDamien Regad * @return array|false containing user data or false in case of error 19624cd6f55SDamien Regad */ 197b741ff68SDamien Regad public function getUserData($user, $requireGroups = true) 198b741ff68SDamien Regad { 199d35aa3ecSDamien Regad if (isset($this->users[$user])) { 200d35aa3ecSDamien Regad return $this->users[$user]; 201d35aa3ecSDamien Regad } 202d35aa3ecSDamien Regad 2032ef3e6a1SDamien Regad $sql = $this->sql_wp_user_data 2042ef3e6a1SDamien Regad . 'WHERE user_login = :user'; 20535dd80b8SDamien Regad 2062ef3e6a1SDamien Regad $stmt = $this->db->prepare($sql); 20735dd80b8SDamien Regad $stmt->bindParam(':user', $user); 2082ef3e6a1SDamien Regad dbglog("Retrieving data for user '$user'\n$sql"); 20935dd80b8SDamien Regad 21035dd80b8SDamien Regad if (!$stmt->execute()) { 2119520968dSDamien Regad // Query execution failed 212eed09871SDamien Regad $err = $stmt->errorInfo(); 213eed09871SDamien Regad dbglog("Error $err[1]: $err[2]"); 21424cd6f55SDamien Regad return false; 21524cd6f55SDamien Regad } 2169520968dSDamien Regad 2179520968dSDamien Regad $user = $stmt->fetch(PDO::FETCH_ASSOC); 2189520968dSDamien Regad if ($user === false) { 2199520968dSDamien Regad // Unknown user 220eed09871SDamien Regad dbglog("Unknown user"); 2219520968dSDamien Regad return false; 2229520968dSDamien Regad } 22324cd6f55SDamien Regad 224d35aa3ecSDamien Regad return $this->cacheUser($user); 22524cd6f55SDamien Regad } 22624cd6f55SDamien Regad 22724cd6f55SDamien Regad 22824cd6f55SDamien Regad /** 22935dd80b8SDamien Regad * Connect to Wordpress database 230015f33b2SDamien Regad * Initializes $db property as PDO object 23124cd6f55SDamien Regad */ 232b741ff68SDamien Regad protected function connectWordpressDb() 233b741ff68SDamien Regad { 234cb81639bSDamien Regad if ($this->db) { 235cb81639bSDamien Regad // Already connected 236cb81639bSDamien Regad return; 237cb81639bSDamien Regad } 238cb81639bSDamien Regad 239cb81639bSDamien Regad // Build connection string 24035dd80b8SDamien Regad $dsn = array( 24135dd80b8SDamien Regad 'host=' . $this->getConf('hostname'), 24235dd80b8SDamien Regad 'dbname=' . $this->getConf('database'), 24302fbe6b4SDamien Regad 'charset=UTF8', 24435dd80b8SDamien Regad ); 24535dd80b8SDamien Regad $port = $this->getConf('port'); 24635dd80b8SDamien Regad if ($port) { 24735dd80b8SDamien Regad $dsn[] = 'port=' . $port; 24835dd80b8SDamien Regad } 24935dd80b8SDamien Regad $dsn = 'mysql:' . implode(';', $dsn); 25035dd80b8SDamien Regad 251015f33b2SDamien Regad $this->db = new PDO($dsn, $this->getConf('username'), $this->getConf('password')); 25224cd6f55SDamien Regad } 25324cd6f55SDamien Regad 254d1f83a80SDamien Regad /** 255d1f83a80SDamien Regad * Convert a Wordpress DB User row to DokuWiki user info array 256d35aa3ecSDamien Regad * and stores it in the users cache 257d1f83a80SDamien Regad * 258*5d099ac1SDamien Regad * @param array $row Raw Wordpress user table row 259d1f83a80SDamien Regad * @return array user data 260d1f83a80SDamien Regad */ 261b741ff68SDamien Regad protected function cacheUser($row) 262b741ff68SDamien Regad { 263d1f83a80SDamien Regad global $conf; 264d1f83a80SDamien Regad 265d35aa3ecSDamien Regad $login = $row['user_login']; 266d35aa3ecSDamien Regad 267d35aa3ecSDamien Regad // If the user is already cached, just return it 268d35aa3ecSDamien Regad if (isset($this->users[$login])) { 269d35aa3ecSDamien Regad return $this->users[$login]; 270d35aa3ecSDamien Regad } 271d35aa3ecSDamien Regad 272d1f83a80SDamien Regad // Group membership - add DokuWiki's default group 273d35aa3ecSDamien Regad $groups = array_keys(unserialize($row['groups'])); 274d1f83a80SDamien Regad if ($this->getConf('usedefaultgroup')) { 275d1f83a80SDamien Regad $groups[] = $conf['defaultgroup']; 276d1f83a80SDamien Regad } 277d1f83a80SDamien Regad 278d1f83a80SDamien Regad $info = array( 279d35aa3ecSDamien Regad 'user' => $login, 280d35aa3ecSDamien Regad 'name' => $row['display_name'], 281d35aa3ecSDamien Regad 'pass' => $row['user_pass'], 282d35aa3ecSDamien Regad 'mail' => $row['user_email'], 283d1f83a80SDamien Regad 'grps' => $groups, 284d1f83a80SDamien Regad ); 285d35aa3ecSDamien Regad 286d35aa3ecSDamien Regad $this->users[$login] = $info; 287d1f83a80SDamien Regad return $info; 288d1f83a80SDamien Regad } 289d1f83a80SDamien Regad 290d35aa3ecSDamien Regad /** 291d35aa3ecSDamien Regad * Loads all Wordpress users into the cache 292d35aa3ecSDamien Regad * 293d35aa3ecSDamien Regad * @return void 294d35aa3ecSDamien Regad */ 295b741ff68SDamien Regad protected function cacheAllUsers() 296b741ff68SDamien Regad { 297d35aa3ecSDamien Regad if ($this->usersCached) { 298d35aa3ecSDamien Regad return; 299d35aa3ecSDamien Regad } 300d35aa3ecSDamien Regad 301d35aa3ecSDamien Regad $stmt = $this->db->prepare($this->sql_wp_user_data); 302d35aa3ecSDamien Regad $stmt->execute(); 303d35aa3ecSDamien Regad 304d35aa3ecSDamien Regad foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $user) { 305d35aa3ecSDamien Regad $this->cacheUser($user); 306d35aa3ecSDamien Regad } 307d35aa3ecSDamien Regad 308d35aa3ecSDamien Regad $this->usersCached = true; 309d35aa3ecSDamien Regad } 310d35aa3ecSDamien Regad 3119f5692a3SDamien Regad /** 3129f5692a3SDamien Regad * Build filter patterns from given criteria 3139f5692a3SDamien Regad * 3149f5692a3SDamien Regad * @param array $filter 3159f5692a3SDamien Regad */ 316b741ff68SDamien Regad protected function setFilter($filter) 317b741ff68SDamien Regad { 3189f5692a3SDamien Regad $this->filter = array(); 3199f5692a3SDamien Regad foreach ($filter as $field => $value) { 3209f5692a3SDamien Regad // Build PCRE pattern, utf8 + case insensitive 3219f5692a3SDamien Regad $this->filter[$field] = '/' . str_replace('/', '\/', $value) . '/ui'; 3229f5692a3SDamien Regad } 3239f5692a3SDamien Regad } 3249f5692a3SDamien Regad 3259f5692a3SDamien Regad /** 3269f5692a3SDamien Regad * Return true if given user matches filter pattern, false otherwise 3279f5692a3SDamien Regad * 3289f5692a3SDamien Regad * @param string $user login 3299f5692a3SDamien Regad * @param array $info User data 3309f5692a3SDamien Regad * @return bool 3319f5692a3SDamien Regad */ 332b741ff68SDamien Regad protected function applyFilter($user, $info) 333b741ff68SDamien Regad { 3349f5692a3SDamien Regad foreach ($this->filter as $elem => $pattern) { 3359f5692a3SDamien Regad if ($elem == 'grps') { 33637a3480aSDamien Regad if (!preg_grep($pattern, $info['grps'])) { 3379f5692a3SDamien Regad return false; 3389f5692a3SDamien Regad } 3399f5692a3SDamien Regad } else { 3409f5692a3SDamien Regad if (!preg_match($pattern, $info[$elem])) { 3419f5692a3SDamien Regad return false; 3429f5692a3SDamien Regad } 3439f5692a3SDamien Regad } 3449f5692a3SDamien Regad } 3459f5692a3SDamien Regad return true; 3469f5692a3SDamien Regad } 34724cd6f55SDamien Regad} 34824cd6f55SDamien Regad 3490e6cb03cSDamien Regad// vim:ts=4:sw=4:noet: 350