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 2724cd6f55SDamien Regadif(!defined('DOKU_INC')) die(); 2824cd6f55SDamien Regad 2935dd80b8SDamien Regad/** 3035dd80b8SDamien Regad * WordPress password hashing framework 3135dd80b8SDamien Regad */ 3235dd80b8SDamien Regadrequire_once('class-phpass.php'); 3335dd80b8SDamien Regad 3435dd80b8SDamien Regad/** 3535dd80b8SDamien Regad * Authentication class 3635dd80b8SDamien Regad */ 3724cd6f55SDamien Regadclass auth_plugin_authwordpress extends DokuWiki_Auth_Plugin { 3824cd6f55SDamien Regad 3935dd80b8SDamien Regad /** 4035dd80b8SDamien Regad * SQL statement to retrieve User data from WordPress DB 4135dd80b8SDamien Regad * (including group memberships) 42b5b72c15SDamien Regad * '%prefix%' will be replaced by the actual prefix (from plugin config) 4335dd80b8SDamien Regad */ 440341717fSDamien Regad protected $sql_wp_user_data = "SELECT 4535dd80b8SDamien Regad id, user_login, user_pass, user_email, display_name, 4635dd80b8SDamien Regad meta_value AS groups 47b5b72c15SDamien Regad FROM %prefix%users u 482ef3e6a1SDamien Regad JOIN %prefix%usermeta m ON u.id = m.user_id AND meta_key = '%prefix%capabilities'"; 4924cd6f55SDamien Regad 5024cd6f55SDamien Regad /** 51015f33b2SDamien Regad * Wordpress database connection 52015f33b2SDamien Regad */ 530341717fSDamien Regad protected $db; 54015f33b2SDamien Regad 55d35aa3ecSDamien Regad /** 56d35aa3ecSDamien Regad * Users cache 57d35aa3ecSDamien Regad */ 58d35aa3ecSDamien Regad protected $users; 59d35aa3ecSDamien Regad 60d35aa3ecSDamien Regad /** 61d35aa3ecSDamien Regad * True if all users have been loaded in the cache 62d35aa3ecSDamien Regad * @see $users 63d35aa3ecSDamien Regad */ 64d35aa3ecSDamien Regad protected $usersCached = false; 65d35aa3ecSDamien Regad 66*9f5692a3SDamien Regad /** 67*9f5692a3SDamien Regad * Filter pattern 68*9f5692a3SDamien Regad */ 69*9f5692a3SDamien Regad protected $filter; 70015f33b2SDamien Regad 71015f33b2SDamien Regad /** 7224cd6f55SDamien Regad * Constructor. 7324cd6f55SDamien Regad */ 7424cd6f55SDamien Regad public function __construct() { 7535dd80b8SDamien Regad parent::__construct(); 7624cd6f55SDamien Regad 77aa69c0cfSDamien Regad // Plugin capabilities 7816ceb666SDamien Regad $this->cando['getUsers'] = true; 79aa69c0cfSDamien Regad $this->cando['getUserCount'] = true; 8016ceb666SDamien Regad 8135dd80b8SDamien Regad // Try to establish a connection to the WordPress DB 8235dd80b8SDamien Regad // abort in case of failure 8335dd80b8SDamien Regad try { 845a360df9SDamien Regad $this->wp_connect(); 8535dd80b8SDamien Regad } 8635dd80b8SDamien Regad catch (Exception $e) { 8735dd80b8SDamien Regad msg(sprintf($this->getLang('error_connect_failed'), $e->getMessage())); 8835dd80b8SDamien Regad $this->success = false; 8935dd80b8SDamien Regad return; 9035dd80b8SDamien Regad } 9124cd6f55SDamien Regad 92b5b72c15SDamien Regad // Initialize SQL query with configured prefix 93b5b72c15SDamien Regad $this->sql_wp_user_data = str_replace( 94b5b72c15SDamien Regad '%prefix%', 95b5b72c15SDamien Regad $this->getConf('prefix'), 96b5b72c15SDamien Regad $this->sql_wp_user_data 97b5b72c15SDamien Regad ); 98b5b72c15SDamien Regad 9924cd6f55SDamien Regad $this->success = true; 10024cd6f55SDamien Regad } 10124cd6f55SDamien Regad 10224cd6f55SDamien Regad 10324cd6f55SDamien Regad /** 10424cd6f55SDamien Regad * Check user+password 10524cd6f55SDamien Regad * 10624cd6f55SDamien Regad * @param string $user the user name 10724cd6f55SDamien Regad * @param string $pass the clear text password 10824cd6f55SDamien Regad * @return bool 10935dd80b8SDamien Regad * 11035dd80b8SDamien Regad * @uses PasswordHash::CheckPassword WordPress password hasher 11124cd6f55SDamien Regad */ 11224cd6f55SDamien Regad public function checkPass($user, $pass) { 11335dd80b8SDamien Regad $data = $this->getUserData($user); 11435dd80b8SDamien Regad if ($data === false) { 11535dd80b8SDamien Regad return false; 11624cd6f55SDamien Regad } 11724cd6f55SDamien Regad 11835dd80b8SDamien Regad $hasher = new PasswordHash(8, true); 119eed09871SDamien Regad $check = $hasher->CheckPassword($pass, $data['pass']); 120eed09871SDamien Regad dbglog("Password " . ($check ? 'OK' : 'Invalid')); 121eed09871SDamien Regad 122eed09871SDamien Regad return $check; 12335dd80b8SDamien Regad } 12435dd80b8SDamien Regad 12516ceb666SDamien Regad /** 12616ceb666SDamien Regad * Bulk retrieval of user data 12716ceb666SDamien Regad * 12816ceb666SDamien Regad * @param int $start index of first user to be returned 12916ceb666SDamien Regad * @param int $limit max number of users to be returned 13016ceb666SDamien Regad * @param array $filter array of field/pattern pairs 13116ceb666SDamien Regad * @return array userinfo (refer getUserData for internal userinfo details) 13216ceb666SDamien Regad */ 13316ceb666SDamien Regad public function retrieveUsers($start = 0, $limit = 0, $filter = array()) { 13416ceb666SDamien Regad msg($this->getLang('user_list_use_wordpress')); 1352ef3e6a1SDamien Regad 136d35aa3ecSDamien Regad $this->cacheAllUsers(); 137*9f5692a3SDamien Regad 138*9f5692a3SDamien Regad // Apply filter and pagination 139*9f5692a3SDamien Regad $this->setFilter($filter); 140*9f5692a3SDamien Regad $list = array(); 141*9f5692a3SDamien Regad foreach($this->users as $user => $info) { 142*9f5692a3SDamien Regad if($this->applyFilter($user, $info)) { 143*9f5692a3SDamien Regad if($i >= $start) { 144*9f5692a3SDamien Regad $list[$user] = $info; 145*9f5692a3SDamien Regad $count++; 146*9f5692a3SDamien Regad if($limit > 0 && $count >= $limit) { 147*9f5692a3SDamien Regad break; 148*9f5692a3SDamien Regad } 149*9f5692a3SDamien Regad } 150*9f5692a3SDamien Regad $i++; 151*9f5692a3SDamien Regad } 152*9f5692a3SDamien Regad } 153*9f5692a3SDamien Regad 154*9f5692a3SDamien Regad return $list; 15516ceb666SDamien Regad } 15616ceb666SDamien Regad 157aa69c0cfSDamien Regad /** 158aa69c0cfSDamien Regad * Return a count of the number of user which meet $filter criteria 159aa69c0cfSDamien Regad * 160aa69c0cfSDamien Regad * @param array $filter 161aa69c0cfSDamien Regad * @return int 162aa69c0cfSDamien Regad */ 163aa69c0cfSDamien Regad public function getUserCount($filter = array()) { 164aa69c0cfSDamien Regad $this->cacheAllUsers(); 165*9f5692a3SDamien Regad 166*9f5692a3SDamien Regad if(empty($filter)) { 167*9f5692a3SDamien Regad $count = count($this->users); 168*9f5692a3SDamien Regad } else { 169*9f5692a3SDamien Regad $this->setFilter($filter); 170*9f5692a3SDamien Regad foreach($this->users as $user => $info) { 171*9f5692a3SDamien Regad $count += (int)$this->applyFilter($user, $info); 172*9f5692a3SDamien Regad } 173*9f5692a3SDamien Regad } 174*9f5692a3SDamien Regad return $count; 175aa69c0cfSDamien Regad } 176aa69c0cfSDamien Regad 17735dd80b8SDamien Regad 17824cd6f55SDamien Regad /** 17935dd80b8SDamien Regad * Returns info about the given user 18024cd6f55SDamien Regad * 18124cd6f55SDamien Regad * @param string $user the user name 18224cd6f55SDamien Regad * @return array containing user data or false 18324cd6f55SDamien Regad */ 1840341717fSDamien Regad public function getUserData($user, $requireGroups=true) { 185d35aa3ecSDamien Regad if(isset($this->users[$user])) { 186d35aa3ecSDamien Regad return $this->users[$user]; 187d35aa3ecSDamien Regad } 188d35aa3ecSDamien Regad 1892ef3e6a1SDamien Regad $sql = $this->sql_wp_user_data 1902ef3e6a1SDamien Regad . 'WHERE user_login = :user'; 19135dd80b8SDamien Regad 1922ef3e6a1SDamien Regad $stmt = $this->db->prepare($sql); 19335dd80b8SDamien Regad $stmt->bindParam(':user', $user); 1942ef3e6a1SDamien Regad dbglog("Retrieving data for user '$user'\n$sql"); 19535dd80b8SDamien Regad 19635dd80b8SDamien Regad if (!$stmt->execute()) { 1979520968dSDamien Regad // Query execution failed 198eed09871SDamien Regad $err = $stmt->errorInfo(); 199eed09871SDamien Regad dbglog("Error $err[1]: $err[2]"); 20024cd6f55SDamien Regad return false; 20124cd6f55SDamien Regad } 2029520968dSDamien Regad 2039520968dSDamien Regad $user = $stmt->fetch(PDO::FETCH_ASSOC); 2049520968dSDamien Regad if ($user === false) { 2059520968dSDamien Regad // Unknown user 206eed09871SDamien Regad dbglog("Unknown user"); 2079520968dSDamien Regad return false; 2089520968dSDamien Regad } 20924cd6f55SDamien Regad 210d35aa3ecSDamien Regad return $this->cacheUser($user); 21124cd6f55SDamien Regad } 21224cd6f55SDamien Regad 21324cd6f55SDamien Regad 21424cd6f55SDamien Regad /** 21535dd80b8SDamien Regad * Connect to Wordpress database 216015f33b2SDamien Regad * Initializes $db property as PDO object 21724cd6f55SDamien Regad */ 2180341717fSDamien Regad protected function wp_connect() { 219cb81639bSDamien Regad if($this->db) { 220cb81639bSDamien Regad // Already connected 221cb81639bSDamien Regad return; 222cb81639bSDamien Regad } 223cb81639bSDamien Regad 224cb81639bSDamien Regad // Build connection string 22535dd80b8SDamien Regad $dsn = array( 22635dd80b8SDamien Regad 'host=' . $this->getConf('hostname'), 22735dd80b8SDamien Regad 'dbname=' . $this->getConf('database'), 22835dd80b8SDamien Regad ); 22935dd80b8SDamien Regad $port = $this->getConf('port'); 23035dd80b8SDamien Regad if ($port) { 23135dd80b8SDamien Regad $dsn[] = 'port=' . $port; 23235dd80b8SDamien Regad } 23335dd80b8SDamien Regad $dsn = 'mysql:' . implode(';', $dsn); 23435dd80b8SDamien Regad 235015f33b2SDamien Regad $this->db = new PDO($dsn, $this->getConf('username'), $this->getConf('password')); 23624cd6f55SDamien Regad } 23724cd6f55SDamien Regad 238d1f83a80SDamien Regad /** 239d1f83a80SDamien Regad * Convert a Wordpress DB User row to DokuWiki user info array 240d35aa3ecSDamien Regad * and stores it in the users cache 241d1f83a80SDamien Regad * 242d1f83a80SDamien Regad * @param array $user Raw Wordpress user table row 243d1f83a80SDamien Regad * @return array user data 244d1f83a80SDamien Regad */ 245d35aa3ecSDamien Regad protected function cacheUser($row) { 246d1f83a80SDamien Regad global $conf; 247d1f83a80SDamien Regad 248d35aa3ecSDamien Regad $login = $row['user_login']; 249d35aa3ecSDamien Regad 250d35aa3ecSDamien Regad // If the user is already cached, just return it 251d35aa3ecSDamien Regad if(isset($this->users[$login])) { 252d35aa3ecSDamien Regad return $this->users[$login]; 253d35aa3ecSDamien Regad } 254d35aa3ecSDamien Regad 255d1f83a80SDamien Regad // Group membership - add DokuWiki's default group 256d35aa3ecSDamien Regad $groups = array_keys(unserialize($row['groups'])); 257d1f83a80SDamien Regad if($this->getConf('usedefaultgroup')) { 258d1f83a80SDamien Regad $groups[] = $conf['defaultgroup']; 259d1f83a80SDamien Regad } 260d1f83a80SDamien Regad 261d1f83a80SDamien Regad $info = array( 262d35aa3ecSDamien Regad 'user' => $login, 263d35aa3ecSDamien Regad 'name' => $row['display_name'], 264d35aa3ecSDamien Regad 'pass' => $row['user_pass'], 265d35aa3ecSDamien Regad 'mail' => $row['user_email'], 266d1f83a80SDamien Regad 'grps' => $groups, 267d1f83a80SDamien Regad ); 268d35aa3ecSDamien Regad 269d35aa3ecSDamien Regad $this->users[$login] = $info; 270d1f83a80SDamien Regad return $info; 271d1f83a80SDamien Regad } 272d1f83a80SDamien Regad 273d35aa3ecSDamien Regad /** 274d35aa3ecSDamien Regad * Loads all Wordpress users into the cache 275d35aa3ecSDamien Regad * 276d35aa3ecSDamien Regad * @return void 277d35aa3ecSDamien Regad */ 278d35aa3ecSDamien Regad protected function cacheAllUsers() { 279d35aa3ecSDamien Regad if($this->usersCached) { 280d35aa3ecSDamien Regad return; 281d35aa3ecSDamien Regad } 282d35aa3ecSDamien Regad 283d35aa3ecSDamien Regad $stmt = $this->db->prepare($this->sql_wp_user_data); 284d35aa3ecSDamien Regad $stmt->execute(); 285d35aa3ecSDamien Regad 286d35aa3ecSDamien Regad foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $user) { 287d35aa3ecSDamien Regad $this->cacheUser($user); 288d35aa3ecSDamien Regad } 289d35aa3ecSDamien Regad 290d35aa3ecSDamien Regad $this->usersCached = true; 291d35aa3ecSDamien Regad } 292d35aa3ecSDamien Regad 293*9f5692a3SDamien Regad /** 294*9f5692a3SDamien Regad * Build filter patterns from given criteria 295*9f5692a3SDamien Regad * 296*9f5692a3SDamien Regad * @param array $filter 297*9f5692a3SDamien Regad */ 298*9f5692a3SDamien Regad protected function setFilter($filter) { 299*9f5692a3SDamien Regad $this->filter = array(); 300*9f5692a3SDamien Regad foreach($filter as $field => $value) { 301*9f5692a3SDamien Regad // Build PCRE pattern, utf8 + case insensitive 302*9f5692a3SDamien Regad $this->filter[$field] = '/' . str_replace('/', '\/', $value) . '/ui'; 303*9f5692a3SDamien Regad } 304*9f5692a3SDamien Regad } 305*9f5692a3SDamien Regad 306*9f5692a3SDamien Regad /** 307*9f5692a3SDamien Regad * Return true if given user matches filter pattern, false otherwise 308*9f5692a3SDamien Regad * 309*9f5692a3SDamien Regad * @param string $user login 310*9f5692a3SDamien Regad * @param array $info User data 311*9f5692a3SDamien Regad * @return bool 312*9f5692a3SDamien Regad */ 313*9f5692a3SDamien Regad protected function applyFilter($user, $info) { 314*9f5692a3SDamien Regad foreach ($this->filter as $elem => $pattern) { 315*9f5692a3SDamien Regad if ($elem == 'grps') { 316*9f5692a3SDamien Regad if (empty(preg_grep($pattern, $info['grps']))) { 317*9f5692a3SDamien Regad return false; 318*9f5692a3SDamien Regad } 319*9f5692a3SDamien Regad } else { 320*9f5692a3SDamien Regad if(!preg_match($pattern, $info[$elem])) { 321*9f5692a3SDamien Regad return false; 322*9f5692a3SDamien Regad } 323*9f5692a3SDamien Regad } 324*9f5692a3SDamien Regad } 325*9f5692a3SDamien Regad return true; 326*9f5692a3SDamien Regad } 327*9f5692a3SDamien Regad 32824cd6f55SDamien Regad} 32924cd6f55SDamien Regad 3300e6cb03cSDamien Regad// vim:ts=4:sw=4:noet: 331