1
0
mirror of https://github.com/moparisthebest/user_sql synced 2024-11-21 16:55:02 -05:00

Support autocompletion for tables and columns

Update documentation
Switch to ownCloud's DBAL (not fully tested, yet)
Bump to v2.0
This commit is contained in:
Andreas Boehler 2015-07-30 11:22:50 +02:00
parent bb96639166
commit 3a3934392a
6 changed files with 428 additions and 40 deletions

View File

@ -1,5 +1,39 @@
<?php
/**
* ownCloud - user_sql
*
* @author Andreas Böhler and contributors
* @copyright 2012-2015 Andreas Böhler <dev (at) aboehler (dot) at>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* This is the AJAX portion of the settings page.
*
* It can:
* - Verify the connection settings
* - Load autocomplete values for tables
* - Load autocomplete values for columns
* - Save settings for a given domain
* - Load settings for a given domain
*
* It always returns JSON encoded responses
*/
namespace OCA\user_sql;
// Init owncloud
@ -17,15 +51,19 @@ $helper = new \OCA\user_sql\lib\Helper;
$l = \OC::$server->getL10N('user_sql');
$params = $helper -> getParameterArray();
$response = new \OCP\AppFramework\Http\JSONResponse();
// Check if the request is for us
if(isset($_POST['appname']) && ($_POST['appname'] === 'user_sql') && isset($_POST['function']) && isset($_POST['domain']))
{
$domain = $_POST['domain'];
switch($_POST['function'])
{
// Save the settings for the given domain to the database
case 'saveSettings':
foreach($params as $param)
{
// Special handling for checkbox fields
if(isset($_POST[$param]))
{
if($param === 'set_strip_domain')
@ -60,24 +98,132 @@ if(isset($_POST['appname']) && ($_POST['appname'] === 'user_sql') && isset($_POS
}
}
}
$response->setData(array('status' => 'success',
'data' => array('message' => $l -> t('Application settings successfully stored.'))));
break;
// Load the settings for a given domain
case 'loadSettingsForDomain':
$retArr = array();
foreach($params as $param)
{
$retArr[$param] = \OC::$server->getConfig()->getAppValue('user_sql', $param.'_'.$domain, '');
}
\OCP\JSON::success(array('settings' => $retArr));
return true;
$response->setData(array('status' => 'success',
'settings' => $retArr));
break;
// Try to verify the database connection settings
case 'verifySettings':
$cm = new \OC\DB\ConnectionFactory();
if(!isset($_POST['sql_driver']))
{
$response->setData(array('status' => 'error',
'data' => array('message' => $l -> t('Error connecting to database: No driver specified.'))));
break;
}
if(($_POST['sql_hostname'] === '') || ($_POST['sql_database'] === ''))
{
$response->setData(array('status' => 'error',
'data' => array('message' => $l -> t('Error connecting to database: You must specify at least host and database'))));
break;
}
$parameters = array('host' => $_POST['sql_hostname'],
'password' => $_POST['sql_password'],
'user' => $_POST['sql_username'],
'dbname' => $_POST['sql_database'],
'tablePrefix' => ''
);
try {
$conn = $cm -> getConnection($_POST['sql_driver'], $parameters);
$response->setData(array('status' => 'success',
'data' => array('message' => $l -> t('Successfully connected to database'))));
}
catch(\Exception $e)
{
$response->setData(array('status' => 'error',
'data' => array('message' => $l -> t('Error connecting to database: ').$e->getMessage())));
}
break;
// Get the autocompletion values for a column
case 'getColumnAutocomplete':
$cm = new \OC\DB\ConnectionFactory();
$search = $_POST['request'];
$table = $_POST['sql_table'];
$parameters = array('host' => $_POST['sql_hostname'],
'password' => $_POST['sql_password'],
'user' => $_POST['sql_username'],
'dbname' => $_POST['sql_database'],
'tablePrefix' => ''
);
try {
$conn = $cm -> getConnection($_POST['sql_driver'], $parameters);
$platform = $conn -> getDatabasePlatform();
$query = $platform -> getListTableColumnsSQL($table);
$result = $conn -> executeQuery($query);
$ret = array();
while($row = $result -> fetch())
{
$name = $row['Field'];
if(($search === '') || ($search === 'search') || (strpos($name, $search) === 0))
{
$ret[] = array('label' => $name,
'value' => $name);
}
}
$response -> setData($ret);
}
catch(\Exception $e)
{
$response->setData(array());
}
break;
// Get the autocompletion values for a table
case 'getTableAutocomplete':
$cm = new \OC\DB\ConnectionFactory();
$search = $_POST['request'];
$parameters = array('host' => $_POST['sql_hostname'],
'password' => $_POST['sql_password'],
'user' => $_POST['sql_username'],
'dbname' => $_POST['sql_database'],
'tablePrefix' => ''
);
try {
$conn = $cm -> getConnection($_POST['sql_driver'], $parameters);
$platform = $conn -> getDatabasePlatform();
$query = $platform -> getListTablesSQL();
$result = $conn -> executeQuery($query);
$ret = array();
while($row = $result -> fetch())
{
$name = $row['Tables_in_'.$_POST['sql_database']];
if(($search === '') || ($search === 'search') || (strpos($name, $search) === 0))
{
$ret[] = array('label' => $name,
'value' => $name);
}
}
$response -> setData($ret);
}
catch(\Exception $e)
{
$response->setData(array());
}
break;
}
} else
{
\OCP\JSON::error(array('data' => array('message' => $l -> t('Not submitted for us.'))));
return false;
// If the request was not for us, set an error message
$response->setData(array('status' => 'error',
'data' => array('message' => $l -> t('Not submitted for us.'))));
}
\OCP\JSON::success(array('data' => array('message' => $l -> t('Application settings successfully stored.'))));
return true;
// Return the JSON array
echo $response->render();

View File

@ -6,7 +6,7 @@
<licence>AGPL</licence>
<author>Andreas Boehler &lt;dev (at) aboehler
(dot) at &gt;</author>
<version>1.99</version>
<version>2.0</version>
<requiremin>8.1</requiremin>
<shipped>false</shipped>
<namespace>user_sql</namespace>

View File

@ -1,3 +1,5 @@
// settings.js of user_sql
// declare namespace
var user_sql = user_sql ||
{
@ -14,11 +16,132 @@ user_sql.adminSettingsUI = function()
// enable tabs on settings page
$('#sql').tabs();
// Attach auto-completion to all column fields
$('#col_username, #col_password, #col_displayname, #col_active, #col_email').autocomplete({
source: function(request, response)
{
var post = $('#sqlForm').serializeArray();
var domain = $('#sql_domain_chooser option:selected').val();
post.push({
name: 'function',
value: 'getColumnAutocomplete'
});
post.push({
name: 'domain',
value: domain
});
post.push({
name: 'request',
value: request.term
});
// Ajax foobar
$.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, response, 'json');
},
minLength: 0,
open: function() {
$(this).attr('state', 'open');
},
close: function() {
$(this).attr('state', 'closed');
}
}).focus(function() {
if($(this).attr('state') != 'open')
{
$(this).autocomplete("search");
}
});
// Attach auto-completion to all table fields
$('#sql_table').autocomplete({
source: function(request, response)
{
var post = $('#sqlForm').serializeArray();
var domain = $('#sql_domain_chooser option:selected').val();
post.push({
name: 'function',
value: 'getTableAutocomplete'
});
post.push({
name: 'domain',
value: domain
});
post.push({
name: 'request',
value: request.term
});
// Ajax foobar
$.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, response, 'json');
},
minLength: 0,
open: function() {
$(this).attr('state', 'open');
},
close: function() {
$(this).attr('state', 'closed');
}
}).focus(function() {
if($(this).attr('state') != 'open')
{
$(this).autocomplete("search");
}
});
// Verify the SQL database settings
$('#sqlVerify').click(function(event)
{
event.preventDefault();
var post = $('#sqlForm').serializeArray();
var domain = $('#sql_domain_chooser option:selected').val();
post.push({
name: 'function',
value: 'verifySettings'
});
post.push({
name: 'domain',
value: domain
});
$('#sql_verify_message').show();
$('#sql_success_message').hide();
$('#sql_error_message').hide();
$('#sql_update_message').hide();
// Ajax foobar
$.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, function(data)
{
$('#sql_verify_message').hide();
if(data.status == 'success')
{
$('#sql_success_message').html(data.data.message);
$('#sql_success_message').show();
window.setTimeout(function()
{
$('#sql_success_message').hide();
}, 10000);
} else
{
$('#sql_error_message').html(data.data.message);
$('#sql_error_message').show();
}
}, 'json');
return false;
});
// Save the settings for a domain
$('#sqlSubmit').click(function(event)
{
event.preventDefault();
var self = $(this);
var post = $('#sqlForm').serializeArray();
var domain = $('#sql_domain_chooser option:selected').val();
@ -34,6 +157,7 @@ user_sql.adminSettingsUI = function()
$('#sql_update_message').show();
$('#sql_success_message').hide();
$('#sql_verify_message').hide();
$('#sql_error_message').hide();
// Ajax foobar
$.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, function(data)
@ -56,16 +180,22 @@ user_sql.adminSettingsUI = function()
return false;
});
// Attach event handler to the domain chooser
$('#sql_domain_chooser').change(function() {
user_sql.loadDomainSettings($('#sql_domain_chooser option:selected').val());
});
}
};
/**
* Load the settings for the selected domain
* @param string domain The domain to load
*/
user_sql.loadDomainSettings = function(domain)
{
$('#sql_success_message').hide();
$('#sql_error_message').hide();
$('#sql_verify_message').hide();
$('#sql_loading_message').show();
var post = [
{
@ -120,10 +250,11 @@ user_sql.loadDomainSettings = function(domain)
$('#sql_error_message').html(data.data.message);
$('#sql_error_message').show();
}
}
}, 'json'
);
};
// Run our JS if the SQL settings are present
$(document).ready(function()
{
if($('#sql'))

View File

@ -29,11 +29,18 @@ class Helper {
protected $db_conn;
protected $settings;
/**
* The default constructor initializes some parameters
*/
public function __construct()
{
$this->db_conn = false;
}
/**
* Return an array with all supported parameters
* @return array Containing strings of the parameters
*/
public function getParameterArray()
{
$params = array(
@ -59,6 +66,12 @@ class Helper {
return $params;
}
/**
* Load the settings for a given domain. If the domain is not found,
* the settings for 'default' are returned instead.
* @param string $domain The domain name
* @return array of settings
*/
public function loadSettingsForDomain($domain)
{
\OCP\Util::writeLog('OC_USER_SQL', "Trying to load settings for domain: " . $domain, \OCP\Util::DEBUG);
@ -77,6 +90,15 @@ class Helper {
return $settings;
}
/**
* Run a given query type and return the results
* @param string $type The type of query to run
* @param array $params The parameter array of the query (i.e. the values to bind as key-value pairs)
* @param bool $execOnly Only execute the query, but don't fetch the results (optional, default = false)
* @param bool $fetchArray Fetch an array instead of a single row (optional, default=false)
* @param array $limits use the given limits for the query (optional, default = empty)
* @return mixed
*/
public function runQuery($type, $params, $execOnly = false, $fetchArray = false, $limits = array())
{
\OCP\Util::writeLog('OC_USER_SQL', "Entering runQuery for type: " . $type, \OCP\Util::DEBUG);
@ -188,18 +210,29 @@ class Helper {
return $row;
}
/**
* Connect to the database using ownCloud's DBAL
* @param array $settings The settings for the connection
* @return bool
*/
public function connectToDb($settings)
{
$this -> settings = $settings;
$dsn = $this -> settings['sql_driver'] . ":host=" . $this -> settings['sql_hostname'] . ";dbname=" . $this -> settings['sql_database'];
$cm = new \OC\DB\ConnectionFactory();
$parameters = array('host' => $this -> settings['sql_hostname'],
'password' => $this -> settings['sql_password'],
'user' => $this -> settings['sql_username'],
'dbname' => $this -> settings['sql_database'],
'tablePrefix' => ''
);
try
{
$this -> db = new \PDO($dsn, $this -> settings['sql_username'], $this -> settings['sql_password']);
$this -> db = $cm -> getConnection($this -> settings['sql_driver'], $parameters);
$this -> db -> query("SET NAMES 'UTF8'");
$this -> db_conn = true;
return true;
}
catch (\PDOException $e)
catch (\Exception $e)
{
\OCP\Util::writeLog('OC_USER_SQL', 'Failed to connect to the database: ' . $e -> getMessage(), \OCP\Util::ERROR);
$this -> db_conn = false;

View File

@ -15,7 +15,7 @@ $cfgClass = $ocVersion >= 7 ? 'section' : 'personalblock';
<?php endforeach ?>
</select>
<ul>
<li><a id="sqlBasicSettings" href="#sql-1"><?php p($l -> t('Basic Settings')); ?></a></li>
<li><a id="sqlBasicSettings" href="#sql-1"><?php p($l -> t('Connection Settings')); ?></a></li>
<li><a id="sqlAdvSettings" href="#sql-2"><?php p($l -> t('Advanced Settings')); ?></a></li>
</ul>
@ -41,11 +41,12 @@ $cfgClass = $ocVersion >= 7 ? 'section' : 'personalblock';
<tr><td><label for="sql_username"><?php p($l -> t('Username')); ?></label></td><td><input type="text" id="sql_username" name="sql_username" value="<?php p($_['sql_username']); ?>" /></td></tr>
<tr><td><label for="sql_database"><?php p($l -> t('Database')); ?></label></td><td><input type="text" id="sql_database" name="sql_database" value="<?php p($_['sql_database']); ?>" /></td></tr>
<tr><td><label for="sql_password"><?php p($l -> t('Password')); ?></label></td><td><input type="password" id="sql_password" name="sql_password" value="<?php p($_['sql_password']); ?>" /></td></tr>
<tr><td><label for="sql_table"><?php p($l -> t('Table')); ?></label></td><td><input type="text" id="sql_table" name="sql_table" value="<?php p($_['sql_table']); ?>" /></td></tr>
<tr><td></td><td><input type="submit" id="sqlVerify" value="<?php p($l -> t('Verify Settings')); ?>"></td></tr>
</table>
</fieldset>
<fieldset id="sql-2">
<table>
<tr><td><label for="sql_table"><?php p($l -> t('Table')); ?></label></td><td><input type="text" id="sql_table" name="sql_table" value="<?php p($_['sql_table']); ?>" /></td></tr>
<tr><td><label for="col_username"><?php p($l -> t('Username Column')); ?></label></td><td><input type="text" id="col_username" name="col_username" value="<?php p($_['col_username']); ?>" /></td></tr>
<tr><td><label for="col_password"><?php p($l -> t('Password Column')); ?></label></td><td><input type="text" id="col_password" name="col_password" value="<?php p($_['col_password']); ?>" /></td></tr>
<tr><td><label for="set_allow_pwchange"><?php p($l -> t('Allow password changing (read README!)')); ?></label></td><td><input type="checkbox" id="set_allow_pwchange" name="set_allow_pwchange" value="1"<?php
@ -101,6 +102,7 @@ $cfgClass = $ocVersion >= 7 ? 'section' : 'personalblock';
<input id="sqlSubmit" type="submit" value="<?php p($l -> t('Save')); ?>" />
<div id="sql_update_message" class="statusmessage"><?php p($l -> t('Saving...')); ?></div>
<div id="sql_loading_message" class="statusmessage"><?php p($l -> t('Loading...')); ?></div>
<div id="sql_verify_message" class="statusmessage"><?php p($l -> t('Verifying...')); ?></div>
<div id="sql_error_message" class="errormessage"></div>
<div id="sql_success_message" class="successmessage"></div>
</div>

View File

@ -34,11 +34,14 @@ use \OCA\user_sql\lib\Helper;
class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\UserInterface
{
protected $cache;
// cached settings
protected $settings;
protected $helper;
protected $session_cache_name;
/**
* The default constructor. It loads the settings for the given domain
* and tries to connect to the database.
*/
public function __construct()
{
$memcache = \OC::$server->getMemCacheFactory();
@ -54,6 +57,17 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
return false;
}
/**
* Sync the user's E-Mail address with the address stored by ownCloud.
* We have three (four) sync modes:
* - none: Does nothing
* - initial: Do the sync only once from SQL -> ownCloud
* - forcesql: The SQL database always wins and sync to ownCloud
* - forceoc: ownCloud always wins and syncs to SQL
*
* @param string $uid The user's ID to sync
* @return bool Success or Fail
*/
private function doEmailSync($uid)
{
\OCP\Util::writeLog('OC_USER_SQL', "Entering doEmailSync for UID: $uid", \OCP\Util::DEBUG);
@ -100,6 +114,13 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
return true;
}
/**
* This maps the username to the specified domain name.
* It can only append a default domain name.
*
* @param string $uid The UID to work with
* @return string The mapped UID
*/
private function doUserDomainMapping($uid)
{
$uid = trim($uid);
@ -118,6 +139,11 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
return $uid;
}
/**
* Return the actions implemented by this backend
* @param $actions
* @return bool
*/
public function implementsAction($actions)
{
return (bool)((\OC_User_Backend::CHECK_PASSWORD
@ -126,11 +152,19 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
) & $actions);
}
/**
* Checks if this backend has user listing support
* @return bool
*/
public function hasUserListings()
{
return true;
}
/**
* Create a new user account using this backend
* @return bool always false, as we can't create users
*/
public function createUser()
{
// Can't create user
@ -138,6 +172,11 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
return false;
}
/**
* Delete a user account using this backend
* @param string $uid The user's ID to delete
* @return bool always false, as we can't delete users
*/
public function deleteUser($uid)
{
// Can't delete user
@ -145,6 +184,14 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
return false;
}
/**
* Set (change) a user password
* This can be enabled/disabled in the settings (set_allow_pwchange)
*
* @param string $uid The user ID
* @param string $password The user's new password
* @return bool The return status
*/
public function setPassword($uid, $password)
{
// Update the user's password - this might affect other services, that
@ -192,10 +239,10 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
}
/**
* @brief Check if the password is correct
* @param $uid The username
* @param $password The password
* @returns true/false
* Check if the password is correct
* @param string $uid The username
* @param string $password The password
* @return bool true/false
*
* Check if the password is correct without logging in the user
*/
@ -250,6 +297,10 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
}
}
/**
* Count the number of users
* @return int The user count
*/
public function countUsers()
{
\OCP\Util::writeLog('OC_USER_SQL', "Entering countUsers()", \OCP\Util::DEBUG);
@ -269,12 +320,12 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
}
/**
* @brief Get a list of all users
* @returns array with all uids
*
* Get a list of all users.
* Get a list of all users
* @param string $search The search term (can be empty)
* @param int $limit The search limit (can be null)
* @param int $offset The search offset (can be null)
* @return array with all uids
*/
public function getUsers($search = '', $limit = null, $offset = null)
{
\OCP\Util::writeLog('OC_USER_SQL', "Entering getUsers() with Search: $search, Limit: $limit, Offset: $offset", \OCP\Util::DEBUG);
@ -308,11 +359,10 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
}
/**
* @brief check if a user exists
* Check if a user exists
* @param string $uid the username
* @return boolean
*/
public function userExists($uid)
{
@ -341,6 +391,11 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
}
/**
* Get the display name of the user
* @param string $uid The user ID
* @return mixed The user's display name or FALSE
*/
public function getDisplayName($uid)
{
\OCP\Util::writeLog('OC_USER_SQL', "Entering getDisplayName() for UID: $uid", \OCP\Util::DEBUG);
@ -398,8 +453,8 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
* salt used - hence
* the second parameter ($pw_db), which is the existing hash from the DB.
*
* @param string $pw
* @param string $encrypted password
* @param string $pw cleartext password
* @param string $pw_db encrypted password from database
* @return string encrypted password.
*/
private function pacrypt($pw, $pw_db = "")
@ -486,11 +541,14 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
return $password;
}
//
// md5crypt
// Action: Creates MD5 encrypted password
// Call: md5crypt (string cleartextpassword)
//
/**
* md5crypt
* Creates MD5 encrypted password
* @param string $pw The password to encrypt
* @param string $salt The salt to use
* @param string $magic ?
* @return string The encrypted password
*/
private function md5crypt($pw, $salt = "", $magic = "")
{
@ -563,6 +621,10 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
return "$magic$salt\$$passwd";
}
/**
* Create a new salte
* @return string The salt
*/
private function create_salt()
{
srand((double) microtime() * 1000000);
@ -570,11 +632,22 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
return $salt;
}
/**
* Encrypt using SSHA256 algorithm
* @param string $pw The password
* @param string $salt The salt to use
* @return string The hashed password, prefixed by {SSHA256}
*/
private function ssha256($pw, $salt)
{
return '{SSHA256}'.base64_encode(hash('sha256',$pw.$salt,true).$salt);
}
/**
* PostfixAdmin's hex2bin function
* @param string $str The string to convert
* @return string The converted string
*/
private function pahex2bin($str)
{
if(function_exists('hex2bin'))
@ -593,6 +666,9 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
}
}
/**
* Convert to 64?
*/
private function to64($v, $n)
{
$ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@ -608,8 +684,8 @@ class OC_USER_SQL extends \OC_User_Backend implements \OCP\IUserBackend, \OCP\Us
/**
* Store a value in memcache or the session, if no memcache is available
* @param string $key
* @param mixed $value
* @param string $key The key
* @param mixed $value The value to store
* @param int $ttl (optional) defaults to 3600 seconds.
*/
private function setCache($key, $value, $ttl=3600)