summaryrefslogtreecommitdiff
path: root/lib/plugins/authpgsql/auth.php
diff options
context:
space:
mode:
authorAndreas Gohr <andi@splitbrain.org>2012-11-09 17:11:10 +0100
committerAndreas Gohr <andi@splitbrain.org>2012-11-09 17:11:10 +0100
commit4476793e4f72ca2d73ff618d3eb009e8b430f7f2 (patch)
treeb8f026f46293319ec86741b3537b3cfd6a5d83c2 /lib/plugins/authpgsql/auth.php
parent2657e46860a359adae6f3bf3bbf8d7fcaf626f31 (diff)
downloadrpg-4476793e4f72ca2d73ff618d3eb009e8b430f7f2.tar.gz
rpg-4476793e4f72ca2d73ff618d3eb009e8b430f7f2.tar.bz2
authpgsql fixes
* fixes merge messup (code was made up from parts of authad) * code and PHP5 cleanup
Diffstat (limited to 'lib/plugins/authpgsql/auth.php')
-rw-r--r--lib/plugins/authpgsql/auth.php561
1 files changed, 323 insertions, 238 deletions
diff --git a/lib/plugins/authpgsql/auth.php b/lib/plugins/authpgsql/auth.php
index 46154b6ca..3f8ff3249 100644
--- a/lib/plugins/authpgsql/auth.php
+++ b/lib/plugins/authpgsql/auth.php
@@ -1,15 +1,9 @@
<?php
-/**
- * Plugin auth provider
- *
- * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
- * @author Jan Schumann <js@schumann-it.com>
- */
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
/**
- * PgSQL authentication backend
+ * PostgreSQL authentication backend
*
* This class inherits much functionality from the MySQL class
* and just reimplements the Postgres specific parts.
@@ -19,315 +13,406 @@ if(!defined('DOKU_INC')) die();
* @author Chris Smith <chris@jalakai.co.uk>
* @author Matthias Grimm <matthias.grimmm@sourceforge.net>
* @author Jan Schumann <js@schumann-it.com>
-*/
-class auth_plugin_authpgsql extends auth_plugin_authmysql
-{
- var $cnf = null;
- var $opts = null;
- var $adldap = null;
- var $users = null;
+ */
+class auth_plugin_authpgsql extends auth_plugin_authmysql {
/**
* Constructor
+ *
+ * checks if the pgsql interface is available, otherwise it will
+ * set the variable $success of the basis class to false
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ * @author Andreas Gohr <andi@splitbrain.org>
*/
- function auth_plugin_authpgsql() {
- parent::__construct();
-
- global $conf;
- $this->cnf = $conf['auth']['ad'];
+ public function __construct() {
+ // we don't want the stuff the MySQL constructor does, but the grandparent might do something
+ DokuWiki_Auth_Plugin::__construct();
-
- // additional information fields
- if (isset($this->cnf['additional'])) {
- $this->cnf['additional'] = str_replace(' ', '', $this->cnf['additional']);
- $this->cnf['additional'] = explode(',', $this->cnf['additional']);
- } else $this->cnf['additional'] = array();
-
- // ldap extension is needed
- if (!function_exists('ldap_connect')) {
- if ($this->cnf['debug'])
- msg("AD Auth: PHP LDAP extension not found.",-1);
+ if(!function_exists('pg_connect')) {
+ $this->_debug("PgSQL err: PHP Postgres extension not found.", -1, __LINE__, __FILE__);
$this->success = false;
return;
}
- // Prepare SSO
- if($_SERVER['REMOTE_USER'] && $this->cnf['sso']){
- // remove possible NTLM domain
- list($dom,$usr) = explode('\\',$_SERVER['REMOTE_USER'],2);
- if(!$usr) $usr = $dom;
-
- // remove possible Kerberos domain
- list($usr,$dom) = explode('@',$usr);
+ $this->loadConfig();
- $dom = strtolower($dom);
- $_SERVER['REMOTE_USER'] = $usr;
-
- // we need to simulate a login
- if(empty($_COOKIE[DOKU_COOKIE])){
- $_REQUEST['u'] = $_SERVER['REMOTE_USER'];
- $_REQUEST['p'] = 'sso_only';
- }
+ // set capabilities based upon config strings set
+ if(empty($this->conf['user']) ||
+ empty($this->conf['password']) || empty($this->conf['database'])
+ ) {
+ $this->_debug("PgSQL err: insufficient configuration.", -1, __LINE__, __FILE__);
+ $this->success = false;
+ return;
}
- // prepare adLDAP standard configuration
- $this->opts = $this->cnf;
+ $this->cando['addUser'] = $this->_chkcnf(
+ array(
+ 'getUserInfo',
+ 'getGroups',
+ 'addUser',
+ 'getUserID',
+ 'getGroupID',
+ 'addGroup',
+ 'addUserGroup'
+ )
+ );
+ $this->cando['delUser'] = $this->_chkcnf(
+ array(
+ 'getUserID',
+ 'delUser',
+ 'delUserRefs'
+ )
+ );
+ $this->cando['modLogin'] = $this->_chkcnf(
+ array(
+ 'getUserID',
+ 'updateUser',
+ 'UpdateTarget'
+ )
+ );
+ $this->cando['modPass'] = $this->cando['modLogin'];
+ $this->cando['modName'] = $this->cando['modLogin'];
+ $this->cando['modMail'] = $this->cando['modLogin'];
+ $this->cando['modGroups'] = $this->_chkcnf(
+ array(
+ 'getUserID',
+ 'getGroups',
+ 'getGroupID',
+ 'addGroup',
+ 'addUserGroup',
+ 'delGroup',
+ 'getGroupID',
+ 'delUserGroup'
+ )
+ );
+ /* getGroups is not yet supported
+ $this->cando['getGroups'] = $this->_chkcnf(array('getGroups',
+ 'getGroupID')); */
+ $this->cando['getUsers'] = $this->_chkcnf(
+ array(
+ 'getUsers',
+ 'getUserInfo',
+ 'getGroups'
+ )
+ );
+ $this->cando['getUserCount'] = $this->_chkcnf(array('getUsers'));
+ }
- // add possible domain specific configuration
- if($dom && is_array($this->cnf[$dom])) foreach($this->cnf[$dom] as $key => $val){
- $this->opts[$key] = $val;
+ /**
+ * Check if the given config strings are set
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ *
+ * @param array $keys
+ * @param bool $wop
+ * @return bool
+ */
+ protected function _chkcnf($keys, $wop = false) {
+ foreach($keys as $key) {
+ if(empty($this->conf[$key])) return false;
}
+ return true;
+ }
- // handle multiple AD servers
- $this->opts['domain_controllers'] = explode(',',$this->opts['domain_controllers']);
- $this->opts['domain_controllers'] = array_map('trim',$this->opts['domain_controllers']);
- $this->opts['domain_controllers'] = array_filter($this->opts['domain_controllers']);
+ /**
+ * Counts users which meet certain $filter criteria.
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ *
+ * @param array $filter filter criteria in item/pattern pairs
+ * @return int count of found users.
+ */
+ public function getUserCount($filter = array()) {
+ $rc = 0;
- // we can change the password if SSL is set
- if($this->opts['use_ssl'] || $this->opts['use_tls']){
- $this->cando['modPass'] = true;
+ if($this->_openDB()) {
+ $sql = $this->_createSQLFilter($this->conf['getUsers'], $filter);
+
+ // no equivalent of SQL_CALC_FOUND_ROWS in pgsql?
+ if(($result = $this->_queryDB($sql))) {
+ $rc = count($result);
+ }
+ $this->_closeDB();
}
- $this->cando['modName'] = true;
- $this->cando['modMail'] = true;
+ return $rc;
}
/**
- * Check user+password [required auth function]
+ * Bulk retrieval of user data
*
- * Checks if the given user exists and the given
- * plaintext password is correct by trying to bind
- * to the LDAP server
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
*
- * @author James Van Lommel <james@nosq.com>
- * @return bool
+ * @param int $first 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
+ * @return array userinfo (refer getUserData for internal userinfo details)
*/
- function checkPass($user, $pass){
- if($_SERVER['REMOTE_USER'] &&
- $_SERVER['REMOTE_USER'] == $user &&
- $this->cnf['sso']) return true;
+ public function retrieveUsers($first = 0, $limit = 10, $filter = array()) {
+ $out = array();
+
+ if($this->_openDB()) {
+ $this->_lockTables("READ");
+ $sql = $this->_createSQLFilter($this->conf['getUsers'], $filter);
+ $sql .= " ".$this->conf['SortOrder']." LIMIT $limit OFFSET $first";
+ $result = $this->_queryDB($sql);
+
+ foreach($result as $user)
+ if(($info = $this->_getUserInfo($user['user'])))
+ $out[$user['user']] = $info;
- if(!$this->_init()) return false;
- return $this->adldap->authenticate($user, $pass);
+ $this->_unlockTables();
+ $this->_closeDB();
+ }
+ return $out;
}
+ // @inherit function joinGroup($user, $group)
+ // @inherit function leaveGroup($user, $group) {
+
/**
- * Return user info [required auth function]
+ * Adds a user to a group.
*
- * Returns info about the given user needs to contain
- * at least these fields:
+ * If $force is set to true non existing groups would be created.
*
- * name string full name of the user
- * mail string email address of the user
- * grps array list of groups the user is in
+ * The database connection must already be established. Otherwise
+ * this function does nothing and returns 'false'.
*
- * This LDAP specific function returns the following
- * addional fields:
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ * @author Andreas Gohr <andi@splitbrain.org>
*
- * dn string distinguished name (DN)
- * uid string Posix User ID
- *
- * @author James Van Lommel <james@nosq.com>
+ * @param string $user user to add to a group
+ * @param string $group name of the group
+ * @param bool $force create missing groups
+ * @return bool true on success, false on error
*/
- function getUserData($user){
- global $conf;
- if(!$this->_init()) return false;
-
- $fields = array('mail','displayname','samaccountname');
-
- // add additional fields to read
- $fields = array_merge($fields, $this->cnf['additional']);
- $fields = array_unique($fields);
-
- //get info for given user
- $result = $this->adldap->user_info($user, $fields);
- //general user info
- $info['name'] = $result[0]['displayname'][0];
- $info['mail'] = $result[0]['mail'][0];
- $info['uid'] = $result[0]['samaccountname'][0];
- $info['dn'] = $result[0]['dn'];
-
- // additional information
- foreach ($this->cnf['additional'] as $field) {
- if (isset($result[0][strtolower($field)])) {
- $info[$field] = $result[0][strtolower($field)][0];
+ protected function _addUserToGroup($user, $group, $force = false) {
+ $newgroup = 0;
+
+ if(($this->dbcon) && ($user)) {
+ $gid = $this->_getGroupID($group);
+ if(!$gid) {
+ if($force) { // create missing groups
+ $sql = str_replace('%{group}', addslashes($group), $this->conf['addGroup']);
+ $this->_modifyDB($sql);
+ //group should now exists try again to fetch it
+ $gid = $this->_getGroupID($group);
+ $newgroup = 1; // group newly created
+ }
}
- }
-
- // handle ActiveDirectory memberOf
- $info['grps'] = $this->adldap->user_groups($user,(bool) $this->opts['recursive_groups']);
+ if(!$gid) return false; // group didn't exist and can't be created
- if (is_array($info['grps'])) {
- foreach ($info['grps'] as $ndx => $group) {
- $info['grps'][$ndx] = $this->cleanGroup($group);
+ $sql = $this->conf['addUserGroup'];
+ if(strpos($sql, '%{uid}') !== false) {
+ $uid = $this->_getUserID($user);
+ $sql = str_replace('%{uid}', addslashes($uid), $sql);
+ }
+ $sql = str_replace('%{user}', addslashes($user), $sql);
+ $sql = str_replace('%{gid}', addslashes($gid), $sql);
+ $sql = str_replace('%{group}', addslashes($group), $sql);
+ if($this->_modifyDB($sql) !== false) return true;
+
+ if($newgroup) { // remove previously created group on error
+ $sql = str_replace('%{gid}', addslashes($gid), $this->conf['delGroup']);
+ $sql = str_replace('%{group}', addslashes($group), $sql);
+ $this->_modifyDB($sql);
}
}
-
- // always add the default group to the list of groups
- if(!is_array($info['grps']) || !in_array($conf['defaultgroup'],$info['grps'])){
- $info['grps'][] = $conf['defaultgroup'];
- }
-
- return $info;
+ return false;
}
+ // @inherit function _delUserFromGroup($user $group)
+ // @inherit function _getGroups($user)
+ // @inherit function _getUserID($user)
+
/**
- * Make AD group names usable by DokuWiki.
+ * Adds a new User to the database.
*
- * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
+ * The database connection must already be established
+ * for this function to work. Otherwise it will return
+ * 'false'.
*
- * @author James Van Lommel (jamesvl@gmail.com)
+ * @param string $user login of the user
+ * @param string $pwd encrypted password
+ * @param string $name full name of the user
+ * @param string $mail email address
+ * @param array $grps array of groups the user should become member of
+ * @return bool
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
*/
- function cleanGroup($name) {
- $sName = str_replace('\\', '', $name);
- $sName = str_replace('#', '', $sName);
- $sName = preg_replace('[\s]', '_', $sName);
- return $sName;
+ protected function _addUser($user, $pwd, $name, $mail, $grps) {
+ if($this->dbcon && is_array($grps)) {
+ $sql = str_replace('%{user}', addslashes($user), $this->conf['addUser']);
+ $sql = str_replace('%{pass}', addslashes($pwd), $sql);
+ $sql = str_replace('%{name}', addslashes($name), $sql);
+ $sql = str_replace('%{email}', addslashes($mail), $sql);
+ if($this->_modifyDB($sql)) {
+ $uid = $this->_getUserID($user);
+ } else {
+ return false;
+ }
+
+ $group = '';
+ $gid = false;
+
+ if($uid) {
+ foreach($grps as $group) {
+ $gid = $this->_addUserToGroup($user, $group, 1);
+ if($gid === false) break;
+ }
+
+ if($gid !== false){
+ return true;
+ } else {
+ /* remove the new user and all group relations if a group can't
+ * be assigned. Newly created groups will remain in the database
+ * and won't be removed. This might create orphaned groups but
+ * is not a big issue so we ignore this problem here.
+ */
+ $this->_delUser($user);
+ $this->_debug("PgSQL err: Adding user '$user' to group '$group' failed.", -1, __LINE__, __FILE__);
+ }
+ }
+ }
+ return false;
}
/**
- * Sanitize user names
+ * Opens a connection to a database and saves the handle for further
+ * usage in the object. The successful call to this functions is
+ * essential for most functions in this object.
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ *
+ * @return bool
*/
- function cleanUser($name) {
- return $this->cleanGroup($name);
+ protected function _openDB() {
+ if(!$this->dbcon) {
+ $dsn = $this->conf['server'] ? 'host='.$this->conf['server'] : '';
+ $dsn .= ' port='.$this->conf['port'];
+ $dsn .= ' dbname='.$this->conf['database'];
+ $dsn .= ' user='.$this->conf['user'];
+ $dsn .= ' password='.$this->conf['password'];
+
+ $con = @pg_connect($dsn);
+ if($con) {
+ $this->dbcon = $con;
+ return true; // connection and database successfully opened
+ } else {
+ $this->_debug(
+ "PgSQL err: Connection to {$this->conf['user']}@{$this->conf['server']} not possible.",
+ -1, __LINE__, __FILE__
+ );
+ }
+ return false; // connection failed
+ }
+ return true; // connection already open
}
/**
- * Most values in LDAP are case-insensitive
+ * Closes a database connection.
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
*/
- function isCaseSensitive(){
- return false;
+ protected function _closeDB() {
+ if($this->dbcon) {
+ pg_close($this->dbcon);
+ $this->dbcon = 0;
+ }
}
/**
- * Bulk retrieval of user data
+ * Sends a SQL query to the database and transforms the result into
+ * an associative array.
*
- * @author Dominik Eckelmann <dokuwiki@cosmocode.de>
- * @param start index of first user to be returned
- * @param limit max number of users to be returned
- * @param filter array of field/pattern pairs, null for no filter
- * @return array of userinfo (refer getUserData for internal userinfo details)
+ * This function is only able to handle queries that returns a
+ * table such as SELECT.
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ *
+ * @param string $query SQL string that contains the query
+ * @return array the result table
*/
- function retrieveUsers($start=0,$limit=-1,$filter=array()) {
- if(!$this->_init()) return false;
-
- if ($this->users === null) {
- //get info for given user
- $result = $this->adldap->all_users();
- if (!$result) return array();
- $this->users = array_fill_keys($result, false);
- }
-
- $i = 0;
- $count = 0;
- $this->_constructPattern($filter);
- $result = array();
-
- foreach ($this->users as $user => &$info) {
- if ($i++ < $start) {
- continue;
- }
- if ($info === false) {
- $info = $this->getUserData($user);
- }
- if ($this->_filter($user, $info)) {
- $result[$user] = $info;
- if (($limit >= 0) && (++$count >= $limit)) break;
+ protected function _queryDB($query) {
+ $resultarray = array();
+ if($this->dbcon) {
+ $result = @pg_query($this->dbcon, $query);
+ if($result) {
+ while(($t = pg_fetch_assoc($result)) !== false)
+ $resultarray[] = $t;
+ pg_free_result($result);
+ return $resultarray;
+ } else{
+ $this->_debug('PgSQL err: '.pg_last_error($this->dbcon), -1, __LINE__, __FILE__);
}
}
- return $result;
+ return false;
}
/**
- * Modify user data
+ * Executes an update or insert query. This differs from the
+ * MySQL one because it does NOT return the last insertID
*
- * @param $user nick of the user to be changed
- * @param $changes array of field/value pairs to be changed
- * @return bool
- */
- function modifyUser($user, $changes) {
- $return = true;
-
- // password changing
- if(isset($changes['pass'])){
- try {
- $return = $this->adldap->user_password($user,$changes['pass']);
- } catch (adLDAPException $e) {
- if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
- $return = false;
- }
- if(!$return) msg('AD Auth: failed to change the password. Maybe the password policy was not met?',-1);
- }
-
- // changing user data
- $adchanges = array();
- if(isset($changes['name'])){
- // get first and last name
- $parts = explode(' ',$changes['name']);
- $adchanges['surname'] = array_pop($parts);
- $adchanges['firstname'] = join(' ',$parts);
- $adchanges['display_name'] = $changes['name'];
- }
- if(isset($changes['mail'])){
- $adchanges['email'] = $changes['mail'];
- }
- if(count($adchanges)){
- try {
- $return = $return & $this->adldap->user_modify($user,$adchanges);
- } catch (adLDAPException $e) {
- if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
- $return = false;
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ protected function _modifyDB($query) {
+ if($this->dbcon) {
+ $result = @pg_query($this->dbcon, $query);
+ if($result) {
+ pg_free_result($result);
+ return true;
}
+ $this->_debug('PgSQL err: '.pg_last_error($this->dbcon), -1, __LINE__, __FILE__);
}
-
- return $return;
+ return false;
}
/**
- * Initialize the AdLDAP library and connect to the server
+ * Start a transaction
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ *
+ * @param string $mode could be 'READ' or 'WRITE'
+ * @return bool
*/
- function _init(){
- if(!is_null($this->adldap)) return true;
-
- // connect
- try {
- $this->adldap = new adLDAP($this->opts);
- if (isset($this->opts['ad_username']) && isset($this->opts['ad_password'])) {
- $this->canDo['getUsers'] = true;
- }
+ protected function _lockTables($mode) {
+ if($this->dbcon) {
+ $this->_modifyDB('BEGIN');
return true;
- } catch (adLDAPException $e) {
- if ($this->cnf['debug']) {
- msg('AD Auth: '.$e->getMessage(), -1);
- }
- $this->success = false;
- $this->adldap = null;
}
return false;
}
/**
- * return 1 if $user + $info match $filter criteria, 0 otherwise
+ * Commit a transaction
*
- * @author Chris Smith <chris@jalakai.co.uk>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
*/
- function _filter($user, $info) {
- foreach ($this->_pattern as $item => $pattern) {
- if ($item == 'user') {
- if (!preg_match($pattern, $user)) return 0;
- } else if ($item == 'grps') {
- if (!count(preg_grep($pattern, $info['grps']))) return 0;
- } else {
- if (!preg_match($pattern, $info[$item])) return 0;
- }
+ protected function _unlockTables() {
+ if($this->dbcon) {
+ $this->_modifyDB('COMMIT');
+ return true;
}
- return 1;
+ return false;
}
- function _constructPattern($filter) {
- $this->_pattern = array();
- foreach ($filter as $item => $pattern) {
-// $this->_pattern[$item] = '/'.preg_quote($pattern,"/").'/i'; // don't allow regex characters
- $this->_pattern[$item] = '/'.str_replace('/','\/',$pattern).'/i'; // allow regex characters
+ /**
+ * Escape a string for insertion into the database
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $string The string to escape
+ * @param bool $like Escape wildcard chars as well?
+ * @return string
+ */
+ protected function _escape($string, $like = false) {
+ $string = pg_escape_string($string);
+ if($like) {
+ $string = addcslashes($string, '%_');
}
+ return $string;
}
} \ No newline at end of file