1<?php 2/** 3 * DokuWiki Plugin authwordpress (Auth Component) 4 * 5 * Provides authentication against a WordPress MySQL database backend 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; version 2 of the License 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * See the COPYING file in your DokuWiki folder for details 17 * 18 * @author Damien Regad <dregad@mantisbt.org> 19 * @copyright 2015 Damien Regad 20 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 21 * @version 1.1 22 * @link https://github.com/dregad/dokuwiki-authwordpress 23 */ 24 25 26// must be run within Dokuwiki 27if(!defined('DOKU_INC')) die(); 28 29/** 30 * WordPress password hashing framework 31 */ 32require_once('class-phpass.php'); 33 34/** 35 * Authentication class 36 */ 37class auth_plugin_authwordpress extends DokuWiki_Auth_Plugin { 38 39 /** 40 * SQL statement to retrieve User data from WordPress DB 41 * (including group memberships) 42 * '%prefix%' will be replaced by the actual prefix (from plugin config) 43 */ 44 protected $sql_wp_user_data = "SELECT 45 id, user_login, user_pass, user_email, display_name, 46 meta_value AS groups 47 FROM %prefix%users u 48 JOIN %prefix%usermeta m ON u.id = m.user_id AND meta_key = '%prefix%capabilities'"; 49 50 /** 51 * Wordpress database connection 52 */ 53 protected $db; 54 55 /** 56 * Users cache 57 */ 58 protected $users; 59 60 /** 61 * True if all users have been loaded in the cache 62 * @see $users 63 */ 64 protected $usersCached = false; 65 66 /** 67 * Filter pattern 68 */ 69 protected $filter; 70 71 /** 72 * Constructor. 73 */ 74 public function __construct() { 75 parent::__construct(); 76 77 // Plugin capabilities 78 $this->cando['getUsers'] = true; 79 $this->cando['getUserCount'] = true; 80 81 // Try to establish a connection to the WordPress DB 82 // abort in case of failure 83 try { 84 $this->wp_connect(); 85 } 86 catch (Exception $e) { 87 msg(sprintf($this->getLang('error_connect_failed'), $e->getMessage())); 88 $this->success = false; 89 return; 90 } 91 92 // Initialize SQL query with configured prefix 93 $this->sql_wp_user_data = str_replace( 94 '%prefix%', 95 $this->getConf('prefix'), 96 $this->sql_wp_user_data 97 ); 98 99 $this->success = true; 100 } 101 102 103 /** 104 * Check user+password 105 * 106 * @param string $user the user name 107 * @param string $pass the clear text password 108 * @return bool 109 * 110 * @uses PasswordHash::CheckPassword WordPress password hasher 111 */ 112 public function checkPass($user, $pass) { 113 $data = $this->getUserData($user); 114 if ($data === false) { 115 return false; 116 } 117 118 $hasher = new PasswordHash(8, true); 119 $check = $hasher->CheckPassword($pass, $data['pass']); 120 dbglog("Password " . ($check ? 'OK' : 'Invalid')); 121 122 return $check; 123 } 124 125 /** 126 * Bulk retrieval of user data 127 * 128 * @param int $start index of first user to be returned 129 * @param int $limit max number of users to be returned 130 * @param array $filter array of field/pattern pairs 131 * @return array userinfo (refer getUserData for internal userinfo details) 132 */ 133 public function retrieveUsers($start = 0, $limit = 0, $filter = array()) { 134 msg($this->getLang('user_list_use_wordpress')); 135 136 $this->cacheAllUsers(); 137 138 // Apply filter and pagination 139 $this->setFilter($filter); 140 $list = array(); 141 foreach($this->users as $user => $info) { 142 if($this->applyFilter($user, $info)) { 143 if($i >= $start) { 144 $list[$user] = $info; 145 $count++; 146 if($limit > 0 && $count >= $limit) { 147 break; 148 } 149 } 150 $i++; 151 } 152 } 153 154 return $list; 155 } 156 157 /** 158 * Return a count of the number of user which meet $filter criteria 159 * 160 * @param array $filter 161 * @return int 162 */ 163 public function getUserCount($filter = array()) { 164 $this->cacheAllUsers(); 165 166 if(empty($filter)) { 167 $count = count($this->users); 168 } else { 169 $this->setFilter($filter); 170 foreach($this->users as $user => $info) { 171 $count += (int)$this->applyFilter($user, $info); 172 } 173 } 174 return $count; 175 } 176 177 178 /** 179 * Returns info about the given user 180 * 181 * @param string $user the user name 182 * @return array containing user data or false 183 */ 184 public function getUserData($user, $requireGroups=true) { 185 if(isset($this->users[$user])) { 186 return $this->users[$user]; 187 } 188 189 $sql = $this->sql_wp_user_data 190 . 'WHERE user_login = :user'; 191 192 $stmt = $this->db->prepare($sql); 193 $stmt->bindParam(':user', $user); 194 dbglog("Retrieving data for user '$user'\n$sql"); 195 196 if (!$stmt->execute()) { 197 // Query execution failed 198 $err = $stmt->errorInfo(); 199 dbglog("Error $err[1]: $err[2]"); 200 return false; 201 } 202 203 $user = $stmt->fetch(PDO::FETCH_ASSOC); 204 if ($user === false) { 205 // Unknown user 206 dbglog("Unknown user"); 207 return false; 208 } 209 210 return $this->cacheUser($user); 211 } 212 213 214 /** 215 * Connect to Wordpress database 216 * Initializes $db property as PDO object 217 */ 218 protected function wp_connect() { 219 if($this->db) { 220 // Already connected 221 return; 222 } 223 224 // Build connection string 225 $dsn = array( 226 'host=' . $this->getConf('hostname'), 227 'dbname=' . $this->getConf('database'), 228 ); 229 $port = $this->getConf('port'); 230 if ($port) { 231 $dsn[] = 'port=' . $port; 232 } 233 $dsn = 'mysql:' . implode(';', $dsn); 234 235 $this->db = new PDO($dsn, $this->getConf('username'), $this->getConf('password')); 236 } 237 238 /** 239 * Convert a Wordpress DB User row to DokuWiki user info array 240 * and stores it in the users cache 241 * 242 * @param array $user Raw Wordpress user table row 243 * @return array user data 244 */ 245 protected function cacheUser($row) { 246 global $conf; 247 248 $login = $row['user_login']; 249 250 // If the user is already cached, just return it 251 if(isset($this->users[$login])) { 252 return $this->users[$login]; 253 } 254 255 // Group membership - add DokuWiki's default group 256 $groups = array_keys(unserialize($row['groups'])); 257 if($this->getConf('usedefaultgroup')) { 258 $groups[] = $conf['defaultgroup']; 259 } 260 261 $info = array( 262 'user' => $login, 263 'name' => $row['display_name'], 264 'pass' => $row['user_pass'], 265 'mail' => $row['user_email'], 266 'grps' => $groups, 267 ); 268 269 $this->users[$login] = $info; 270 return $info; 271 } 272 273 /** 274 * Loads all Wordpress users into the cache 275 * 276 * @return void 277 */ 278 protected function cacheAllUsers() { 279 if($this->usersCached) { 280 return; 281 } 282 283 $stmt = $this->db->prepare($this->sql_wp_user_data); 284 $stmt->execute(); 285 286 foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $user) { 287 $this->cacheUser($user); 288 } 289 290 $this->usersCached = true; 291 } 292 293 /** 294 * Build filter patterns from given criteria 295 * 296 * @param array $filter 297 */ 298 protected function setFilter($filter) { 299 $this->filter = array(); 300 foreach($filter as $field => $value) { 301 // Build PCRE pattern, utf8 + case insensitive 302 $this->filter[$field] = '/' . str_replace('/', '\/', $value) . '/ui'; 303 } 304 } 305 306 /** 307 * Return true if given user matches filter pattern, false otherwise 308 * 309 * @param string $user login 310 * @param array $info User data 311 * @return bool 312 */ 313 protected function applyFilter($user, $info) { 314 foreach ($this->filter as $elem => $pattern) { 315 if ($elem == 'grps') { 316 if (empty(preg_grep($pattern, $info['grps']))) { 317 return false; 318 } 319 } else { 320 if(!preg_match($pattern, $info[$elem])) { 321 return false; 322 } 323 } 324 } 325 return true; 326 } 327 328} 329 330// vim:ts=4:sw=4:noet: 331