diff --git a/conf/mysql.conf.php.example b/conf/mysql.conf.php.example index fe0664903f0e2986926d0608bc12013fe42f4eab..66fcf5f1393754fa6c61880bbe13ef8fcd9dc565 100644 --- a/conf/mysql.conf.php.example +++ b/conf/mysql.conf.php.example @@ -28,6 +28,11 @@ $conf['auth']['mysql']['user'] = ''; $conf['auth']['mysql']['password'] = ''; $conf['auth']['mysql']['database'] = ''; +/* This option enables debug messages in the mysql module. It is + * mostly usefull for system admins. + */ +$conf['auth']['mysql']['debug'] = 0; + /* Normally password encryptionis done by DokuWiki (recommended) but for * some reasons it might be usefull to let the database do the encryption. * Set 'encryptPass' to '1' and the cleartext password is forwarded to @@ -48,7 +53,9 @@ $conf['auth']['mysql']['TablesToLock']= array("users", "users AS u","groups", "g * following patters will be replaced: * %{user} user name */ -$conf['auth']['mysql']['getUserID'] = "SELECT uid AS id FROM users WHERE login='%{user}'"; +$conf['auth']['mysql']['getUserID'] = "SELECT uid AS id + FROM users + WHERE login='%{user}'"; /* This statement should return the database index of a given group name. * The module will access the index with the name 'id' so a alias might be @@ -56,7 +63,9 @@ $conf['auth']['mysql']['getUserID'] = "SELECT uid AS id FROM users WHERE login * following patters will be replaced: * %{group} group name */ -$conf['auth']['mysql']['getGroupID'] = "SELECT gid AS id FROM groups WHERE name='%{group}'"; +$conf['auth']['mysql']['getGroupID'] = "SELECT gid AS id + FROM groups + WHERE name='%{group}'"; /* This statement is used to grant or deny access to the wiki. The result should * be a table with exact one line containing at least the password of the user. @@ -137,6 +146,24 @@ $conf['auth']['mysql']['addUser'] = "INSERT INTO users SUBSTRING_INDEX('%{name}',' ', 1), SUBSTRING_INDEX('%{name}',' ', -1))"; +/* This statements should modify a user entry in the database. The statements + * UpdateLogin, UpdatePass, UpdateEmail and UpdateName will be added to + * updateUser on demand. Only changed parameters will be used. + * Following patterns will be replaced: + * %{user} user's login name + * %{pass} password (encrypted or clear text, depends on 'encryptPass') + * %{email} email address + * %{name} user's full name + * %{uid} user id that should be updated + */ +$conf['auth']['mysql']['updateUser'] = "UPDATE users SET"; +$conf['auth']['mysql']['UpdateLogin'] = "login='%{user}'"; +$conf['auth']['mysql']['UpdatePass'] = "pass='%{pass}'"; +$conf['auth']['mysql']['UpdateEmail'] = "email='%{email}'"; +$conf['auth']['mysql']['UpdateName'] = "firstname=SUBSTRING_INDEX('%{name}',' ', 1), + lastname=SUBSTRING_INDEX('%{name}',' ', -1)"; +$conf['auth']['mysql']['UpdateTarget']= "WHERE uid=%{uid}"; + /* This statement should remove a user fom the database. * Following patterns will be replaced: * %{user} user's login name diff --git a/inc/auth/mysql.class.php b/inc/auth/mysql.class.php index 41d45e940a963980ec49ef6d2e8dd365df2b7716..a5fda82e538ad4f1186c66c02394e9b5aee594a8 100644 --- a/inc/auth/mysql.class.php +++ b/inc/auth/mysql.class.php @@ -14,6 +14,9 @@ require_once(DOKU_AUTH.'/basic.class.php'); class auth_mysql extends auth_basic { var $dbcon = 0; + var $dbver = 0; // database version + var $dbrev = 0; // database revision + var $dbsub = 0; // database subrevision var $cnf = null; var $defaultgroup = ""; @@ -27,13 +30,13 @@ class auth_mysql extends auth_basic { */ function auth_mysql() { global $conf; - global $lang; if (method_exists($this, 'auth_basic')) parent::auth_basic(); if(!function_exists('mysql_connect')) { - msg($lang['noMySQL'],-1); + if ($this->cnf['debug']) + msg("MySQL err: PHP MySQL extension not found.",-1); $this->success = false; } @@ -141,17 +144,24 @@ class auth_mysql extends auth_basic { } /** - * [public function] + * Modify user data [public function] * - * Modify user data + * An existing user dataset will be modified. Changes are given in an array. + * + * The dataset update will be rejected if the user name should be changed + * to an already existing one. * - * @param $user nick of the user to be changed - * @param $changes array of field/value pairs to be changed (password will be clear text) - * @return bool + * The password must be provides unencrypted. Pasword cryption is done + * automatically if configured. * - * @todo Modifications are done through deleting and recreating the user. - * This might be suboptimal and dangerous. Using UPDATE seems the - * better way. + * If one or more groups could't be updated, no error would be set. In + * this case the dataset might already be changed and we can't rollback + * the changes. Transactions would be really usefull here. + * + * @param $user nick of the user to be changed + * @param $changes array of field/value pairs to be changed (password + * will be clear text) + * @return bool true on success, false on error * * @author Chris Smith <chris@jalakai.co.uk> * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> @@ -161,25 +171,26 @@ class auth_mysql extends auth_basic { if (!is_array($changes) || !count($changes)) return true; // nothing to change - + if($this->_openDB()) { $this->_lockTables("WRITE"); - if (($info = $this->_getUserInfo($user)) !== false) { - $newuser = $user; - foreach ($changes as $field => $value) { - if ($field == 'user') - $newuser = $value; - if ($field == 'pass' && !$this->cnf['encryptPass']) - $value = auth_cryptPassword($value); - $info[$field] = $value; // update user record - } - $rc = $this->_delUser($user); // remove user from database - if ($rc) - $rc = $this->_addUser($newuser,$info['pass'],$info['name'],$info['mail'],$info['grps']); - if (!$rc) - msg($lang['modUserFailed'], -1); - } + if (($uid = $this->_getUserID($user))) { + $rc = $this->_updateUserInfo($changes, $uid); + + if ($rc && isset($changes['grps'])) { + $groups = $this->_getGroups($user); + $grpadd = array_diff($changes['grps'], $groups); + $grpdel = array_diff($groups, $changes['grps']); + + foreach($grpadd as $group) + $this->_addUserToGroup($uid, $group, 1); + + foreach($grpdel as $group) + $this->_delUserFromGroup($uid, $group); + } + } + $this->_unlockTables(); $this->_closeDB(); } @@ -238,9 +249,7 @@ class auth_mysql extends auth_basic { } /** - * [public function] - * - * Bulk retrieval of user data. + * Bulk retrieval of user data. [public function] * * @param start index of first user to be returned * @param limit max number of users to be returned @@ -276,22 +285,21 @@ class auth_mysql extends auth_basic { } /** - * [public function] - * - * Give user membership of a group + * Give user membership of a group [public function] * * @param $user * @param $group - * @return bool + * @return bool true on success, false on error * * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> */ function joinGroup($user, $group) { $rc = false; - if($this->_openDB()) { + if ($this->_openDB()) { $this->_lockTables("WRITE"); - $rc = _addUserToGroup($user, $group); + $uid = $this->_getUserID($user); + $rc = $this->_addUserToGroup($uid, $group); $this->_unlockTables(); $this->_closeDB(); } @@ -299,9 +307,7 @@ class auth_mysql extends auth_basic { } /** - * [public function] - * - * Remove user from a group + * Remove user from a group [public function] * * @param $user user that leaves a group * @param $group group to leave @@ -312,20 +318,10 @@ class auth_mysql extends auth_basic { function leaveGroup($user, $group) { $rc = false; - if($this->_openDB()) { + if ($this->_openDB()) { $this->_lockTables("WRITE"); - $uid = $this->_getUserID($user); - if ($uid) { - $gid = $this->_getGroupID($group); - if ($gid) { - $sql = str_replace('%{uid}', addslashes($uid),$this->cnf['delUserGroup']); - $sql = str_replace('%{user}', addslashes($user),$sql); - $sql = str_replace('%{gid}', addslashes($gid),$sql); - $sql = str_replace('%{group}',addslashes($group),$sql); - $rc = $this->_modifyDB($sql) == 0 ? true : false; - } - } + $rc = $this->_delUserFromGroup($uid, $group); $this->_unlockTables(); $this->_closeDB(); } @@ -333,53 +329,76 @@ class auth_mysql extends auth_basic { } /** - * Adds a user to a group. If $force is set to '1' the group will be - * created if it not already existed. + * Adds a user to a group. + * + * If $force is set to '1' non existing groups would be created. * * The database connection must already be established. Otherwise * this function does nothing and returns 'false'. It is strongly * recommended to call this function only after all participating * tables (group and usergroup) have been locked. * - * @param $user user to add to a group + * @param $uid user id to add to a group * @param $group name of the group * @param $force '1' create missing groups * @return bool 'true' on success, 'false' on error * * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> */ - function _addUserToGroup($user, $group, $force=0) { + function _addUserToGroup($uid, $group, $force=0) { $newgroup = 0; - if($this->dbcon) { - $uid = $this->_getUserID($user); - if ($uid) { - $gid = $this->_getGroupID($group); - if (!$gid) { - if ($force) { // create missing groups - $sql = str_replace('%{group}',addslashes($group),$this->cnf['addGroup']); - $gid = $this->_modifyDB($sql); - $newgroup = 1; // group newly created - } - if (!$gid) return false; // group didn't exist and can't be created + if (($this->dbcon) && ($uid)) { + $gid = $this->_getGroupID($group); + if (!$gid) { + if ($force) { // create missing groups + $sql = str_replace('%{group}',addslashes($group),$this->cnf['addGroup']); + $gid = $this->_modifyDB($sql); + $newgroup = 1; // group newly created } + if (!$gid) return false; // group didn't exist and can't be created + } - $sql = str_replace('%{uid}', addslashes($uid),$this->cnf['addUserGroup']); - $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; + $sql = str_replace('%{uid}', addslashes($uid),$this->cnf['addUserGroup']); + $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->cnf['delGroup']); - $sql = str_replace('%{group}',addslashes($group),$sql); - $this->_modifyDB($sql); - } + if ($newgroup) { // remove previously created group on error + $sql = str_replace('%{gid}', addslashes($gid),$this->cnf['delGroup']); + $sql = str_replace('%{group}',addslashes($group),$sql); + $this->_modifyDB($sql); } } return false; } + /** + * Remove user from a group + * + * @param $uid user id that leaves a group + * @param $group group to leave + * @return bool true on success, false on error + * + * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> + */ + function _delUserFromGroup($uid, $group) { + $rc = false; + + if (($this->dbcon) && ($uid)) { + $gid = $this->_getGroupID($group); + if ($gid) { + $sql = str_replace('%{uid}', addslashes($uid),$this->cnf['delUserGroup']); + $sql = str_replace('%{user}', addslashes($user),$sql); + $sql = str_replace('%{gid}', addslashes($gid),$sql); + $sql = str_replace('%{group}',addslashes($group),$sql); + $rc = $this->_modifyDB($sql) == 0 ? true : false; + } + } + return $rc; + } + /** * Retrieves a list of groups the user is a member off. * @@ -458,7 +477,8 @@ class auth_mysql extends auth_basic { if ($uid) { foreach($grps as $group) { - $gid = $this->_addUserToGroup($user, $group, 1); + $uid = $this->_getUserID($user); + $gid = $this->_addUserToGroup($uid, $group, 1); if ($gid === false) break; } @@ -470,9 +490,8 @@ class auth_mysql extends auth_basic { * is not a big issue so we ignore this problem here. */ $this->_delUser($user); - $text = str_replace('%{user}',addslashes($user),$this->cnf['joinGroupFailed']); - $text = str_replace('%{group}',addslashes($group),$text); - msg($text, -1); + if ($this->cnf['debug']) + msg ("MySQL err: Adding user '$user' to group '$group' failed.",-1); } } } @@ -530,6 +549,65 @@ class auth_mysql extends auth_basic { return false; } + /** + * Updates the user info in the database + * + * Update a user data structure in the database according changes + * given in an array. The user name can only be changes if it didn't + * exists already. If the new user name exists the update procedure + * will be aborted. The database keeps unchanged. + * + * The database connection has already to be established for this + * function to work. Otherwise it will return 'false'. + * + * The password will be crypted if necessary. + * + * @param $changes array of items to change as pairs of item and value + * @param $uid user id of dataset to change, must be unique in DB + * @return true on success or false on error + * + * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> + */ + function _updateUserInfo($changes, $uid) { + $sql = $this->cnf['updateUser']." "; + $cnt = 0; + $err = 0; + + if($this->dbcon) { + foreach ($changes as $item => $value) { + if ($item == 'user') { + if (($this->_getUserID($changes['user']))) { + $err = 1; /* new username already exists */ + break; /* abort update */ + } + if ($cnt++ > 0) $sql .= ", "; + $sql .= str_replace('%{user}',$value,$this->cnf['UpdateLogin']); + } else if ($item == 'name') { + if ($cnt++ > 0) $sql .= ", "; + $sql .= str_replace('%{name}',$value,$this->cnf['UpdateName']); + } else if ($item == 'pass') { + if (!$this->cnf['encryptPass']) + $value = auth_cryptPassword($value); + if ($cnt++ > 0) $sql .= ", "; + $sql .= str_replace('%{pass}',$value,$this->cnf['UpdatePass']); + } else if ($item == 'mail') { + if ($cnt++ > 0) $sql .= ", "; + $sql .= str_replace('%{email}',$value,$this->cnf['UpdateEmail']); + } + } + + if ($err == 0) { + if ($cnt > 0) { + $sql .= " ".str_replace('%{uid}', $uid, $this->cnf['UpdateTarget']); + $sql .= " LIMIT 1"; + $this->_modifyDB($sql); + } + return true; + } + } + return false; + } + /** * Retrieves the group id of a given group name * @@ -561,24 +639,25 @@ class auth_mysql extends auth_basic { * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> */ function _openDB() { - global $lang; - if (!$this->dbcon) { $con = @mysql_connect ($this->cnf['server'], $this->cnf['user'], $this->cnf['password']); if ($con) { if ((mysql_select_db($this->cnf['database'], $con))) { + if ((preg_match("/^(\d+)\.(\d+)\.(\d+).*/", mysql_get_server_info ($con), $result)) == 1) { + $this->dbver = $result[1]; + $this->dbrev = $result[2]; + $this->dbsub = $result[3]; + } $this->dbcon = $con; - return true; // connection and database sucessfully opened + return true; // connection and database successfully opened } else { - $text = str_replace('%d',addslashes($this->cnf['database']),$lang['noDatabase']); - msg($text, -1); mysql_close ($con); + if ($this->cnf['debug']) + msg("MySQL err: No access to database {$this->cnf['database']}.", -1); } - } else { - $text = str_replace('%u',addslashes($this->cnf['user']),$lang['noConnect']); - $text = str_replace('%s',addslashes($this->cnf['server']),$text); - msg($text, -1); - } + } else if ($this->cnf['debug']) + msg ("MySQL err: Connection to {$this->cnf['user']}@{$this->cnf['server']} not possible.", -1); + return false; // connection failed } return true; // connection already open @@ -617,7 +696,8 @@ class auth_mysql extends auth_basic { mysql_free_result ($result); return $resultarray; } - msg('MySQL: '.mysql_error($this->dbcon), -1); + if ($this->cnf['debug']) + msg('MySQL err: '.mysql_error($this->dbcon), -1); } return false; } @@ -640,7 +720,8 @@ class auth_mysql extends auth_basic { $rc = mysql_insert_id($this->dbcon); //give back ID on insert if ($rc !== false) return $rc; } - msg('MySQL: '.mysql_error($this->dbcon), -1); + if ($this->cnf['debug']) + msg('MySQL err: '.mysql_error($this->dbcon), -1); } return false; } @@ -741,7 +822,8 @@ class auth_mysql extends auth_basic { return $sql; } - + + } //Setup VIM: ex: et ts=2 enc=utf-8 : diff --git a/inc/lang/de/lang.php b/inc/lang/de/lang.php index 733f8caf50659a12099f29d61d23e67b402977cf..037456af1449cd7fad8375b4e28f9970bd826d75 100644 --- a/inc/lang/de/lang.php +++ b/inc/lang/de/lang.php @@ -195,11 +195,4 @@ $lang['unsubscribe_error'] = 'Das Abonnement von %s für die Seite %s konnte ni /* auth.class lanuage support */ $lang['authmodfailed'] = 'Benutzerüberprüfung nicht möglich. Bitte wenden Sie sich an den Systembetreuer.'; -/* mysql.class language support */ -$lang['noMySQL'] = "MySQL Erweiterung für PHP nicht gefunden. Bitte informieren Sie ihren Wiki Admin."; -$lang['noDatabase'] = "MySQL: Zugriff auf Datenbank '%d' fehlgeschlagen. Bitte informieren Sie ihren Wiki Admin."; -$lang['noConnect'] = "MySQL: Datenbank '%u@%s' nicht erreichbar. Bitte informieren Sie ihren Wiki Admin."; -$lang['joinGroupFailed'] = "Konto für '%u' nicht angelegt; Benutzer konnte nicht der Gruppe '%g' zugeordnet werden."; -$lang['modUserFailed'] = "Benutzerdaten können nicht geändert werden. Bitte informieren Sie ihren Wiki Admin."; - //Setup VIM: ex: et ts=2 enc=utf-8 : diff --git a/inc/lang/en/lang.php b/inc/lang/en/lang.php index 3642038dc205a6c7cb00be739b0b9a0d726e6204..a543ea46e64b519d8f76eefbe0c73ca142e67a52 100644 --- a/inc/lang/en/lang.php +++ b/inc/lang/en/lang.php @@ -193,11 +193,4 @@ $lang['unsubscribe_error'] = 'Error removing %s from subscription list for %s'; /* auth.class lanuage support */ $lang['authmodfailed'] = 'User authentification not possible. Please inform your Wiki Admin.'; -/* mysql.class language support */ -$lang['noMySQL'] = "MySQL extension for PHP not found. Please inform your Wiki Admin."; -$lang['noDatabase'] = "MySQL: Can't access Database '%d'. Please inform you Wiki Admin."; -$lang['noConnect'] = "MySQL: Can't connect to '%u@%s'. Please inform your Wiki Admin."; -$lang['joinGroupFailed'] = "Account for '%u' not created because it can't be added to group '%g'."; -$lang['modUserFailed'] = "Unable to modify user data. Please inform your Wiki Admin"; - //Setup VIM: ex: et ts=2 enc=utf-8 :