xref: /plugin/authwordpress/auth.php (revision e03e23f386418cbb2902e1f02e9535b3e029d1fe)
124cd6f55SDamien Regad<?php
2*e03e23f3SDamien Regad
324cd6f55SDamien Regad/**
424cd6f55SDamien Regad * DokuWiki Plugin authwordpress (Auth Component)
524cd6f55SDamien Regad *
635dd80b8SDamien Regad * Provides authentication against a WordPress MySQL database backend
735dd80b8SDamien Regad *
835dd80b8SDamien Regad * This program is free software; you can redistribute it and/or modify
935dd80b8SDamien Regad * it under the terms of the GNU General Public License as published by
1035dd80b8SDamien Regad * the Free Software Foundation; version 2 of the License
1135dd80b8SDamien Regad *
1235dd80b8SDamien Regad * This program is distributed in the hope that it will be useful,
1335dd80b8SDamien Regad * but WITHOUT ANY WARRANTY; without even the implied warranty of
1435dd80b8SDamien Regad * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1535dd80b8SDamien Regad * GNU General Public License for more details.
1635dd80b8SDamien Regad *
1735dd80b8SDamien Regad * See the COPYING file in your DokuWiki folder for details
1835dd80b8SDamien Regad *
1924cd6f55SDamien Regad * @author     Damien Regad <dregad@mantisbt.org>
2035dd80b8SDamien Regad * @copyright  2015 Damien Regad
2135dd80b8SDamien Regad * @license    GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
22b5b72c15SDamien Regad * @version    1.1
2335dd80b8SDamien Regad * @link       https://github.com/dregad/dokuwiki-authwordpress
24d7435096SDamien Regad *
25d7435096SDamien Regad * @noinspection PhpComposerExtensionStubsInspection
26d7435096SDamien Regad *               PhpUnused
27d7435096SDamien Regad *               PhpMissingReturnTypeInspection
2824cd6f55SDamien Regad */
2924cd6f55SDamien Regad
3024cd6f55SDamien Regad// must be run within Dokuwiki
31b741ff68SDamien Regadif (!defined('DOKU_INC')) {
32b741ff68SDamien Regad    die();
33b741ff68SDamien Regad}
3424cd6f55SDamien Regad
35308b48d3SDamien Regaduse dokuwiki\Logger;
36308b48d3SDamien Regad
3735dd80b8SDamien Regad/**
3835dd80b8SDamien Regad * WordPress password hashing framework
3935dd80b8SDamien Regad */
4035dd80b8SDamien Regadrequire_once('class-phpass.php');
4135dd80b8SDamien Regad
4235dd80b8SDamien Regad/**
4335dd80b8SDamien Regad * Authentication class
4435dd80b8SDamien Regad */
45b741ff68SDamien Regad// @codingStandardsIgnoreLine
46b741ff68SDamien Regadclass auth_plugin_authwordpress extends DokuWiki_Auth_Plugin
47b741ff68SDamien Regad{
4835dd80b8SDamien Regad    /**
4935dd80b8SDamien Regad     * SQL statement to retrieve User data from WordPress DB
5035dd80b8SDamien Regad     * (including group memberships)
51b5b72c15SDamien Regad     * '%prefix%' will be replaced by the actual prefix (from plugin config)
525d099ac1SDamien Regad     * @var string $sql_wp_user_data
5335dd80b8SDamien Regad     */
540341717fSDamien Regad    protected $sql_wp_user_data = "SELECT
5535dd80b8SDamien Regad            id, user_login, user_pass, user_email, display_name,
56493dbdc6SFerdinand Thiessen            meta_value AS grps
57b5b72c15SDamien Regad        FROM %prefix%users u
582ef3e6a1SDamien Regad        JOIN %prefix%usermeta m ON u.id = m.user_id AND meta_key = '%prefix%capabilities'";
5924cd6f55SDamien Regad
6024cd6f55SDamien Regad    /**
61015f33b2SDamien Regad     * Wordpress database connection
625d099ac1SDamien Regad     * @var PDO $db
63015f33b2SDamien Regad     */
640341717fSDamien Regad    protected $db;
65015f33b2SDamien Regad
66d35aa3ecSDamien Regad    /**
67d35aa3ecSDamien Regad     * Users cache
685d099ac1SDamien Regad     * @var array $users
69d35aa3ecSDamien Regad     */
70d35aa3ecSDamien Regad    protected $users;
71d35aa3ecSDamien Regad
72d35aa3ecSDamien Regad    /**
73d35aa3ecSDamien Regad     * True if all users have been loaded in the cache
74d35aa3ecSDamien Regad     * @see $users
755d099ac1SDamien Regad     * @var bool $usersCached
76d35aa3ecSDamien Regad     */
77d35aa3ecSDamien Regad    protected $usersCached = false;
78d35aa3ecSDamien Regad
799f5692a3SDamien Regad    /**
809f5692a3SDamien Regad     * Filter pattern
815d099ac1SDamien Regad     * @var array $filter
829f5692a3SDamien Regad     */
839f5692a3SDamien Regad    protected $filter;
84015f33b2SDamien Regad
85015f33b2SDamien Regad    /**
8624cd6f55SDamien Regad     * Constructor.
8724cd6f55SDamien Regad     */
88b741ff68SDamien Regad    public function __construct()
89b741ff68SDamien Regad    {
9035dd80b8SDamien Regad        parent::__construct();
9124cd6f55SDamien Regad
92aa69c0cfSDamien Regad        // Plugin capabilities
9316ceb666SDamien Regad        $this->cando['getUsers'] = true;
94aa69c0cfSDamien Regad        $this->cando['getUserCount'] = true;
9516ceb666SDamien Regad
9635dd80b8SDamien Regad        // Try to establish a connection to the WordPress DB
9735dd80b8SDamien Regad        // abort in case of failure
9835dd80b8SDamien Regad        try {
99b741ff68SDamien Regad            $this->connectWordpressDb();
100b741ff68SDamien Regad        } catch (Exception $e) {
10135dd80b8SDamien Regad            msg(sprintf($this->getLang('error_connect_failed'), $e->getMessage()));
10235dd80b8SDamien Regad            $this->success = false;
10335dd80b8SDamien Regad            return;
10435dd80b8SDamien Regad        }
10524cd6f55SDamien Regad
106b5b72c15SDamien Regad        // Initialize SQL query with configured prefix
107b5b72c15SDamien Regad        $this->sql_wp_user_data = str_replace(
108b5b72c15SDamien Regad            '%prefix%',
109b5b72c15SDamien Regad            $this->getConf('prefix'),
110b5b72c15SDamien Regad            $this->sql_wp_user_data
111b5b72c15SDamien Regad        );
112b5b72c15SDamien Regad
11324cd6f55SDamien Regad        $this->success = true;
11424cd6f55SDamien Regad    }
11524cd6f55SDamien Regad
11624cd6f55SDamien Regad
11724cd6f55SDamien Regad    /**
118d7435096SDamien Regad     * Check user+password.
11924cd6f55SDamien Regad     *
12024cd6f55SDamien Regad     * @param   string $user the user name
12124cd6f55SDamien Regad     * @param   string $pass the clear text password
122d7435096SDamien Regad     *
12324cd6f55SDamien Regad     * @return  bool
12435dd80b8SDamien Regad     *
12535dd80b8SDamien Regad     * @uses PasswordHash::CheckPassword WordPress password hasher
12624cd6f55SDamien Regad     */
127b741ff68SDamien Regad    public function checkPass($user, $pass)
128b741ff68SDamien Regad    {
12935dd80b8SDamien Regad        $data = $this->getUserData($user);
13035dd80b8SDamien Regad        if ($data === false) {
13135dd80b8SDamien Regad            return false;
13224cd6f55SDamien Regad        }
13324cd6f55SDamien Regad
13435dd80b8SDamien Regad        $hasher = new PasswordHash(8, true);
135eed09871SDamien Regad        $check = $hasher->CheckPassword($pass, $data['pass']);
136308b48d3SDamien Regad        $this->logDebug("Password " . ($check ? 'OK' : 'Invalid'));
137eed09871SDamien Regad
138eed09871SDamien Regad        return $check;
13935dd80b8SDamien Regad    }
14035dd80b8SDamien Regad
14116ceb666SDamien Regad    /**
142d7435096SDamien Regad     * Bulk retrieval of user data.
14316ceb666SDamien Regad     *
14416ceb666SDamien Regad     * @param   int   $start index of first user to be returned
14516ceb666SDamien Regad     * @param   int   $limit max number of users to be returned
14616ceb666SDamien Regad     * @param   array $filter array of field/pattern pairs
147d7435096SDamien Regad     *
14816ceb666SDamien Regad     * @return  array userinfo (refer getUserData for internal userinfo details)
14916ceb666SDamien Regad     */
150b741ff68SDamien Regad    public function retrieveUsers($start = 0, $limit = 0, $filter = array())
151b741ff68SDamien Regad    {
15216ceb666SDamien Regad        msg($this->getLang('user_list_use_wordpress'));
1532ef3e6a1SDamien Regad
154d35aa3ecSDamien Regad        $this->cacheAllUsers();
1559f5692a3SDamien Regad
1569f5692a3SDamien Regad        // Apply filter and pagination
1579f5692a3SDamien Regad        $this->setFilter($filter);
1589f5692a3SDamien Regad        $list = array();
159acc20be0SDamien Regad        $count = $i = 0;
1609f5692a3SDamien Regad        foreach ($this->users as $user => $info) {
1619f5692a3SDamien Regad            if ($this->applyFilter($user, $info)) {
1629f5692a3SDamien Regad                if ($i >= $start) {
1639f5692a3SDamien Regad                    $list[$user] = $info;
1649f5692a3SDamien Regad                    $count++;
1659f5692a3SDamien Regad                    if ($limit > 0 && $count >= $limit) {
1669f5692a3SDamien Regad                        break;
1679f5692a3SDamien Regad                    }
1689f5692a3SDamien Regad                }
1699f5692a3SDamien Regad                $i++;
1709f5692a3SDamien Regad            }
1719f5692a3SDamien Regad        }
1729f5692a3SDamien Regad
1739f5692a3SDamien Regad        return $list;
17416ceb666SDamien Regad    }
17516ceb666SDamien Regad
176aa69c0cfSDamien Regad    /**
177d7435096SDamien Regad     * Return a count of the number of user which meet $filter criteria.
178aa69c0cfSDamien Regad     *
179aa69c0cfSDamien Regad     * @param array $filter
180d7435096SDamien Regad     *
181aa69c0cfSDamien Regad     * @return int
182aa69c0cfSDamien Regad     */
183b741ff68SDamien Regad    public function getUserCount($filter = array())
184b741ff68SDamien Regad    {
185aa69c0cfSDamien Regad        $this->cacheAllUsers();
1869f5692a3SDamien Regad
1879f5692a3SDamien Regad        if (empty($filter)) {
1889f5692a3SDamien Regad            $count = count($this->users);
1899f5692a3SDamien Regad        } else {
1909f5692a3SDamien Regad            $this->setFilter($filter);
191acc20be0SDamien Regad            $count = 0;
1929f5692a3SDamien Regad            foreach ($this->users as $user => $info) {
1939f5692a3SDamien Regad                $count += (int)$this->applyFilter($user, $info);
1949f5692a3SDamien Regad            }
1959f5692a3SDamien Regad        }
1969f5692a3SDamien Regad        return $count;
197aa69c0cfSDamien Regad    }
198aa69c0cfSDamien Regad
19935dd80b8SDamien Regad
20024cd6f55SDamien Regad    /**
201d7435096SDamien Regad     * Returns info about the given user.
20224cd6f55SDamien Regad     *
20324cd6f55SDamien Regad     * @param string $user the user name
2045d099ac1SDamien Regad     * @param bool   $requireGroups defaults to true
205d7435096SDamien Regad     *
2065d099ac1SDamien Regad     * @return array|false containing user data or false in case of error
20724cd6f55SDamien Regad     */
208b741ff68SDamien Regad    public function getUserData($user, $requireGroups = true)
209b741ff68SDamien Regad    {
210d35aa3ecSDamien Regad        if (isset($this->users[$user])) {
211d35aa3ecSDamien Regad            return $this->users[$user];
212d35aa3ecSDamien Regad        }
213d35aa3ecSDamien Regad
2142ef3e6a1SDamien Regad        $sql = $this->sql_wp_user_data
2152ef3e6a1SDamien Regad            . 'WHERE user_login = :user';
21635dd80b8SDamien Regad
2172ef3e6a1SDamien Regad        $stmt = $this->db->prepare($sql);
21835dd80b8SDamien Regad        $stmt->bindParam(':user', $user);
219308b48d3SDamien Regad        $this->logDebug("Retrieving data for user '$user'\n$sql");
22035dd80b8SDamien Regad
22135dd80b8SDamien Regad        if (!$stmt->execute()) {
2229520968dSDamien Regad            // Query execution failed
223eed09871SDamien Regad            $err = $stmt->errorInfo();
224308b48d3SDamien Regad            $this->logDebug("Error $err[1]: $err[2]");
22524cd6f55SDamien Regad            return false;
22624cd6f55SDamien Regad        }
2279520968dSDamien Regad
2289520968dSDamien Regad        $user = $stmt->fetch(PDO::FETCH_ASSOC);
2299520968dSDamien Regad        if ($user === false) {
2309520968dSDamien Regad            // Unknown user
231308b48d3SDamien Regad            $this->logDebug("Unknown user");
2329520968dSDamien Regad            return false;
2339520968dSDamien Regad        }
23424cd6f55SDamien Regad
235d35aa3ecSDamien Regad        return $this->cacheUser($user);
23624cd6f55SDamien Regad    }
23724cd6f55SDamien Regad
23824cd6f55SDamien Regad
23924cd6f55SDamien Regad    /**
240d7435096SDamien Regad     * Connect to Wordpress database.
241d7435096SDamien Regad     *
242d7435096SDamien Regad     * Initializes $db property as PDO object.
243d7435096SDamien Regad     *
244d7435096SDamien Regad     * @return void
24524cd6f55SDamien Regad     */
246d7435096SDamien Regad    protected function connectWordpressDb(): void
247b741ff68SDamien Regad    {
248cb81639bSDamien Regad        if ($this->db) {
249cb81639bSDamien Regad            // Already connected
250cb81639bSDamien Regad            return;
251cb81639bSDamien Regad        }
252cb81639bSDamien Regad
253cb81639bSDamien Regad        // Build connection string
25435dd80b8SDamien Regad        $dsn = array(
25535dd80b8SDamien Regad            'host=' . $this->getConf('hostname'),
25635dd80b8SDamien Regad            'dbname=' . $this->getConf('database'),
25702fbe6b4SDamien Regad            'charset=UTF8',
25835dd80b8SDamien Regad        );
25935dd80b8SDamien Regad        $port = $this->getConf('port');
26035dd80b8SDamien Regad        if ($port) {
26135dd80b8SDamien Regad            $dsn[] = 'port=' . $port;
26235dd80b8SDamien Regad        }
26335dd80b8SDamien Regad        $dsn = 'mysql:' . implode(';', $dsn);
26435dd80b8SDamien Regad
265015f33b2SDamien Regad        $this->db = new PDO($dsn, $this->getConf('username'), $this->getConf('password'));
26624cd6f55SDamien Regad    }
26724cd6f55SDamien Regad
268d1f83a80SDamien Regad    /**
269d7435096SDamien Regad     * Cache User Data.
270d7435096SDamien Regad     *
271d1f83a80SDamien Regad     * Convert a Wordpress DB User row to DokuWiki user info array
272d7435096SDamien Regad     * and stores it in the users cache.
273d1f83a80SDamien Regad     *
2745d099ac1SDamien Regad     * @param  array $row Raw Wordpress user table row
275d7435096SDamien Regad     *
276d1f83a80SDamien Regad     * @return array user data
277d1f83a80SDamien Regad     */
278d7435096SDamien Regad    protected function cacheUser(array $row): array
279b741ff68SDamien Regad    {
280d1f83a80SDamien Regad        global $conf;
281d1f83a80SDamien Regad
282d35aa3ecSDamien Regad        $login = $row['user_login'];
283d35aa3ecSDamien Regad
284d35aa3ecSDamien Regad        // If the user is already cached, just return it
285d35aa3ecSDamien Regad        if (isset($this->users[$login])) {
286d35aa3ecSDamien Regad            return $this->users[$login];
287d35aa3ecSDamien Regad        }
288d35aa3ecSDamien Regad
289d1f83a80SDamien Regad        // Group membership - add DokuWiki's default group
290493dbdc6SFerdinand Thiessen        $groups = array_keys(unserialize($row['grps']));
291d1f83a80SDamien Regad        if ($this->getConf('usedefaultgroup')) {
292d1f83a80SDamien Regad            $groups[] = $conf['defaultgroup'];
293d1f83a80SDamien Regad        }
294d1f83a80SDamien Regad
295d1f83a80SDamien Regad        $info = array(
296d35aa3ecSDamien Regad            'user' => $login,
297d35aa3ecSDamien Regad            'name' => $row['display_name'],
298d35aa3ecSDamien Regad            'pass' => $row['user_pass'],
299d35aa3ecSDamien Regad            'mail' => $row['user_email'],
300d1f83a80SDamien Regad            'grps' => $groups,
301d1f83a80SDamien Regad        );
302d35aa3ecSDamien Regad
303d35aa3ecSDamien Regad        $this->users[$login] = $info;
304d1f83a80SDamien Regad        return $info;
305d1f83a80SDamien Regad    }
306d1f83a80SDamien Regad
307d35aa3ecSDamien Regad    /**
308d7435096SDamien Regad     * Loads all Wordpress users into the cache.
309d35aa3ecSDamien Regad     *
310d35aa3ecSDamien Regad     * @return void
311d35aa3ecSDamien Regad     */
312b741ff68SDamien Regad    protected function cacheAllUsers()
313b741ff68SDamien Regad    {
314d35aa3ecSDamien Regad        if ($this->usersCached) {
315d35aa3ecSDamien Regad            return;
316d35aa3ecSDamien Regad        }
317d35aa3ecSDamien Regad
318d35aa3ecSDamien Regad        $stmt = $this->db->prepare($this->sql_wp_user_data);
319d35aa3ecSDamien Regad        $stmt->execute();
320d35aa3ecSDamien Regad
321d35aa3ecSDamien Regad        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $user) {
322d35aa3ecSDamien Regad            $this->cacheUser($user);
323d35aa3ecSDamien Regad        }
324d35aa3ecSDamien Regad
325d35aa3ecSDamien Regad        $this->usersCached = true;
326d35aa3ecSDamien Regad    }
327d35aa3ecSDamien Regad
3289f5692a3SDamien Regad    /**
329d7435096SDamien Regad     * Build filter patterns from given criteria.
3309f5692a3SDamien Regad     *
3319f5692a3SDamien Regad     * @param array $filter
332d7435096SDamien Regad     *
333d7435096SDamien Regad     * @return void
3349f5692a3SDamien Regad     */
335d7435096SDamien Regad    protected function setFilter(array $filter): void
336b741ff68SDamien Regad    {
3379f5692a3SDamien Regad        $this->filter = array();
3389f5692a3SDamien Regad        foreach ($filter as $field => $value) {
3399f5692a3SDamien Regad            // Build PCRE pattern, utf8 + case insensitive
3409f5692a3SDamien Regad            $this->filter[$field] = '/' . str_replace('/', '\/', $value) . '/ui';
3419f5692a3SDamien Regad        }
3429f5692a3SDamien Regad    }
3439f5692a3SDamien Regad
3449f5692a3SDamien Regad    /**
345d7435096SDamien Regad     * Return true if given user matches filter pattern, false otherwise.
3469f5692a3SDamien Regad     *
3479f5692a3SDamien Regad     * @param string $user login
3489f5692a3SDamien Regad     * @param array  $info User data
349d7435096SDamien Regad     *
3509f5692a3SDamien Regad     * @return bool
3519f5692a3SDamien Regad     */
352d7435096SDamien Regad    protected function applyFilter(string $user, array $info): bool
353b741ff68SDamien Regad    {
3549f5692a3SDamien Regad        foreach ($this->filter as $elem => $pattern) {
3559f5692a3SDamien Regad            if ($elem == 'grps') {
35637a3480aSDamien Regad                if (!preg_grep($pattern, $info['grps'])) {
3579f5692a3SDamien Regad                    return false;
3589f5692a3SDamien Regad                }
3599f5692a3SDamien Regad            } else {
3609f5692a3SDamien Regad                if (!preg_match($pattern, $info[$elem])) {
3619f5692a3SDamien Regad                    return false;
3629f5692a3SDamien Regad                }
3639f5692a3SDamien Regad            }
3649f5692a3SDamien Regad        }
3659f5692a3SDamien Regad        return true;
3669f5692a3SDamien Regad    }
367308b48d3SDamien Regad
368308b48d3SDamien Regad    /**
369308b48d3SDamien Regad     * Add message to debug log.
370308b48d3SDamien Regad     *
371308b48d3SDamien Regad     * @param string $msg
372308b48d3SDamien Regad     *
373308b48d3SDamien Regad     * @return void
374308b48d3SDamien Regad     */
375308b48d3SDamien Regad    protected function logDebug(string $msg): void
376308b48d3SDamien Regad    {
377308b48d3SDamien Regad        global $updateVersion;
378308b48d3SDamien Regad        if ($updateVersion >= 52) {
379308b48d3SDamien Regad            Logger::debug($msg);
380308b48d3SDamien Regad        } else {
381308b48d3SDamien Regad            dbglog($msg);
382308b48d3SDamien Regad        }
383308b48d3SDamien Regad    }
38424cd6f55SDamien Regad}
38524cd6f55SDamien Regad
3860e6cb03cSDamien Regad// vim:ts=4:sw=4:noet:
387