xref: /plugin/authwordpress/auth.php (revision 9f5692a39275af609ceca5f5aa005a5ee5e807a5)
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