summaryrefslogtreecommitdiff
path: root/platform/www/lib/plugins/authpdo
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/lib/plugins/authpdo')
-rw-r--r--platform/www/lib/plugins/authpdo/README27
-rw-r--r--platform/www/lib/plugins/authpdo/auth.php826
-rw-r--r--platform/www/lib/plugins/authpdo/conf/default.php118
-rw-r--r--platform/www/lib/plugins/authpdo/conf/metadata.php25
-rw-r--r--platform/www/lib/plugins/authpdo/lang/en/lang.php12
-rw-r--r--platform/www/lib/plugins/authpdo/lang/en/settings.php25
-rw-r--r--platform/www/lib/plugins/authpdo/plugin.info.txt7
7 files changed, 1040 insertions, 0 deletions
diff --git a/platform/www/lib/plugins/authpdo/README b/platform/www/lib/plugins/authpdo/README
new file mode 100644
index 0000000..c99bfbf
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/README
@@ -0,0 +1,27 @@
+authpdo Plugin for DokuWiki
+
+Authenticate against a database via PDO
+
+All documentation for this plugin can be found at
+https://www.dokuwiki.org/plugin:authpdo
+
+If you install this plugin manually, make sure it is installed in
+lib/plugins/authpdo/ - if the folder is called different it
+will not work!
+
+Please refer to http://www.dokuwiki.org/plugins for additional info
+on how to install plugins in DokuWiki.
+
+----
+Copyright (C) Andreas Gohr <andi@splitbrain.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+See the COPYING file in your DokuWiki folder for details
diff --git a/platform/www/lib/plugins/authpdo/auth.php b/platform/www/lib/plugins/authpdo/auth.php
new file mode 100644
index 0000000..9c0968e
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/auth.php
@@ -0,0 +1,826 @@
+<?php
+/**
+ * DokuWiki Plugin authpdo (Auth Component)
+ *
+ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+/**
+ * Class auth_plugin_authpdo
+ */
+class auth_plugin_authpdo extends DokuWiki_Auth_Plugin
+{
+
+ /** @var PDO */
+ protected $pdo;
+
+ /** @var null|array The list of all groups */
+ protected $groupcache = null;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct(); // for compatibility
+
+ if (!class_exists('PDO')) {
+ $this->debugMsg('PDO extension for PHP not found.', -1, __LINE__);
+ $this->success = false;
+ return;
+ }
+
+ if (!$this->getConf('dsn')) {
+ $this->debugMsg('No DSN specified', -1, __LINE__);
+ $this->success = false;
+ return;
+ }
+
+ try {
+ $this->pdo = new PDO(
+ $this->getConf('dsn'),
+ $this->getConf('user'),
+ conf_decodeString($this->getConf('pass')),
+ array(
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // always fetch as array
+ PDO::ATTR_EMULATE_PREPARES => true, // emulating prepares allows us to reuse param names
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // we want exceptions, not error codes
+ )
+ );
+ } catch (PDOException $e) {
+ $this->debugMsg($e);
+ msg($this->getLang('connectfail'), -1);
+ $this->success = false;
+ return;
+ }
+
+ // can Users be created?
+ $this->cando['addUser'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'select-groups',
+ 'insert-user',
+ 'insert-group',
+ 'join-group'
+ )
+ );
+
+ // can Users be deleted?
+ $this->cando['delUser'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'select-groups',
+ 'leave-group',
+ 'delete-user'
+ )
+ );
+
+ // can login names be changed?
+ $this->cando['modLogin'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'update-user-login'
+ )
+ );
+
+ // can passwords be changed?
+ $this->cando['modPass'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'update-user-pass'
+ )
+ );
+
+ // can real names be changed?
+ $this->cando['modName'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'update-user-info:name'
+ )
+ );
+
+ // can real email be changed?
+ $this->cando['modMail'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'update-user-info:mail'
+ )
+ );
+
+ // can groups be changed?
+ $this->cando['modGroups'] = $this->checkConfig(
+ array(
+ 'select-user',
+ 'select-user-groups',
+ 'select-groups',
+ 'leave-group',
+ 'join-group',
+ 'insert-group'
+ )
+ );
+
+ // can a filtered list of users be retrieved?
+ $this->cando['getUsers'] = $this->checkConfig(
+ array(
+ 'list-users'
+ )
+ );
+
+ // can the number of users be retrieved?
+ $this->cando['getUserCount'] = $this->checkConfig(
+ array(
+ 'count-users'
+ )
+ );
+
+ // can a list of available groups be retrieved?
+ $this->cando['getGroups'] = $this->checkConfig(
+ array(
+ 'select-groups'
+ )
+ );
+
+ $this->success = true;
+ }
+
+ /**
+ * Check user+password
+ *
+ * @param string $user the user name
+ * @param string $pass the clear text password
+ * @return bool
+ */
+ public function checkPass($user, $pass)
+ {
+
+ $userdata = $this->selectUser($user);
+ if ($userdata == false) return false;
+
+ // password checking done in SQL?
+ if ($this->checkConfig(array('check-pass'))) {
+ $userdata['clear'] = $pass;
+ $userdata['hash'] = auth_cryptPassword($pass);
+ $result = $this->query($this->getConf('check-pass'), $userdata);
+ if ($result === false) return false;
+ return (count($result) == 1);
+ }
+
+ // we do password checking on our own
+ if (isset($userdata['hash'])) {
+ // hashed password
+ $passhash = new \dokuwiki\PassHash();
+ return $passhash->verify_hash($pass, $userdata['hash']);
+ } else {
+ // clear text password in the database O_o
+ return ($pass === $userdata['clear']);
+ }
+ }
+
+ /**
+ * Return user info
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ * @param string $user the user name
+ * @param bool $requireGroups whether or not the returned data must include groups
+ * @return array|bool containing user data or false
+ */
+ public function getUserData($user, $requireGroups = true)
+ {
+ $data = $this->selectUser($user);
+ if ($data == false) return false;
+
+ if (isset($data['hash'])) unset($data['hash']);
+ if (isset($data['clean'])) unset($data['clean']);
+
+ if ($requireGroups) {
+ $data['grps'] = $this->selectUserGroups($data);
+ if ($data['grps'] === false) return false;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Create a new User [implement only where required/possible]
+ *
+ * Returns false if the user already exists, null when an error
+ * occurred and true if everything went well.
+ *
+ * The new user HAS TO be added to the default group by this
+ * function!
+ *
+ * Set addUser capability when implemented
+ *
+ * @param string $user
+ * @param string $clear
+ * @param string $name
+ * @param string $mail
+ * @param null|array $grps
+ * @return bool|null
+ */
+ public function createUser($user, $clear, $name, $mail, $grps = null)
+ {
+ global $conf;
+
+ if (($info = $this->getUserData($user, false)) !== false) {
+ msg($this->getLang('userexists'), -1);
+ return false; // user already exists
+ }
+
+ // prepare data
+ if ($grps == null) $grps = array();
+ array_unshift($grps, $conf['defaultgroup']);
+ $grps = array_unique($grps);
+ $hash = auth_cryptPassword($clear);
+ $userdata = compact('user', 'clear', 'hash', 'name', 'mail');
+
+ // action protected by transaction
+ $this->pdo->beginTransaction();
+ {
+ // insert the user
+ $ok = $this->query($this->getConf('insert-user'), $userdata);
+ if ($ok === false) goto FAIL;
+ $userdata = $this->getUserData($user, false);
+ if ($userdata === false) goto FAIL;
+
+ // create all groups that do not exist, the refetch the groups
+ $allgroups = $this->selectGroups();
+ foreach ($grps as $group) {
+ if (!isset($allgroups[$group])) {
+ $ok = $this->addGroup($group);
+ if ($ok === false) goto FAIL;
+ }
+ }
+ $allgroups = $this->selectGroups();
+
+ // add user to the groups
+ foreach ($grps as $group) {
+ $ok = $this->joinGroup($userdata, $allgroups[$group]);
+ if ($ok === false) goto FAIL;
+ }
+ }
+ $this->pdo->commit();
+ return true;
+
+ // something went wrong, rollback
+ FAIL:
+ $this->pdo->rollBack();
+ $this->debugMsg('Transaction rolled back', 0, __LINE__);
+ msg($this->getLang('writefail'), -1);
+ return null; // return error
+ }
+
+ /**
+ * Modify user data
+ *
+ * @param string $user nick of the user to be changed
+ * @param array $changes array of field/value pairs to be changed (password will be clear text)
+ * @return bool
+ */
+ public function modifyUser($user, $changes)
+ {
+ // secure everything in transaction
+ $this->pdo->beginTransaction();
+ {
+ $olddata = $this->getUserData($user);
+ $oldgroups = $olddata['grps'];
+ unset($olddata['grps']);
+
+ // changing the user name?
+ if (isset($changes['user'])) {
+ if ($this->getUserData($changes['user'], false)) goto FAIL;
+ $params = $olddata;
+ $params['newlogin'] = $changes['user'];
+
+ $ok = $this->query($this->getConf('update-user-login'), $params);
+ if ($ok === false) goto FAIL;
+ }
+
+ // changing the password?
+ if (isset($changes['pass'])) {
+ $params = $olddata;
+ $params['clear'] = $changes['pass'];
+ $params['hash'] = auth_cryptPassword($changes['pass']);
+
+ $ok = $this->query($this->getConf('update-user-pass'), $params);
+ if ($ok === false) goto FAIL;
+ }
+
+ // changing info?
+ if (isset($changes['mail']) || isset($changes['name'])) {
+ $params = $olddata;
+ if (isset($changes['mail'])) $params['mail'] = $changes['mail'];
+ if (isset($changes['name'])) $params['name'] = $changes['name'];
+
+ $ok = $this->query($this->getConf('update-user-info'), $params);
+ if ($ok === false) goto FAIL;
+ }
+
+ // changing groups?
+ if (isset($changes['grps'])) {
+ $allgroups = $this->selectGroups();
+
+ // remove membership for previous groups
+ foreach ($oldgroups as $group) {
+ if (!in_array($group, $changes['grps']) && isset($allgroups[$group])) {
+ $ok = $this->leaveGroup($olddata, $allgroups[$group]);
+ if ($ok === false) goto FAIL;
+ }
+ }
+
+ // create all new groups that are missing
+ $added = 0;
+ foreach ($changes['grps'] as $group) {
+ if (!isset($allgroups[$group])) {
+ $ok = $this->addGroup($group);
+ if ($ok === false) goto FAIL;
+ $added++;
+ }
+ }
+ // reload group info
+ if ($added > 0) $allgroups = $this->selectGroups();
+
+ // add membership for new groups
+ foreach ($changes['grps'] as $group) {
+ if (!in_array($group, $oldgroups)) {
+ $ok = $this->joinGroup($olddata, $allgroups[$group]);
+ if ($ok === false) goto FAIL;
+ }
+ }
+ }
+
+ }
+ $this->pdo->commit();
+ return true;
+
+ // something went wrong, rollback
+ FAIL:
+ $this->pdo->rollBack();
+ $this->debugMsg('Transaction rolled back', 0, __LINE__);
+ msg($this->getLang('writefail'), -1);
+ return false; // return error
+ }
+
+ /**
+ * Delete one or more users
+ *
+ * Set delUser capability when implemented
+ *
+ * @param array $users
+ * @return int number of users deleted
+ */
+ public function deleteUsers($users)
+ {
+ $count = 0;
+ foreach ($users as $user) {
+ if ($this->deleteUser($user)) $count++;
+ }
+ return $count;
+ }
+
+ /**
+ * Bulk retrieval of user data [implement only where required/possible]
+ *
+ * Set getUsers capability when implemented
+ *
+ * @param int $start index of first user to be returned
+ * @param int $limit max number of users to be returned
+ * @param array $filter array of field/pattern pairs, null for no filter
+ * @return array list of userinfo (refer getUserData for internal userinfo details)
+ */
+ public function retrieveUsers($start = 0, $limit = -1, $filter = null)
+ {
+ if ($limit < 0) $limit = 10000; // we don't support no limit
+ if (is_null($filter)) $filter = array();
+
+ if (isset($filter['grps'])) $filter['group'] = $filter['grps'];
+ foreach (array('user', 'name', 'mail', 'group') as $key) {
+ if (!isset($filter[$key])) {
+ $filter[$key] = '%';
+ } else {
+ $filter[$key] = '%' . $filter[$key] . '%';
+ }
+ }
+ $filter['start'] = (int)$start;
+ $filter['end'] = (int)$start + $limit;
+ $filter['limit'] = (int)$limit;
+
+ $result = $this->query($this->getConf('list-users'), $filter);
+ if (!$result) return array();
+ $users = array();
+ if (is_array($result)) {
+ foreach ($result as $row) {
+ if (!isset($row['user'])) {
+ $this->debugMsg("list-users statement did not return 'user' attribute", -1, __LINE__);
+ return array();
+ }
+ $users[] = $this->getUserData($row['user']);
+ }
+ } else {
+ $this->debugMsg("list-users statement did not return a list of result", -1, __LINE__);
+ }
+ return $users;
+ }
+
+ /**
+ * Return a count of the number of user which meet $filter criteria
+ *
+ * @param array $filter array of field/pattern pairs, empty array for no filter
+ * @return int
+ */
+ public function getUserCount($filter = array())
+ {
+ if (is_null($filter)) $filter = array();
+
+ if (isset($filter['grps'])) $filter['group'] = $filter['grps'];
+ foreach (array('user', 'name', 'mail', 'group') as $key) {
+ if (!isset($filter[$key])) {
+ $filter[$key] = '%';
+ } else {
+ $filter[$key] = '%' . $filter[$key] . '%';
+ }
+ }
+
+ $result = $this->query($this->getConf('count-users'), $filter);
+ if (!$result || !isset($result[0]['count'])) {
+ $this->debugMsg("Statement did not return 'count' attribute", -1, __LINE__);
+ }
+ return (int)$result[0]['count'];
+ }
+
+ /**
+ * Create a new group with the given name
+ *
+ * @param string $group
+ * @return bool
+ */
+ public function addGroup($group)
+ {
+ $sql = $this->getConf('insert-group');
+
+ $result = $this->query($sql, array(':group' => $group));
+ $this->clearGroupCache();
+ if ($result === false) return false;
+ return true;
+ }
+
+ /**
+ * Retrieve groups
+ *
+ * Set getGroups capability when implemented
+ *
+ * @param int $start
+ * @param int $limit
+ * @return array
+ */
+ public function retrieveGroups($start = 0, $limit = 0)
+ {
+ $groups = array_keys($this->selectGroups());
+ if ($groups === false) return array();
+
+ if (!$limit) {
+ return array_splice($groups, $start);
+ } else {
+ return array_splice($groups, $start, $limit);
+ }
+ }
+
+ /**
+ * Select data of a specified user
+ *
+ * @param string $user the user name
+ * @return bool|array user data, false on error
+ */
+ protected function selectUser($user)
+ {
+ $sql = $this->getConf('select-user');
+
+ $result = $this->query($sql, array(':user' => $user));
+ if (!$result) return false;
+
+ if (count($result) > 1) {
+ $this->debugMsg('Found more than one matching user', -1, __LINE__);
+ return false;
+ }
+
+ $data = array_shift($result);
+ $dataok = true;
+
+ if (!isset($data['user'])) {
+ $this->debugMsg("Statement did not return 'user' attribute", -1, __LINE__);
+ $dataok = false;
+ }
+ if (!isset($data['hash']) && !isset($data['clear']) && !$this->checkConfig(array('check-pass'))) {
+ $this->debugMsg("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
+ $dataok = false;
+ }
+ if (!isset($data['name'])) {
+ $this->debugMsg("Statement did not return 'name' attribute", -1, __LINE__);
+ $dataok = false;
+ }
+ if (!isset($data['mail'])) {
+ $this->debugMsg("Statement did not return 'mail' attribute", -1, __LINE__);
+ $dataok = false;
+ }
+
+ if (!$dataok) return false;
+ return $data;
+ }
+
+ /**
+ * Delete a user after removing all their group memberships
+ *
+ * @param string $user
+ * @return bool true when the user was deleted
+ */
+ protected function deleteUser($user)
+ {
+ $this->pdo->beginTransaction();
+ {
+ $userdata = $this->getUserData($user);
+ if ($userdata === false) goto FAIL;
+ $allgroups = $this->selectGroups();
+
+ // remove group memberships (ignore errors)
+ foreach ($userdata['grps'] as $group) {
+ if (isset($allgroups[$group])) {
+ $this->leaveGroup($userdata, $allgroups[$group]);
+ }
+ }
+
+ $ok = $this->query($this->getConf('delete-user'), $userdata);
+ if ($ok === false) goto FAIL;
+ }
+ $this->pdo->commit();
+ return true;
+
+ FAIL:
+ $this->pdo->rollBack();
+ return false;
+ }
+
+ /**
+ * Select all groups of a user
+ *
+ * @param array $userdata The userdata as returned by _selectUser()
+ * @return array|bool list of group names, false on error
+ */
+ protected function selectUserGroups($userdata)
+ {
+ global $conf;
+ $sql = $this->getConf('select-user-groups');
+ $result = $this->query($sql, $userdata);
+ if ($result === false) return false;
+
+ $groups = array($conf['defaultgroup']); // always add default config
+ if (is_array($result)) {
+ foreach ($result as $row) {
+ if (!isset($row['group'])) {
+ $this->debugMsg("No 'group' field returned in select-user-groups statement", -1, __LINE__);
+ return false;
+ }
+ $groups[] = $row['group'];
+ }
+ } else {
+ $this->debugMsg("select-user-groups statement did not return a list of result", -1, __LINE__);
+ }
+
+ $groups = array_unique($groups);
+ sort($groups);
+ return $groups;
+ }
+
+ /**
+ * Select all available groups
+ *
+ * @return array|bool list of all available groups and their properties
+ */
+ protected function selectGroups()
+ {
+ if ($this->groupcache) return $this->groupcache;
+
+ $sql = $this->getConf('select-groups');
+ $result = $this->query($sql);
+ if ($result === false) return false;
+
+ $groups = array();
+ if (is_array($result)) {
+ foreach ($result as $row) {
+ if (!isset($row['group'])) {
+ $this->debugMsg("No 'group' field returned from select-groups statement", -1, __LINE__);
+ return false;
+ }
+
+ // relayout result with group name as key
+ $group = $row['group'];
+ $groups[$group] = $row;
+ }
+ } else {
+ $this->debugMsg("select-groups statement did not return a list of result", -1, __LINE__);
+ }
+
+ ksort($groups);
+ return $groups;
+ }
+
+ /**
+ * Remove all entries from the group cache
+ */
+ protected function clearGroupCache()
+ {
+ $this->groupcache = null;
+ }
+
+ /**
+ * Adds the user to the group
+ *
+ * @param array $userdata all the user data
+ * @param array $groupdata all the group data
+ * @return bool
+ */
+ protected function joinGroup($userdata, $groupdata)
+ {
+ $data = array_merge($userdata, $groupdata);
+ $sql = $this->getConf('join-group');
+ $result = $this->query($sql, $data);
+ if ($result === false) return false;
+ return true;
+ }
+
+ /**
+ * Removes the user from the group
+ *
+ * @param array $userdata all the user data
+ * @param array $groupdata all the group data
+ * @return bool
+ */
+ protected function leaveGroup($userdata, $groupdata)
+ {
+ $data = array_merge($userdata, $groupdata);
+ $sql = $this->getConf('leave-group');
+ $result = $this->query($sql, $data);
+ if ($result === false) return false;
+ return true;
+ }
+
+ /**
+ * Executes a query
+ *
+ * @param string $sql The SQL statement to execute
+ * @param array $arguments Named parameters to be used in the statement
+ * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error
+ */
+ protected function query($sql, $arguments = array())
+ {
+ $sql = trim($sql);
+ if (empty($sql)) {
+ $this->debugMsg('No SQL query given', -1, __LINE__);
+ return false;
+ }
+
+ // execute
+ $params = array();
+ $sth = $this->pdo->prepare($sql);
+ $result = false;
+ try {
+ // prepare parameters - we only use those that exist in the SQL
+ foreach ($arguments as $key => $value) {
+ if (is_array($value)) continue;
+ if (is_object($value)) continue;
+ if ($key[0] != ':') $key = ":$key"; // prefix with colon if needed
+ if (strpos($sql, $key) === false) continue; // skip if parameter is missing
+
+ if (is_int($value)) {
+ $sth->bindValue($key, $value, PDO::PARAM_INT);
+ } else {
+ $sth->bindValue($key, $value);
+ }
+ $params[$key] = $value; //remember for debugging
+ }
+
+ $sth->execute();
+ // only report last line's result
+ $hasnextrowset = true;
+ $currentsql = $sql;
+ while ($hasnextrowset) {
+ if (strtolower(substr($currentsql, 0, 6)) == 'select') {
+ $result = $sth->fetchAll();
+ } else {
+ $result = $sth->rowCount();
+ }
+ $semi_pos = strpos($currentsql, ';');
+ if ($semi_pos) {
+ $currentsql = trim(substr($currentsql, $semi_pos + 1));
+ }
+ try {
+ $hasnextrowset = $sth->nextRowset(); // run next rowset
+ } catch (PDOException $rowset_e) {
+ $hasnextrowset = false; // driver does not support multi-rowset, should be executed in one time
+ }
+ }
+ } catch (Exception $e) {
+ // report the caller's line
+ $trace = debug_backtrace();
+ $line = $trace[0]['line'];
+ $dsql = $this->debugSQL($sql, $params, !defined('DOKU_UNITTEST'));
+ $this->debugMsg($e, -1, $line);
+ $this->debugMsg("SQL: <pre>$dsql</pre>", -1, $line);
+ }
+ $sth->closeCursor();
+ $sth = null;
+
+ return $result;
+ }
+
+ /**
+ * Wrapper around msg() but outputs only when debug is enabled
+ *
+ * @param string|Exception $message
+ * @param int $err
+ * @param int $line
+ */
+ protected function debugMsg($message, $err = 0, $line = 0)
+ {
+ if (!$this->getConf('debug')) return;
+ if (is_a($message, 'Exception')) {
+ $err = -1;
+ $msg = $message->getMessage();
+ if (!$line) $line = $message->getLine();
+ } else {
+ $msg = $message;
+ }
+
+ if (defined('DOKU_UNITTEST')) {
+ printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
+ } else {
+ msg('authpdo: ' . $msg, $err, $line, __FILE__);
+ }
+ }
+
+ /**
+ * Check if the given config strings are set
+ *
+ * @param string[] $keys
+ * @return bool
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ *
+ */
+ protected function checkConfig($keys)
+ {
+ foreach ($keys as $key) {
+ $params = explode(':', $key);
+ $key = array_shift($params);
+ $sql = trim($this->getConf($key));
+
+ // check if sql is set
+ if (!$sql) return false;
+ // check if needed params are there
+ foreach ($params as $param) {
+ if (strpos($sql, ":$param") === false) return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * create an approximation of the SQL string with parameters replaced
+ *
+ * @param string $sql
+ * @param array $params
+ * @param bool $htmlescape Should the result be escaped for output in HTML?
+ * @return string
+ */
+ protected function debugSQL($sql, $params, $htmlescape = true)
+ {
+ foreach ($params as $key => $val) {
+ if (is_int($val)) {
+ $val = $this->pdo->quote($val, PDO::PARAM_INT);
+ } elseif (is_bool($val)) {
+ $val = $this->pdo->quote($val, PDO::PARAM_BOOL);
+ } elseif (is_null($val)) {
+ $val = 'NULL';
+ } else {
+ $val = $this->pdo->quote($val);
+ }
+ $sql = str_replace($key, $val, $sql);
+ }
+ if ($htmlescape) $sql = hsc($sql);
+ return $sql;
+ }
+}
+
+// vim:ts=4:sw=4:et:
diff --git a/platform/www/lib/plugins/authpdo/conf/default.php b/platform/www/lib/plugins/authpdo/conf/default.php
new file mode 100644
index 0000000..138ca2f
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/conf/default.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Default settings for the authpdo plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$conf['debug'] = 0;
+$conf['dsn'] = '';
+$conf['user'] = '';
+$conf['pass'] = '';
+
+/**
+ * statement to select a single user identified by its login name
+ *
+ * input: :user
+ * return: user, name, mail, (clear|hash), [uid], [*]
+ */
+$conf['select-user'] = '';
+
+/**
+ * statement to check the password in SQL, optional when above returned clear or hash
+ *
+ * input: :user, :clear, :hash, [uid], [*]
+ * return: *
+ */
+$conf['check-pass'] = '';
+
+/**
+ * statement to select a single user identified by its login name
+ *
+ * input: :user, [uid]
+ * return: group
+ */
+$conf['select-user-groups'] = '';
+
+/**
+ * Select all the existing group names
+ *
+ * return: group, [gid], [*]
+ */
+$conf['select-groups'] = '';
+
+/**
+ * Create a new user
+ *
+ * input: :user, :name, :mail, (:clear|:hash)
+ */
+$conf['insert-user'] = '';
+
+/**
+ * Remove a user
+ *
+ * input: :user, [:uid], [*]
+ */
+$conf['delete-user'] = '';
+
+/**
+ * list user names matching the given criteria
+ *
+ * Make sure the list is distinct and sorted by user name. Apply the given limit and offset
+ *
+ * input: :user, :name, :mail, :group, :start, :end, :limit
+ * out: user
+ */
+$conf['list-users'] = '';
+
+/**
+ * count user names matching the given criteria
+ *
+ * Make sure the counted list is distinct
+ *
+ * input: :user, :name, :mail, :group
+ * out: count
+ */
+$conf['count-users'] = '';
+
+/**
+ * Update user data (except password and user name)
+ *
+ * input: :user, :name, :mail, [:uid], [*]
+ */
+$conf['update-user-info'] = '';
+
+/**
+ * Update user name aka login
+ *
+ * input: :user, :newlogin, [:uid], [*]
+ */
+$conf['update-user-login'] = '';
+
+/**
+ * Update user password
+ *
+ * input: :user, :clear, :hash, [:uid], [*]
+ */
+$conf['update-user-pass'] = '';
+
+/**
+ * Create a new group
+ *
+ * input: :group
+ */
+$conf['insert-group'] = '';
+
+/**
+ * Make user join group
+ *
+ * input: :user, [:uid], group, [:gid], [*]
+ */
+$conf['join-group'] = '';
+
+/**
+ * Make user leave group
+ *
+ * input: :user, [:uid], group, [:gid], [*]
+ */
+$conf['leave-group'] = '';
diff --git a/platform/www/lib/plugins/authpdo/conf/metadata.php b/platform/www/lib/plugins/authpdo/conf/metadata.php
new file mode 100644
index 0000000..34e60a4
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/conf/metadata.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Options for the authpdo plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$meta['debug'] = array('onoff', '_caution' => 'security');
+$meta['dsn'] = array('string', '_caution' => 'danger');
+$meta['user'] = array('string', '_caution' => 'danger');
+$meta['pass'] = array('password', '_caution' => 'danger', '_code' => 'base64');
+$meta['select-user'] = array('', '_caution' => 'danger');
+$meta['check-pass'] = array('', '_caution' => 'danger');
+$meta['select-user-groups'] = array('', '_caution' => 'danger');
+$meta['select-groups'] = array('', '_caution' => 'danger');
+$meta['insert-user'] = array('', '_caution' => 'danger');
+$meta['delete-user'] = array('', '_caution' => 'danger');
+$meta['list-users'] = array('', '_caution' => 'danger');
+$meta['count-users'] = array('', '_caution' => 'danger');
+$meta['update-user-info'] = array('', '_caution' => 'danger');
+$meta['update-user-login'] = array('', '_caution' => 'danger');
+$meta['update-user-pass'] = array('', '_caution' => 'danger');
+$meta['insert-group'] = array('', '_caution' => 'danger');
+$meta['join-group'] = array('', '_caution' => 'danger');
+$meta['leave-group'] = array('', '_caution' => 'danger');
diff --git a/platform/www/lib/plugins/authpdo/lang/en/lang.php b/platform/www/lib/plugins/authpdo/lang/en/lang.php
new file mode 100644
index 0000000..3e1482e
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/lang/en/lang.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * English language file for authpdo plugin
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ *
+ */
+
+$lang['connectfail'] = 'Failed to connect to database.';
+$lang['userexists'] = 'Sorry, a user with this login already exists.';
+$lang['writefail'] = 'Unable to modify user data. Please inform the Wiki-Admin';
+
+//Setup VIM: ex: et ts=4 :
diff --git a/platform/www/lib/plugins/authpdo/lang/en/settings.php b/platform/www/lib/plugins/authpdo/lang/en/settings.php
new file mode 100644
index 0000000..1aaaec0
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/lang/en/settings.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * english language file for authpdo plugin
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+$lang['debug'] = 'Print out detailed error messages. Should be disabled after setup.';
+$lang['dsn'] = 'The DSN to connect to the database.';
+$lang['user'] = 'The user for the above database connection (empty for sqlite)';
+$lang['pass'] = 'The password for the above database connection (empty for sqlite)';
+$lang['select-user'] = 'SQL Statement to select the data of a single user';
+$lang['select-user-groups'] = 'SQL Statement to select all groups of a single user';
+$lang['select-groups'] = 'SQL Statement to select all available groups';
+$lang['insert-user'] = 'SQL Statement to insert a new user into the database';
+$lang['delete-user'] = 'SQL Statement to remove a single user from the database';
+$lang['list-users'] = 'SQL Statement to list users matching a filter';
+$lang['count-users'] = 'SQL Statement to count users matching a filter';
+$lang['update-user-info'] = 'SQL Statement to update the full name and email address of a single user';
+$lang['update-user-login'] = 'SQL Statement to update the login name of a single user';
+$lang['update-user-pass'] = 'SQL Statement to update the password of a single user';
+$lang['insert-group'] = 'SQL Statement to insert a new group into the database';
+$lang['join-group'] = 'SQL Statement to add a user to an existing group';
+$lang['leave-group'] = 'SQL Statement to remove a user from a group';
+$lang['check-pass'] = 'SQL Statement to check the password for a user. Can be left empty if password info is fetched in select-user.';
diff --git a/platform/www/lib/plugins/authpdo/plugin.info.txt b/platform/www/lib/plugins/authpdo/plugin.info.txt
new file mode 100644
index 0000000..e60ff0b
--- /dev/null
+++ b/platform/www/lib/plugins/authpdo/plugin.info.txt
@@ -0,0 +1,7 @@
+base authpdo
+author Andreas Gohr
+email andi@splitbrain.org
+date 2016-08-20
+name authpdo plugin
+desc Authenticate against a database via PDO
+url https://www.dokuwiki.org/plugin:authpdo