xref: /plugin/authwordpress/auth.php (revision acc20be00924f7d3f29b1836a8392cffda0ec5f3)
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)
475d099ac1SDamien 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
575d099ac1SDamien Regad     * @var PDO $db
58015f33b2SDamien Regad     */
590341717fSDamien Regad    protected $db;
60015f33b2SDamien Regad
61d35aa3ecSDamien Regad    /**
62d35aa3ecSDamien Regad     * Users cache
635d099ac1SDamien 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
705d099ac1SDamien Regad     * @var bool $usersCached
71d35aa3ecSDamien Regad     */
72d35aa3ecSDamien Regad    protected $usersCached = false;
73d35aa3ecSDamien Regad
749f5692a3SDamien Regad    /**
759f5692a3SDamien Regad     * Filter pattern
765d099ac1SDamien 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();
152*acc20be0SDamien Regad        $count = $i = 0;
1539f5692a3SDamien Regad        foreach ($this->users as $user => $info) {
1549f5692a3SDamien Regad            if ($this->applyFilter($user, $info)) {
1559f5692a3SDamien Regad                if ($i >= $start) {
1569f5692a3SDamien Regad                    $list[$user] = $info;
1579f5692a3SDamien Regad                    $count++;
1589f5692a3SDamien Regad                    if ($limit > 0 && $count >= $limit) {
1599f5692a3SDamien Regad                        break;
1609f5692a3SDamien Regad                    }
1619f5692a3SDamien Regad                }
1629f5692a3SDamien Regad                $i++;
1639f5692a3SDamien Regad            }
1649f5692a3SDamien Regad        }
1659f5692a3SDamien Regad
1669f5692a3SDamien Regad        return $list;
16716ceb666SDamien Regad    }
16816ceb666SDamien Regad
169aa69c0cfSDamien Regad    /**
170aa69c0cfSDamien Regad     * Return a count of the number of user which meet $filter criteria
171aa69c0cfSDamien Regad     *
172aa69c0cfSDamien Regad     * @param array $filter
173aa69c0cfSDamien Regad     * @return int
174aa69c0cfSDamien Regad     */
175b741ff68SDamien Regad    public function getUserCount($filter = array())
176b741ff68SDamien Regad    {
177aa69c0cfSDamien Regad        $this->cacheAllUsers();
1789f5692a3SDamien Regad
1799f5692a3SDamien Regad        if (empty($filter)) {
1809f5692a3SDamien Regad            $count = count($this->users);
1819f5692a3SDamien Regad        } else {
1829f5692a3SDamien Regad            $this->setFilter($filter);
183*acc20be0SDamien Regad            $count = 0;
1849f5692a3SDamien Regad            foreach ($this->users as $user => $info) {
1859f5692a3SDamien Regad                $count += (int)$this->applyFilter($user, $info);
1869f5692a3SDamien Regad            }
1879f5692a3SDamien Regad        }
1889f5692a3SDamien Regad        return $count;
189aa69c0cfSDamien Regad    }
190aa69c0cfSDamien Regad
19135dd80b8SDamien Regad
19224cd6f55SDamien Regad    /**
19335dd80b8SDamien Regad     * Returns info about the given user
19424cd6f55SDamien Regad     *
19524cd6f55SDamien Regad     * @param string $user the user name
1965d099ac1SDamien Regad     * @param bool   $requireGroups defaults to true
1975d099ac1SDamien Regad     * @return array|false containing user data or false in case of error
19824cd6f55SDamien Regad     */
199b741ff68SDamien Regad    public function getUserData($user, $requireGroups = true)
200b741ff68SDamien Regad    {
201d35aa3ecSDamien Regad        if (isset($this->users[$user])) {
202d35aa3ecSDamien Regad            return $this->users[$user];
203d35aa3ecSDamien Regad        }
204d35aa3ecSDamien Regad
2052ef3e6a1SDamien Regad        $sql = $this->sql_wp_user_data
2062ef3e6a1SDamien Regad            . 'WHERE user_login = :user';
20735dd80b8SDamien Regad
2082ef3e6a1SDamien Regad        $stmt = $this->db->prepare($sql);
20935dd80b8SDamien Regad        $stmt->bindParam(':user', $user);
2102ef3e6a1SDamien Regad        dbglog("Retrieving data for user '$user'\n$sql");
21135dd80b8SDamien Regad
21235dd80b8SDamien Regad        if (!$stmt->execute()) {
2139520968dSDamien Regad            // Query execution failed
214eed09871SDamien Regad            $err = $stmt->errorInfo();
215eed09871SDamien Regad            dbglog("Error $err[1]: $err[2]");
21624cd6f55SDamien Regad            return false;
21724cd6f55SDamien Regad        }
2189520968dSDamien Regad
2199520968dSDamien Regad        $user = $stmt->fetch(PDO::FETCH_ASSOC);
2209520968dSDamien Regad        if ($user === false) {
2219520968dSDamien Regad            // Unknown user
222eed09871SDamien Regad            dbglog("Unknown user");
2239520968dSDamien Regad            return false;
2249520968dSDamien Regad        }
22524cd6f55SDamien Regad
226d35aa3ecSDamien Regad        return $this->cacheUser($user);
22724cd6f55SDamien Regad    }
22824cd6f55SDamien Regad
22924cd6f55SDamien Regad
23024cd6f55SDamien Regad    /**
23135dd80b8SDamien Regad     * Connect to Wordpress database
232015f33b2SDamien Regad     * Initializes $db property as PDO object
23324cd6f55SDamien Regad     */
234b741ff68SDamien Regad    protected function connectWordpressDb()
235b741ff68SDamien Regad    {
236cb81639bSDamien Regad        if ($this->db) {
237cb81639bSDamien Regad            // Already connected
238cb81639bSDamien Regad            return;
239cb81639bSDamien Regad        }
240cb81639bSDamien Regad
241cb81639bSDamien Regad        // Build connection string
24235dd80b8SDamien Regad        $dsn = array(
24335dd80b8SDamien Regad            'host=' . $this->getConf('hostname'),
24435dd80b8SDamien Regad            'dbname=' . $this->getConf('database'),
24502fbe6b4SDamien Regad            'charset=UTF8',
24635dd80b8SDamien Regad        );
24735dd80b8SDamien Regad        $port = $this->getConf('port');
24835dd80b8SDamien Regad        if ($port) {
24935dd80b8SDamien Regad            $dsn[] = 'port=' . $port;
25035dd80b8SDamien Regad        }
25135dd80b8SDamien Regad        $dsn = 'mysql:' . implode(';', $dsn);
25235dd80b8SDamien Regad
253015f33b2SDamien Regad        $this->db = new PDO($dsn, $this->getConf('username'), $this->getConf('password'));
25424cd6f55SDamien Regad    }
25524cd6f55SDamien Regad
256d1f83a80SDamien Regad    /**
257d1f83a80SDamien Regad     * Convert a Wordpress DB User row to DokuWiki user info array
258d35aa3ecSDamien Regad     * and stores it in the users cache
259d1f83a80SDamien Regad     *
2605d099ac1SDamien Regad     * @param  array $row Raw Wordpress user table row
261d1f83a80SDamien Regad     * @return array user data
262d1f83a80SDamien Regad     */
263b741ff68SDamien Regad    protected function cacheUser($row)
264b741ff68SDamien Regad    {
265d1f83a80SDamien Regad        global $conf;
266d1f83a80SDamien Regad
267d35aa3ecSDamien Regad        $login = $row['user_login'];
268d35aa3ecSDamien Regad
269d35aa3ecSDamien Regad        // If the user is already cached, just return it
270d35aa3ecSDamien Regad        if (isset($this->users[$login])) {
271d35aa3ecSDamien Regad            return $this->users[$login];
272d35aa3ecSDamien Regad        }
273d35aa3ecSDamien Regad
274d1f83a80SDamien Regad        // Group membership - add DokuWiki's default group
275d35aa3ecSDamien Regad        $groups = array_keys(unserialize($row['groups']));
276d1f83a80SDamien Regad        if ($this->getConf('usedefaultgroup')) {
277d1f83a80SDamien Regad            $groups[] = $conf['defaultgroup'];
278d1f83a80SDamien Regad        }
279d1f83a80SDamien Regad
280d1f83a80SDamien Regad        $info = array(
281d35aa3ecSDamien Regad            'user' => $login,
282d35aa3ecSDamien Regad            'name' => $row['display_name'],
283d35aa3ecSDamien Regad            'pass' => $row['user_pass'],
284d35aa3ecSDamien Regad            'mail' => $row['user_email'],
285d1f83a80SDamien Regad            'grps' => $groups,
286d1f83a80SDamien Regad        );
287d35aa3ecSDamien Regad
288d35aa3ecSDamien Regad        $this->users[$login] = $info;
289d1f83a80SDamien Regad        return $info;
290d1f83a80SDamien Regad    }
291d1f83a80SDamien Regad
292d35aa3ecSDamien Regad    /**
293d35aa3ecSDamien Regad     * Loads all Wordpress users into the cache
294d35aa3ecSDamien Regad     *
295d35aa3ecSDamien Regad     * @return void
296d35aa3ecSDamien Regad     */
297b741ff68SDamien Regad    protected function cacheAllUsers()
298b741ff68SDamien Regad    {
299d35aa3ecSDamien Regad        if ($this->usersCached) {
300d35aa3ecSDamien Regad            return;
301d35aa3ecSDamien Regad        }
302d35aa3ecSDamien Regad
303d35aa3ecSDamien Regad        $stmt = $this->db->prepare($this->sql_wp_user_data);
304d35aa3ecSDamien Regad        $stmt->execute();
305d35aa3ecSDamien Regad
306d35aa3ecSDamien Regad        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $user) {
307d35aa3ecSDamien Regad            $this->cacheUser($user);
308d35aa3ecSDamien Regad        }
309d35aa3ecSDamien Regad
310d35aa3ecSDamien Regad        $this->usersCached = true;
311d35aa3ecSDamien Regad    }
312d35aa3ecSDamien Regad
3139f5692a3SDamien Regad    /**
3149f5692a3SDamien Regad     * Build filter patterns from given criteria
3159f5692a3SDamien Regad     *
3169f5692a3SDamien Regad     * @param array $filter
3179f5692a3SDamien Regad     */
318b741ff68SDamien Regad    protected function setFilter($filter)
319b741ff68SDamien Regad    {
3209f5692a3SDamien Regad        $this->filter = array();
3219f5692a3SDamien Regad        foreach ($filter as $field => $value) {
3229f5692a3SDamien Regad            // Build PCRE pattern, utf8 + case insensitive
3239f5692a3SDamien Regad            $this->filter[$field] = '/' . str_replace('/', '\/', $value) . '/ui';
3249f5692a3SDamien Regad        }
3259f5692a3SDamien Regad    }
3269f5692a3SDamien Regad
3279f5692a3SDamien Regad    /**
3289f5692a3SDamien Regad     * Return true if given user matches filter pattern, false otherwise
3299f5692a3SDamien Regad     *
3309f5692a3SDamien Regad     * @param string $user login
3319f5692a3SDamien Regad     * @param array  $info User data
3329f5692a3SDamien Regad     * @return bool
3339f5692a3SDamien Regad     */
334b741ff68SDamien Regad    protected function applyFilter($user, $info)
335b741ff68SDamien Regad    {
3369f5692a3SDamien Regad        foreach ($this->filter as $elem => $pattern) {
3379f5692a3SDamien Regad            if ($elem == 'grps') {
33837a3480aSDamien Regad                if (!preg_grep($pattern, $info['grps'])) {
3399f5692a3SDamien Regad                    return false;
3409f5692a3SDamien Regad                }
3419f5692a3SDamien Regad            } else {
3429f5692a3SDamien Regad                if (!preg_match($pattern, $info[$elem])) {
3439f5692a3SDamien Regad                    return false;
3449f5692a3SDamien Regad                }
3459f5692a3SDamien Regad            }
3469f5692a3SDamien Regad        }
3479f5692a3SDamien Regad        return true;
3489f5692a3SDamien Regad    }
34924cd6f55SDamien Regad}
35024cd6f55SDamien Regad
3510e6cb03cSDamien Regad// vim:ts=4:sw=4:noet:
352