Commit 71c30a10 authored by Fritz Webering's avatar Fritz Webering
Browse files

Add group support

Switch from mysqli to PDO database connection
Switch to prepared statements to prevent injections
Move database stuff to extra class for reuse
parent b5d4c0f5
<?php
/**
* Copyright (c) 2013 Fritz Webering <fritz@webering.eu>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the LICENSE file.
*/
namespace OCA\user_wcf;
/**
* This loads group memberships from a WCF database for all groups listed
* in the $authorizedGroups paramter. The database configuration is imported
* from the WCF configuration file of the WCF installation given in the
* $wcfPath paramter.
*/
class Group_WCF extends \OC_Group_Backend {
protected $db;
public function __construct($wcfPath, $authorizedGroups) {
$this->db = lib\WCF_DB::getInstance($wcfPath);
}
/**
* @brief Check if backend implements actions
* @param int $actions bitwise-or'ed actions
* @return boolean
*
* Returns the supported actions as int to be
* compared with OC_GROUP_BACKEND_CREATE_GROUP etc.
*/
public function getSupportedActions() {
return OC_GROUP_BACKEND_GET_DISPLAYNAME;
}
/**
* @brief is user in group?
* @param string $uid uid of the user
* @param string $gid gid of the group
* @return bool
*
* Checks whether the user is member of a group or not.
*/
public function inGroup($uid, $gid) {
$inGroup = FALSE;
$result = $this->db->prepare('1', 'username=? AND groupName=?');
if ($result !== FALSE and $result->execute(array($uid, $gid))) {
if ($result->fetch() !== FALSE) {
$inGroup = TRUE;
}
$result->closeCursor();
}
return $inGroup;
}
/**
* @brief Get all groups a user belongs to
* @param string $uid Name of the user
* @return array with group names
*
* This function fetches all groups a user belongs to. It does not check
* if the user exists at all.
*/
public function getUserGroups($uid) {
$groups = array();
$result = $this->db->prepare('groupName', 'username=?');
if ($result !== FALSE and $result->execute(array($uid))) {
foreach ($result as $row) {
$groups[] = $row['groupName'];
}
$result->closeCursor();
}
return $groups;
}
/**
* @brief get a list of all groups
* @param string $search
* @param int $limit
* @param int $offset
* @return array with group names
*
* Returns a list with all groups
*/
public function getGroups($search = '', $limit = -1, $offset = 0) {
$groups = array();
$params = array();
$where = NULL;
$append = 'ORDER BY groupName';
if ($search !== '') {
$search = (string) $search;
$where = 'groupName LIKE ?';
$params[] = '%'.$search.'%';
}
if ($limit !== -1) {
$append .= ' LIMIT '.intval($limit);
}
if ($offset !== 0) {
$append .= ' OFFSET '.intval($offset);
}
$result = $this->db->prepare('groupName', $where, $append);
if ($result !== FALSE and $result->execute($params)) {
foreach ($result as $row) {
$groups[] = $row['groupName'];
}
$result->closeCursor();
}
return $groups;
}
/**
* check if a group exists
* @param string $gid
* @return bool
*/
public function groupExists($gid) {
$exists = FALSE;
$result = $this->db->prepare('groupName', 'groupName=?');
if ($result !== FALSE and $result->execute(array($gid))) {
$exists = ($result->fetch() !== FALSE);
$result->closeCursor();
}
return $exists;
}
/**
* @brief get a list of all users in a group
* @param string $gid
* @param string $search
* @param int $limit
* @param int $offset
* @return array with user ids
*/
public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
$users = array();
$params = array($gid);
$where = 'groupName=?';
$append = 'ORDER BY username';
if ($search !== '') {
$where .= ' AND username LIKE ?';
$params[] = $search;
}
if ($limit !== -1) {
$append .= ' LIMIT '.intval($limit);
}
if ($offset !== 0) {
$append .= ' OFFSET '.intval($offset);
}
$result = $this->db->prepare('username', $where, $append);
if ($result !== FALSE and $result->execute($params)) {
foreach ($result as $row) {
$users[] = $row['username'];
}
$result->closeCursor();
}
return $users;
}
}
<?php
namespace OCA\User_WCF\lib;
class WCF_DB {
public $wcfN;
protected $connected = NULL;
protected $db;
protected $dsn='', $dbUser, $dbPassword;
protected $authorizedGroups = NULL;
protected $groupsCondition = 'FALSE'; // Allow no groups by default
protected static $instances = array();
public static function getInstance($wcfPath) {
if (!array_key_exists($wcfPath, WCF_DB::$instances)) {
WCF_DB::$instances[$wcfPath] = new WCF_DB($wcfPath);
}
return WCF_DB::$instances[$wcfPath];
}
protected function __construct($wcfPath) {
require $wcfPath.'/config.inc.php';
if ($dbClass === 'MySQLDatabase') {
$this->dsn = 'mysql';
}
else {
User_WCF::warn('user_wcf', 'Unsupported database type '.
$dbClass.', only MySQLDatabase is supported at the moment.',
\OCP\Util::WARN);
$connected = FALSE;
return;
}
$this->dsn .= ":host=$dbHost;dbname=$dbName;charset=$dbCharset";
$this->wcfN = 'wcf'.WCF_N;
$this->dbUser = $dbUser;
$this->dbPassword = $dbPassword;
}
public function prepare($fields, $where=NULL, $append=NULL) {
if (!$this->connect()) return FALSE;
if ($where === NULL) $where = 'TRUE';
$query = "SELECT DISTINCT $fields FROM {$this->wcfN}_user
LEFT JOIN {$this->wcfN}_user_to_groups USING (userID)
LEFT JOIN {$this->wcfN}_group USING (groupID)
WHERE ($this->groupsCondition) AND ($where) $append";
$result = $this->db->prepare($query);
if ($result === FALSE) {
User_WCF::warn('Error '.$this->db->errorInfo().' preparing statement: '.
$this->db->errorInfo()[2].'. Query was: '.$query);
}
return $result;
}
/**
* @brief Set the groups that are allowed to access OwnCloud.
* @param $groups An array containing one or more group names or TRUE to
* allow all groups. Anything else will prevent all logins.
*
*
* Restricts the queries to return only users that are members of the
* specified groups. Other groups will never be returned by queries.
*/
public function setAuthorizedGroups ($groups) {
if (is_array($groups) and count($groups) > 0) {
$this->authorizedGroups = $groups;
$conditions = array();
foreach ($this->authorizedGroups as $group) {
$conditions[] = "groupName='$group'";
}
$this->groupsCondition = implode(' OR ', $conditions);
}
elseif ($groups === TRUE) {
$this->groupsCondition = 'TRUE';
}
else {
$this->groupsCondition = 'FALSE';
User_WCF::warn('$authorizedGroups is set to "'.$groups.
'", which means that nobody can log in.');
}
}
public function getAuthorizedGroups() {
return $this->authorizedGroups;
}
/**
* @brief Try to connect to the database, but only once. On subsequent
* only the result of the first call is returned.
*
* @return TRUE or FALSE
*/
private function connect() {
if ($this->connected === NULL) {
try {
$this->db = new \PDO($this->dsn, $this->dbUser, $this->dbPassword);
$this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->connected = TRUE;
}
catch (\PDOException $e) {
$this->warn('Unable to connect to database: '.
$this->db->connect_error);
$this->connected = FALSE;
}
}
return $this->connected;
}
}
......@@ -15,26 +15,24 @@ namespace OCA\user_wcf;
* database configuration is imported from the WCF configuration file of
* the WCF installation given in the $wcfPath paramter.
*/
class User_WCF extends \OC_User_Backend implements \OC_User_Interface {
class User_WCF extends \OC_User_Backend {
protected $authorizedGroups;
protected $groupsCondition;
protected $db = NULL;
protected $dbUser, $dbHost, $dbPassword, $dbName;
public function __construct($wcfPath, $authorizedGroups) {
require $wcfPath.'/config.inc.php';
$this->dbHost = $dbHost;
$this->dbUser = $dbUser;
$this->dbPassword = $dbPassword;
$this->dbName = $dbName;
$this->wcfN = 'wcf'.WCF_N;
$this->authorizedGroups = $authorizedGroups;
$groups = array();
foreach ($this->authorizedGroups as $group) {
$groups[] = "{$this->wcfN}_group.groupName='$group'";
public function __construct($wcfPath, $authorizedGroups, $useGroupBackend=TRUE) {
$this->db = lib\WCF_DB::getInstance($wcfPath);
$this->db->setAuthorizedGroups($authorizedGroups);
if ($useGroupBackend) {
$groupBackend = new Group_WCF($wcfPath, $authorizedGroups);
\OC_Group::useBackend($groupBackend);
}
$this->groupsCondition = implode(' OR ', $groups);
}
public function getSupportedActions() {
return OC_USER_BACKEND_CHECK_PASSWORD | OC_USER_BACKEND_GET_DISPLAYNAME;
}
/**
......@@ -47,112 +45,86 @@ class User_WCF extends \OC_User_Backend implements \OC_User_Interface {
* returns the user id or false
*/
public function checkPassword($uid, $password) {
if (!$this->connect()) return FALSE;
$authenticatedAs = FALSE;
$username = $this->db->real_escape_string($uid);
$where = "LOWER({$this->wcfN}_user.username)=LOWER('$username')";
$result = $this->queryDb($where, 'password, salt');
$where = 'LOWER(username)=LOWER(?)';
$result = $this->db->prepare('username, password, salt', $where);
if ($result) {
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
if ($result !== FALSE and $result->execute(array($uid))) {
if (($row = $result->fetch()) !== FALSE) {
$doubleSalted = lib\StringUtil::getDoubleSaltedHash(
$password, $row['salt']);
if ($doubleSalted === $row['password']) {
$authenticatedAs = $row['username'];
$this->warn('User "'.$authenticatedAs.
$this->info('User "'.$authenticatedAs.
'" logged in successfully.');
}
else {
$this->info('Invalid password for user '.$uid);
}
}
else {
$this->debug("Username $username not found in WCF ".
"database in authorized groups.");
$this->info('User '.$uid.' is not in any authorized group.');
}
$result->close();
$result->closeCursor();
}
else {
$this->info('Error while checking password for user '.$uid);
}
return $authenticatedAs;
}
public function userExists($uid) {
if (!$this->connect()) return FALSE;
$username = $this->db->real_escape_string($uid);
$where = "LOWER({$this->wcfN}_user.username)=LOWER('$username')";
$result = $this->queryDb($where, 'password, salt');
$exists = FALSE;
if ($result) {
if ($result->num_rows > 0) {
$exists = TRUE;
}
$result->close();
$result = $this->db->prepare('1', 'username=?');
if ($result !== FALSE and $result->execute(array($uid))) {
$exists = ($result->fetch() !== FALSE);
$result->closeCursor();
}
return $exists;
}
public function getUsers($search = '', $limit = null, $offset = null) {
if (!$this->connect()) return array();
$where = TRUE;
public function getUsers($search='', $limit=null, $offset=null) {
$users = array();
$params = array();
$where = NULL;
$append = 'ORDER BY username';
if ($search !== '') {
$search = $this->db->real_escape_string($search);
$where = "username LIKE '$search'";
$where = 'username LIKE ?';
$params[] = '%'.$search.'%';
}
$append = ' ORDER BY username';
if ($limit !== NULL) $append = $append.' LIMIT '.$limit;
if ($offset !== NULL) $append = $append.' OFFSET '.$offset;
$result = $this->queryDb($where, '', $append);
if (!$result) {
$this->warn("Found no users in the database.");
return array();
if ($limit !== NULL) {
$append .= ' LIMIT '.intval($limit);
}
$this->warn("Found {$result->num_rows} users in the database.");
$users = array();
while ($row = $result->fetch_assoc()) {
$users[] = $row['username'];
if ($offset !== NULL) {
$append .= ' OFFSET '.intval($offset);;
}
return $users;
}
protected function queryDb($where='TRUE', $addFields='', $append='') {
if (!$this->connect()) return FALSE;
if ($addFields !== '') $addFields = ', '.$addFields;
$query = "SELECT username, groupName $addFields
FROM {$this->wcfN}_user
LEFT JOIN {$this->wcfN}_user_to_groups USING (userID)
LEFT JOIN {$this->wcfN}_group USING (groupID)
WHERE ($this->groupsCondition) AND ($where) $append";
$result = $this->db->query($query);
if ($result === FALSE) {
$this->warn("Error querying data from WCF database: ".
"{$this->db->error} ({$this->db->errno}). Query was: $query");
$result = $this->db->prepare('username', $where, $append);
if ($result !== FALSE and $result->execute($params)) {
$i = 0;
foreach ($result as $row) {
$users[] = $row['username'];
}
$result->closeCursor();
}
return $result;
return $users;
}
private function connect() {
if ($this->db === NULL) {
$this->db = new \mysqli($this->dbHost, $this->dbUser,
$this->dbPassword, $this->dbName);
if ($this->db->connect_error) {
$this->warn('Unable to connect to database: '.
$this->db->connect_error);
$this->db = FALSE;
}
}
return $this->db;
public static function info($text) {
\OCP\Util::writeLog('user_wcf', $text, \OCP\Util::INFO);
}
private static function warn($text) {
public static function warn($text) {
\OCP\Util::writeLog('user_wcf', $text, \OCP\Util::WARN);
}
private static function debug($text) {
public static function debug($text) {
\OCP\Util::writeLog('user_wcf', $text, \OCP\Util::DEBUG);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment