* ----------- Authors of the PgSQL and MySQL originals ------------ * @author Andreas Gohr * @author Chris Smith * @author Matthias Grimm * @author Jan Schumann */ class auth_plugin_authsqlite extends auth_plugin_authmysql { /** * Constructor * * checks if the sqlite interface is available, otherwise it will * set the variable $success of the basis class to false * * @author Clay Dowling * @author Matthias Grimm * @author Andreas Gohr */ public function __construct() { // we don't want the stuff the MySQL constructor does, but the grandparent might do something DokuWiki_Auth_Plugin::__construct(); $this->loadConfig(); // set capabilities based upon config strings set if(empty($this->conf['database'])) { echo "Insufficient Config!"; $this->_debug("SQLite err: insufficient configuration.", -1, __LINE__, __FILE__); $this->success = false; return; } $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')); $this->success = true; } /** * Check if the given config strings are set * * @author Matthias Grimm * * @param string[] $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; } /** * Counts users which meet certain $filter criteria. * * @author Matthias Grimm * * @param array $filter filter criteria in item/pattern pairs * @return int count of found users. */ public function getUserCount($filter = array()) { $rc = 0; 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(); } return $rc; } /** * Bulk retrieval of user data * * @author Matthias Grimm * * @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) */ public function retrieveUsers($first = 0, $limit = 0, $filter = array()) { $out = array(); if($this->_openDB()) { $this->_lockTables("READ"); $sql = $this->_createSQLFilter($this->conf['getUsers'], $filter); $sql .= " ".$this->conf['SortOrder']; if($limit) $sql .= " LIMIT $limit"; if($first) $sql .= " OFFSET $first"; $result = $this->_queryDB($sql); foreach($result as $user) { if(($info = $this->_getUserInfo($user['user']))) { $out[$user['user']] = $info; } } $this->_unlockTables(); $this->_closeDB(); } return $out; } // @inherit function joinGroup($user, $group) // @inherit function leaveGroup($user, $group) { /** * Adds a user to a group. * * If $force is set to true non existing groups would be created. * * The database connection must already be established. Otherwise * this function does nothing and returns 'false'. * * @author Matthias Grimm * @author Andreas Gohr * * @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 */ 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 } } if(!$gid) return false; // group didn't exist and can't be created $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) { $this->_flushUserInfoCache($user); 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); } } return false; } // @inherit function _delUserFromGroup($user $group) // @inherit function _getGroups($user) // @inherit function _getUserID($user) /** * Adds a new User to the database. * * The database connection must already be established * for this function to work. Otherwise it will return * 'false'. * * @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 * @author Chris Smith * @author Matthias Grimm */ 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, true); if($gid === false) break; } if($gid !== false){ $this->_flushUserInfoCache($user); 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; } /** * 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 Clay Dowling * @author Matthias Grimm * * @return bool */ protected function _openDB() { if(!$this->dbcon) { $errormsg = ''; $con = new SQLite3($this->conf['database']); if($con) { $this->dbcon = $con; return true; // connection and database successfully opened } else { $this->_debug($errormsg); } return false; // connection failed } return true; // connection already open } /** * Closes a database connection. * * @author Clay Dowling * @author Matthias Grimm */ protected function _closeDB() { if($this->dbcon) { $this->dbcon->close(); $this->dbcon = 0; } } /** * Substitue any %{animal} parameters in the SQL, so that users * and groups can be animal specific in a farm configuration */ protected function _substituteAnimal($query) { if (isset($GLOBALS['FARMCORE'])) { $query = str_replace('%{animal}', addslashes($GLOBALS['FARMCORE']->getAnimal()), $query); } else if(defined('DOKU_FARM') && strpos($query, '%{animal}') !== false) { $parts = split('/', DOKU_CONF); $animal = ''; $len = count($parts); for ($i=$len - 1; $i > 0; $i--) { if ($parts[$i] == 'conf' && $i > 0) { $animal = $parts[$i - 1]; } } $query = str_replace('%{animal}', addslashes($animal), $query); } return $query; } /** * Sends a SQL query to the database and transforms the result into * an associative array. * * This function is only able to handle queries that returns a * table such as SELECT. * * @author Matthias Grimm * * @param string $query SQL string that contains the query * @return array|false the result table */ protected function _queryDB($query) { $resultarray = array(); if($this->dbcon) { $query = $this->_substituteAnimal($query); $result = $this->dbcon->query($query); if($result) { while(($t = $result->fetchArray(SQLITE3_ASSOC)) !== false) { $resultarray[] = $t; } return $resultarray; } else{ $this->_debug('SQLite err: '. $this->dbcon->lastErrorMsg(), -1, __LINE__, __FILE__); } } return false; } /** * Executes an update or insert query. This differs from the * MySQL one because it does NOT return the last insertID * * @author Clay Dowling * @author Andreas Gohr * * @param string $query * @return bool */ protected function _modifyDB($query) { if($this->dbcon) { $query = $this->_substituteAnimal($query); $result = $this->dbcon->exec($query); if($result) { return true; } $this->_debug('SQLite err: '. $this->dbcon->lastErrorMsg(), -1, __LINE__, __FILE__); } return false; } /** * Start a transaction * * @author Matthias Grimm * * @param string $mode could be 'READ' or 'WRITE' * @return bool */ protected function _lockTables($mode) { if($this->dbcon) { $this->_modifyDB('BEGIN'); return true; } return false; } /** * Commit a transaction * * @author Matthias Grimm * * @return bool */ protected function _unlockTables() { if($this->dbcon) { $this->_modifyDB('COMMIT'); return true; } return false; } /** * Escape a string for insertion into the database * * @author Andreas Gohr * * @param string $string The string to escape * @param bool $like Escape wildcard chars as well? * @return string */ protected function _escape($string, $like = false) { $string = $this->dbcon->escapeString($string); if($like) { $string = addcslashes($string, '%_'); } return $string; } }