added:
* Nextcloud 12 & 13 support * added SALT support for password algorithms "system" and "password_hash" * added security fix for password length sniffing attacks * moved files to be more on the standard places * renamed some files to be more standard like * source code changes to be more standard like (max 80 characters)
This commit is contained in:
253
lib/PasswordHash.php
Normal file
253
lib/PasswordHash.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
#
|
||||
# Portable PHP password hashing framework.
|
||||
#
|
||||
# Version 0.3 / genuine.
|
||||
#
|
||||
# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
|
||||
# the public domain. Revised in subsequent years, still public domain.
|
||||
#
|
||||
# There's absolutely no warranty.
|
||||
#
|
||||
# The homepage URL for this framework is:
|
||||
#
|
||||
# http://www.openwall.com/phpass/
|
||||
#
|
||||
# Please be sure to update the Version line if you edit this file in any way.
|
||||
# It is suggested that you leave the main version number intact, but indicate
|
||||
# your project name (after the slash) and add your own revision information.
|
||||
#
|
||||
# Please do not change the "private" password hashing method implemented in
|
||||
# here, thereby making your hashes incompatible. However, if you must, please
|
||||
# change the hash type identifier (the "$P$") to something different.
|
||||
#
|
||||
# Obviously, since this code is in the public domain, the above are not
|
||||
# requirements (there can be none), but merely suggestions.
|
||||
#
|
||||
class PasswordHash {
|
||||
var $itoa64;
|
||||
var $iteration_count_log2;
|
||||
var $portable_hashes;
|
||||
var $random_state;
|
||||
|
||||
function __construct($iteration_count_log2, $portable_hashes)
|
||||
{
|
||||
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
|
||||
$iteration_count_log2 = 8;
|
||||
$this->iteration_count_log2 = $iteration_count_log2;
|
||||
|
||||
$this->portable_hashes = $portable_hashes;
|
||||
|
||||
$this->random_state = microtime();
|
||||
if (function_exists('getmypid'))
|
||||
$this->random_state .= getmypid();
|
||||
}
|
||||
|
||||
function get_random_bytes($count)
|
||||
{
|
||||
$output = '';
|
||||
if (is_readable('/dev/urandom') &&
|
||||
($fh = @fopen('/dev/urandom', 'rb'))) {
|
||||
$output = fread($fh, $count);
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
if (strlen($output) < $count) {
|
||||
$output = '';
|
||||
for ($i = 0; $i < $count; $i += 16) {
|
||||
$this->random_state =
|
||||
md5(microtime() . $this->random_state);
|
||||
$output .=
|
||||
pack('H*', md5($this->random_state));
|
||||
}
|
||||
$output = substr($output, 0, $count);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function encode64($input, $count)
|
||||
{
|
||||
$output = '';
|
||||
$i = 0;
|
||||
do {
|
||||
$value = ord($input[$i++]);
|
||||
$output .= $this->itoa64[$value & 0x3f];
|
||||
if ($i < $count)
|
||||
$value |= ord($input[$i]) << 8;
|
||||
$output .= $this->itoa64[($value >> 6) & 0x3f];
|
||||
if ($i++ >= $count)
|
||||
break;
|
||||
if ($i < $count)
|
||||
$value |= ord($input[$i]) << 16;
|
||||
$output .= $this->itoa64[($value >> 12) & 0x3f];
|
||||
if ($i++ >= $count)
|
||||
break;
|
||||
$output .= $this->itoa64[($value >> 18) & 0x3f];
|
||||
} while ($i < $count);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function gensalt_private($input)
|
||||
{
|
||||
$output = '$P$';
|
||||
$output .= $this->itoa64[min($this->iteration_count_log2 +
|
||||
((PHP_VERSION >= '5') ? 5 : 3), 30)];
|
||||
$output .= $this->encode64($input, 6);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function crypt_private($password, $setting)
|
||||
{
|
||||
$output = '*0';
|
||||
if (substr($setting, 0, 2) === $output)
|
||||
$output = '*1';
|
||||
|
||||
$id = substr($setting, 0, 3);
|
||||
# We use "$P$", phpBB3 uses "$H$" for the same thing
|
||||
if ($id !== '$P$' && $id !== '$H$')
|
||||
return $output;
|
||||
|
||||
$count_log2 = strpos($this->itoa64, $setting[3]);
|
||||
if ($count_log2 < 7 || $count_log2 > 30)
|
||||
return $output;
|
||||
|
||||
$count = 1 << $count_log2;
|
||||
|
||||
$salt = substr($setting, 4, 8);
|
||||
if (strlen($salt) !== 8)
|
||||
return $output;
|
||||
|
||||
# We're kind of forced to use MD5 here since it's the only
|
||||
# cryptographic primitive available in all versions of PHP
|
||||
# currently in use. To implement our own low-level crypto
|
||||
# in PHP would result in much worse performance and
|
||||
# consequently in lower iteration counts and hashes that are
|
||||
# quicker to crack (by non-PHP code).
|
||||
if (PHP_VERSION >= '5') {
|
||||
$hash = md5($salt . $password, TRUE);
|
||||
do {
|
||||
$hash = md5($hash . $password, TRUE);
|
||||
} while (--$count);
|
||||
} else {
|
||||
$hash = pack('H*', md5($salt . $password));
|
||||
do {
|
||||
$hash = pack('H*', md5($hash . $password));
|
||||
} while (--$count);
|
||||
}
|
||||
|
||||
$output = substr($setting, 0, 12);
|
||||
$output .= $this->encode64($hash, 16);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function gensalt_extended($input)
|
||||
{
|
||||
$count_log2 = min($this->iteration_count_log2 + 8, 24);
|
||||
# This should be odd to not reveal weak DES keys, and the
|
||||
# maximum valid value is (2**24 - 1) which is odd anyway.
|
||||
$count = (1 << $count_log2) - 1;
|
||||
|
||||
$output = '_';
|
||||
$output .= $this->itoa64[$count & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 6) & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 12) & 0x3f];
|
||||
$output .= $this->itoa64[($count >> 18) & 0x3f];
|
||||
|
||||
$output .= $this->encode64($input, 3);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function gensalt_blowfish($input)
|
||||
{
|
||||
# This one needs to use a different order of characters and a
|
||||
# different encoding scheme from the one in encode64() above.
|
||||
# We care because the last character in our encoded string will
|
||||
# only represent 2 bits. While two known implementations of
|
||||
# bcrypt will happily accept and correct a salt string which
|
||||
# has the 4 unused bits set to non-zero, we do not want to take
|
||||
# chances and we also do not want to waste an additional byte
|
||||
# of entropy.
|
||||
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
$output = '$2a$';
|
||||
$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
|
||||
$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
|
||||
$output .= '$';
|
||||
|
||||
$i = 0;
|
||||
do {
|
||||
$c1 = ord($input[$i++]);
|
||||
$output .= $itoa64[$c1 >> 2];
|
||||
$c1 = ($c1 & 0x03) << 4;
|
||||
if ($i >= 16) {
|
||||
$output .= $itoa64[$c1];
|
||||
break;
|
||||
}
|
||||
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 4;
|
||||
$output .= $itoa64[$c1];
|
||||
$c1 = ($c2 & 0x0f) << 2;
|
||||
|
||||
$c2 = ord($input[$i++]);
|
||||
$c1 |= $c2 >> 6;
|
||||
$output .= $itoa64[$c1];
|
||||
$output .= $itoa64[$c2 & 0x3f];
|
||||
} while (1);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function HashPassword($password)
|
||||
{
|
||||
$random = '';
|
||||
|
||||
if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) {
|
||||
$random = $this->get_random_bytes(16);
|
||||
$hash =
|
||||
crypt($password, $this->gensalt_blowfish($random));
|
||||
if (strlen($hash) === 60)
|
||||
return $hash;
|
||||
}
|
||||
|
||||
if (CRYPT_EXT_DES === 1 && !$this->portable_hashes) {
|
||||
if (strlen($random) < 3)
|
||||
$random = $this->get_random_bytes(3);
|
||||
$hash =
|
||||
crypt($password, $this->gensalt_extended($random));
|
||||
if (strlen($hash) === 20)
|
||||
return $hash;
|
||||
}
|
||||
|
||||
if (strlen($random) < 6)
|
||||
$random = $this->get_random_bytes(6);
|
||||
$hash =
|
||||
$this->crypt_private($password,
|
||||
$this->gensalt_private($random));
|
||||
if (strlen($hash) === 34)
|
||||
return $hash;
|
||||
|
||||
# Returning '*' on error is safe here, but would _not_ be safe
|
||||
# in a crypt(3)-like function used _both_ for generating new
|
||||
# hashes and for validating passwords against existing hashes.
|
||||
return '*';
|
||||
}
|
||||
|
||||
function CheckPassword($password, $stored_hash)
|
||||
{
|
||||
$hash = $this->crypt_private($password, $stored_hash);
|
||||
if ($hash[0] === '*')
|
||||
$hash = crypt($password, $stored_hash);
|
||||
|
||||
return $hash === $stored_hash;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
100
lib/Settings/Admin.php
Normal file
100
lib/Settings/Admin.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c)
|
||||
*
|
||||
* @author
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\user_sql\Settings;
|
||||
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\Defaults;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\Settings\ISettings;
|
||||
use OCA\user_sql\lib\Helper;
|
||||
|
||||
class Admin implements ISettings {
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
/** @var Defaults */
|
||||
private $defaults;
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @param IL10N $l10n
|
||||
* @param Defaults $defaults
|
||||
* @param IConfig $config
|
||||
*/
|
||||
public function __construct(IL10N $l10n,
|
||||
Defaults $defaults,
|
||||
IConfig $config) {
|
||||
$this->l10n = $l10n;
|
||||
$this->defaults = $defaults;
|
||||
$this->config = $config;
|
||||
$this->helper = new \OCA\user_sql\lib\Helper();
|
||||
$this->params = $this->helper->getParameterArray();
|
||||
$this->settings = $this->helper -> loadSettingsForDomain('default');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm() {
|
||||
|
||||
$type = $this->config->getAppValue('user_sql', 'type');
|
||||
$trusted_domains = \OC::$server->getConfig()->getSystemValue('trusted_domains');
|
||||
$inserted = array('default');
|
||||
array_splice($trusted_domains, 0, 0, $inserted);
|
||||
|
||||
$params = [
|
||||
'type' => $type,
|
||||
];
|
||||
$params['allowed_domains']= array_unique($trusted_domains);
|
||||
|
||||
foreach($this->params as $key)
|
||||
{
|
||||
$value = $this->settings[$key];
|
||||
$params[$key]=$value;
|
||||
}
|
||||
|
||||
$params["config"]=$this->config;
|
||||
return new TemplateResponse('user_sql', 'admin', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the section ID, e.g. 'sharing'
|
||||
*/
|
||||
public function getSection() {
|
||||
return 'usersql';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int whether the form should be rather on the top or bottom of
|
||||
* the admin section. The forms are arranged in ascending order of the
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
*
|
||||
* keep the server setting at the top, right after "server settings"
|
||||
*/
|
||||
public function getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
68
lib/Settings/Section.php
Normal file
68
lib/Settings/Section.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c)
|
||||
*
|
||||
* @author
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\user_sql\Settings;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\Settings\IIconSection;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class Section implements IIconSection {
|
||||
/** @var IL10N */
|
||||
private $l;
|
||||
|
||||
/**
|
||||
* @param IL10N $l
|
||||
*/
|
||||
public function __construct(IURLGenerator $url,IL10N $l) {
|
||||
$this->l = $l;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getID() {
|
||||
return 'usersql';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->l->t('User SQL');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority() {
|
||||
return 75;
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIcon() {
|
||||
return $this->url->imagePath('user_sql', 'app-dark.svg');
|
||||
}
|
||||
}
|
||||
95
lib/group_sql.php
Normal file
95
lib/group_sql.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\user_sql;
|
||||
|
||||
use \OCA\user_sql\lib\Helper;
|
||||
|
||||
class OC_GROUP_SQL extends \OC_Group_Backend implements \OCP\GroupInterface
|
||||
{
|
||||
protected $settings;
|
||||
protected $helper;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this -> helper = new \OCA\user_sql\lib\Helper();
|
||||
$domain = \OC::$server->getRequest()->getServerHost();
|
||||
$this -> settings = $this -> helper -> loadSettingsForDomain($domain);
|
||||
$this -> helper -> connectToDb($this -> settings);
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserGroups($uid) {
|
||||
if(empty($this -> settings['sql_group_table']))
|
||||
{
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Group table not configured", \OCP\Util::DEBUG);
|
||||
return [];
|
||||
}
|
||||
$rows = $this -> helper -> runQuery('getUserGroups', array('uid' => $uid), false, true);
|
||||
if($rows === false)
|
||||
{
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Found no group", \OCP\Util::DEBUG);
|
||||
return [];
|
||||
}
|
||||
$groups = array();
|
||||
foreach($rows as $row)
|
||||
{
|
||||
$groups[] = $row[$this -> settings['col_group_name']];
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
public function getGroups($search = '', $limit = null, $offset = null) {
|
||||
if(empty($this -> settings['sql_group_table']))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
$search = "%".$search."%";
|
||||
$rows = $this -> helper -> runQuery('getGroups', array('search' => $search), false, true, array('limit' => $limit, 'offset' => $offset));
|
||||
if($rows === false)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
$groups = array();
|
||||
foreach($rows as $row)
|
||||
{
|
||||
$groups[] = $row[$this -> settings['col_group_name']];
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
public function usersInGroup($gid, $search = '', $limit = null, $offset = null) {
|
||||
if(empty($this -> settings['sql_group_table']))
|
||||
{
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Group table not configured", \OCP\Util::DEBUG);
|
||||
return [];
|
||||
}
|
||||
$rows = $this -> helper -> runQuery('getGroupUsers', array('gid' => $gid), false, true);
|
||||
if($rows === false)
|
||||
{
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Found no users for group", \OCP\Util::DEBUG);
|
||||
return [];
|
||||
}
|
||||
$users = array();
|
||||
foreach($rows as $row)
|
||||
{
|
||||
$users[] = $row[$this -> settings['col_group_username']];
|
||||
}
|
||||
return $users;
|
||||
}
|
||||
|
||||
public function countUsersInGroup($gid, $search = '') {
|
||||
if(empty($this -> settings['sql_group_table']))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
$search = "%".$search."%";
|
||||
$count = $this -> helper -> runQuery('countUsersInGroup', array('gid' => $gid, 'search' => $search));
|
||||
if($count === false)
|
||||
{
|
||||
return 0;
|
||||
} else {
|
||||
return intval(reset($count));
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -20,15 +20,17 @@
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
namespace OCA\user_sql\lib;
|
||||
use OCP\IConfig;
|
||||
use OCP\Util;
|
||||
|
||||
class Helper {
|
||||
|
||||
protected $db;
|
||||
protected $db_conn;
|
||||
protected $settings;
|
||||
|
||||
|
||||
/**
|
||||
* The default constructor initializes some parameters
|
||||
*/
|
||||
@@ -74,7 +76,7 @@ class Helper {
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the settings for a given domain. If the domain is not found,
|
||||
* the settings for 'default' are returned instead.
|
||||
@@ -83,7 +85,7 @@ class Helper {
|
||||
*/
|
||||
public function loadSettingsForDomain($domain)
|
||||
{
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Trying to load settings for domain: " . $domain, \OCP\Util::DEBUG);
|
||||
Util::writeLog('OC_USER_SQL', "Trying to load settings for domain: " . $domain, Util::DEBUG);
|
||||
$settings = array();
|
||||
$sql_host = \OC::$server->getConfig()->getAppValue('user_sql', 'sql_hostname_'.$domain, '');
|
||||
if($sql_host === '')
|
||||
@@ -95,10 +97,10 @@ class Helper {
|
||||
{
|
||||
$settings[$param] = \OC::$server->getConfig()->getAppValue('user_sql', $param.'_'.$domain, '');
|
||||
}
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Loaded settings for domain: " . $domain, \OCP\Util::DEBUG);
|
||||
Util::writeLog('OC_USER_SQL', "Loaded settings for domain: " . $domain, Util::DEBUG);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a given query type and return the results
|
||||
* @param string $type The type of query to run
|
||||
@@ -110,10 +112,10 @@ class Helper {
|
||||
*/
|
||||
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);
|
||||
Util::writeLog('OC_USER_SQL', "Entering runQuery for type: " . $type, Util::DEBUG);
|
||||
if(!$this -> db_conn)
|
||||
return false;
|
||||
|
||||
|
||||
switch($type)
|
||||
{
|
||||
case 'getHome':
|
||||
@@ -152,7 +154,7 @@ class Helper {
|
||||
$query .= " WHERE ".$this->settings['col_username']." LIKE :search";
|
||||
if($this -> settings['col_active'] !== '')
|
||||
$query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active'];
|
||||
$query .= " ORDER BY ".$this->settings['col_username'];
|
||||
$query .= " ORDER BY ".$this->settings['col_username'];
|
||||
break;
|
||||
|
||||
case 'userExists':
|
||||
@@ -205,27 +207,27 @@ class Helper {
|
||||
if(isset($limits['offset']) && $limits['offset'] !== null)
|
||||
{
|
||||
$offset = intval($limits['offset']);
|
||||
$query .= " OFFSET ".$offset;
|
||||
$query .= " OFFSET ".$offset;
|
||||
}
|
||||
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Preparing query: $query", \OCP\Util::DEBUG);
|
||||
Util::writeLog('OC_USER_SQL', "Preparing query: $query", Util::DEBUG);
|
||||
$result = $this -> db -> prepare($query);
|
||||
foreach($params as $param => $value)
|
||||
{
|
||||
$result -> bindValue(":".$param, $value);
|
||||
}
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Executing query...", \OCP\Util::DEBUG);
|
||||
Util::writeLog('OC_USER_SQL', "Executing query...", Util::DEBUG);
|
||||
if(!$result -> execute())
|
||||
{
|
||||
$err = $result -> errorInfo();
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Query failed: " . $err[2], \OCP\Util::DEBUG);
|
||||
Util::writeLog('OC_USER_SQL', "Query failed: " . $err[2], Util::DEBUG);
|
||||
return false;
|
||||
}
|
||||
if($execOnly === true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Fetching result...", \OCP\Util::DEBUG);
|
||||
Util::writeLog('OC_USER_SQL', "Fetching result...", Util::DEBUG);
|
||||
if($fetchArray === true)
|
||||
$row = $result -> fetchAll();
|
||||
else
|
||||
@@ -259,15 +261,15 @@ class Helper {
|
||||
$this -> db -> query("SET NAMES 'UTF8'");
|
||||
$this -> db_conn = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
\OCP\Util::writeLog('OC_USER_SQL', 'Failed to connect to the database: ' . $e -> getMessage(), \OCP\Util::ERROR);
|
||||
Util::writeLog('OC_USER_SQL', 'Failed to connect to the database: ' . $e -> getMessage(), Util::ERROR);
|
||||
$this -> db_conn = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if all of the given columns exist
|
||||
* @param array $parameters The connection parameters
|
||||
@@ -286,15 +288,15 @@ class Helper {
|
||||
if(!in_array($col, $columns, true))
|
||||
{
|
||||
$res = false;
|
||||
$err .= $table.'.'.$col.' ';
|
||||
$err .= $col.' ';
|
||||
}
|
||||
}
|
||||
if($res)
|
||||
return true;
|
||||
else
|
||||
else
|
||||
return $err;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given table exists
|
||||
* @param array $parameters The connection parameters
|
||||
@@ -308,7 +310,7 @@ class Helper {
|
||||
$tablesWithoutSchema = $this->getTables($parameters, $sql_driver, false);
|
||||
return in_array($table, $tablesWithSchema, true) || in_array($table, $tablesWithoutSchema, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a list of tables for the given connection parameters
|
||||
* @param array $parameters The connection parameters
|
||||
@@ -428,7 +430,7 @@ class Helper {
|
||||
catch(\Exception $e)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
996
lib/user_sql.php
Normal file
996
lib/user_sql.php
Normal file
@@ -0,0 +1,996 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ownCloud - user_sql
|
||||
*
|
||||
* @author Andreas Böhler and contributors
|
||||
* @copyright 2012-2015 Andreas Böhler <dev (at) aboehler (dot) at>
|
||||
*
|
||||
* credits go to Ed W for several SQL injection fixes and caching support
|
||||
* credits go to Frédéric France for providing Joomla support
|
||||
* credits go to Mark Jansenn for providing Joomla 2.5.18+ / 3.2.1+ support
|
||||
* credits go to Dominik Grothaus for providing SSHA256 support and fixing a few bugs
|
||||
* credits go to Sören Eberhardt-Biermann for providing multi-host support
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\user_sql;
|
||||
use OC\User\Backend;
|
||||
|
||||
use \OCA\user_sql\lib\Helper;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Notification\IManager as INotificationManager;
|
||||
use OCP\Util;
|
||||
|
||||
abstract class BackendUtility {
|
||||
protected $access;
|
||||
|
||||
/**
|
||||
* constructor, make sure the subclasses call this one!
|
||||
* @param Access $access an instance of Access for LDAP interaction
|
||||
*/
|
||||
public function __construct(Access $access) {
|
||||
$this->access = $access;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class OC_USER_SQL extends BackendUtility implements \OCP\IUserBackend,
|
||||
\OCP\UserInterface
|
||||
{
|
||||
protected $cache;
|
||||
protected $settings;
|
||||
protected $helper;
|
||||
protected $session_cache_name;
|
||||
protected $ocConfig;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
if ( $memcache -> isAvailable())
|
||||
{
|
||||
$this -> cache = $memcache -> create();
|
||||
}
|
||||
$this -> helper = new \OCA\user_sql\lib\Helper();
|
||||
$domain = \OC::$server->getRequest()->getServerHost();
|
||||
$this -> settings = $this -> helper -> loadSettingsForDomain($domain);
|
||||
$this -> ocConfig = \OC::$server->getConfig();
|
||||
$this -> helper -> connectToDb($this -> settings);
|
||||
$this -> session_cache_name = 'USER_SQL_CACHE';
|
||||
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)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL', "Entering doEmailSync for UID: $uid",
|
||||
Util::DEBUG);
|
||||
if($this -> settings['col_email'] === '')
|
||||
return false;
|
||||
|
||||
if($this -> settings['set_mail_sync_mode'] === 'none')
|
||||
return false;
|
||||
|
||||
$ocUid = $uid;
|
||||
$uid = $this -> doUserDomainMapping($uid);
|
||||
|
||||
$row = $this -> helper -> runQuery('getMail', array('uid' => $uid));
|
||||
if($row === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$newMail = $row[$this -> settings['col_email']];
|
||||
|
||||
$currMail = $this->ocConfig->getUserValue( $ocUid,
|
||||
'settings',
|
||||
'email', '');
|
||||
|
||||
switch($this -> settings['set_mail_sync_mode'])
|
||||
{
|
||||
case 'initial':
|
||||
if($currMail === '')
|
||||
$this->ocConfig->setUserValue( $ocUid,
|
||||
'settings',
|
||||
'email',
|
||||
$newMail);
|
||||
break;
|
||||
case 'forcesql':
|
||||
//if($currMail !== $newMail)
|
||||
$this->ocConfig->setUserValue( $ocUid,
|
||||
'settings',
|
||||
'email',
|
||||
$newMail);
|
||||
break;
|
||||
case 'forceoc':
|
||||
if(($currMail !== '') && ($currMail !== $newMail))
|
||||
{
|
||||
$row = $this -> helper -> runQuery('setMail',
|
||||
array('uid' => $uid,
|
||||
'currMail' => $currMail)
|
||||
, true);
|
||||
|
||||
if($row === false)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Could not update E-Mail address in SQL database!",
|
||||
\OCP\Util::ERROR);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if($this -> settings['set_default_domain'] !== '')
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL', "Append default domain: ".
|
||||
$this -> settings['set_default_domain'], Util::DEBUG);
|
||||
if(strpos($uid, '@') === false)
|
||||
{
|
||||
$uid .= "@" . $this -> settings['set_default_domain'];
|
||||
}
|
||||
}
|
||||
|
||||
$uid = strtolower($uid);
|
||||
Util::writeLog('OC_USER_SQL', 'Returning mapped UID: ' . $uid,
|
||||
Util::DEBUG);
|
||||
return $uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actions implemented by this backend
|
||||
* @param $actions
|
||||
* @return bool
|
||||
*/
|
||||
public function implementsActions($actions)
|
||||
{
|
||||
return (bool)((Backend::CHECK_PASSWORD
|
||||
| Backend::GET_DISPLAYNAME
|
||||
| Backend::COUNT_USERS
|
||||
| ($this -> settings['set_allow_pwchange'] === 'true' ?
|
||||
Backend::SET_PASSWORD : 0)
|
||||
| ($this -> settings['set_enable_gethome'] === 'true' ?
|
||||
Backend::GET_HOME : 0)
|
||||
) & $actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this backend has user listing support
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUserListings()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user's home directory, if enabled
|
||||
* @param string $uid The user's ID to retrieve
|
||||
* @return mixed The user's home directory or false
|
||||
*/
|
||||
public function getHome($uid)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL', "Entering getHome for UID: $uid",
|
||||
Util::DEBUG);
|
||||
|
||||
if($this -> settings['set_enable_gethome'] !== 'true')
|
||||
return false;
|
||||
|
||||
$uidMapped = $this -> doUserDomainMapping($uid);
|
||||
$home = false;
|
||||
|
||||
switch($this->settings['set_gethome_mode'])
|
||||
{
|
||||
case 'query':
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"getHome with Query selected, running Query...",
|
||||
Util::DEBUG);
|
||||
$row = $this -> helper -> runQuery('getHome',
|
||||
array('uid' => $uidMapped));
|
||||
if($row === false)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Got no row, return false",
|
||||
Util::DEBUG);
|
||||
return false;
|
||||
}
|
||||
$home = $row[$this -> settings['col_gethome']];
|
||||
break;
|
||||
|
||||
case 'static':
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"getHome with static selected",
|
||||
Util::DEBUG);
|
||||
$home = $this -> settings['set_gethome'];
|
||||
$home = str_replace('%ud', $uidMapped, $home);
|
||||
$home = str_replace('%u', $uid, $home);
|
||||
$home = str_replace('%d',
|
||||
$this -> settings['set_default_domain'],
|
||||
$home);
|
||||
break;
|
||||
}
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Returning getHome for UID: $uid with Home $home",
|
||||
Util::DEBUG);
|
||||
return $home;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
'Not possible to create local users from web'.
|
||||
' frontend using SQL user backend', \OCP\Util::ERROR);
|
||||
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
|
||||
Util::writeLog('OC_USER_SQL', 'Not possible to delete local users'.
|
||||
' from web frontend using SQL user backend', \OCP\Util::ERROR);
|
||||
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
|
||||
// use the same database, as well
|
||||
Util::writeLog('OC_USER_SQL', "Entering setPassword for UID: $uid",
|
||||
Util::DEBUG);
|
||||
|
||||
if($this -> settings['set_allow_pwchange'] !== 'true')
|
||||
return false;
|
||||
|
||||
$uid = $this -> doUserDomainMapping($uid);
|
||||
|
||||
$row = $this -> helper -> runQuery('getPass', array('uid' => $uid));
|
||||
if($row === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$old_password = $row[$this -> settings['col_password']];
|
||||
if($this -> settings['set_crypt_type'] === 'joomla2')
|
||||
{
|
||||
if(!class_exists('\PasswordHash'))
|
||||
require_once('PasswordHash.php');
|
||||
$hasher = new \PasswordHash(10, true);
|
||||
$enc_password = $hasher -> HashPassword($password);
|
||||
}
|
||||
// Redmine stores the salt separatedly, this doesn't play nice with
|
||||
// the way we check passwords
|
||||
elseif($this -> settings['set_crypt_type'] === 'redmine')
|
||||
{
|
||||
$salt = $this -> helper -> runQuery('getRedmineSalt',
|
||||
array('uid' => $uid));
|
||||
if(!$salt)
|
||||
return false;
|
||||
$enc_password = sha1($salt['salt'].sha1($password));
|
||||
|
||||
}
|
||||
elseif($this -> settings['set_crypt_type'] === 'sha1')
|
||||
{
|
||||
$enc_password = sha1($password);
|
||||
}
|
||||
elseif($this -> settings['set_crypt_type'] === 'system')
|
||||
{
|
||||
$prefix=substr($old_password,0,2);
|
||||
if ($prefix==="$2")
|
||||
{
|
||||
$enc_password = $this->pw_hash($password);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (($prefix==="$1") or ($prefix[0] != "$")) //old md5 or DES
|
||||
{
|
||||
//Update encryption algorithm
|
||||
$prefix="$6"; //change to sha512
|
||||
}
|
||||
|
||||
$newsalt=$this->create_systemsalt();
|
||||
$enc_password=crypt($password,$prefix ."$" . $newsalt);
|
||||
}
|
||||
|
||||
}
|
||||
elseif($this -> settings['set_crypt_type'] === 'password_hash')
|
||||
{
|
||||
$enc_password = $this->pw_hash($password);
|
||||
}
|
||||
else
|
||||
{
|
||||
$enc_password = $this -> pacrypt($password, $old_password);
|
||||
}
|
||||
$res = $this -> helper -> runQuery('setPass',
|
||||
array('uid' => $uid, 'enc_password' => $enc_password),
|
||||
true);
|
||||
if($res === false)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL', "Could not update password!",
|
||||
\OCP\Util::ERROR);
|
||||
return false;
|
||||
}
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Updated password successfully, return true",
|
||||
Util::DEBUG);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function checkPassword($uid, $password)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Entering checkPassword() for UID: $uid",
|
||||
Util::DEBUG);
|
||||
|
||||
$uid = $this -> doUserDomainMapping($uid);
|
||||
|
||||
$superuid = $this -> settings['supervisor'];
|
||||
if($uid !== $superuid && $this -> settings['set_supervisor'] === 'true' && substr($uid, 0, strlen($superuid)) === $superuid)
|
||||
{
|
||||
$row = $this -> helper -> runQuery('getPass', array('uid' => $superuid));
|
||||
if($row === false)
|
||||
{
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Got no row, return false", \OCP\Util::DEBUG);
|
||||
return false;
|
||||
}
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Logging in as supervisor", \OCP\Util::DEBUG);
|
||||
$db_pass = $row[$this -> settings['col_password']];
|
||||
$uid = explode(';', $uid)[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$row = $this -> helper -> runQuery('getPass', array('uid' => $uid));
|
||||
if($row === false)
|
||||
{
|
||||
\OCP\Util::writeLog('OC_USER_SQL', "Got no row, return false", \OCP\Util::DEBUG);
|
||||
return false;
|
||||
}
|
||||
$db_pass = $row[$this -> settings['col_password']];
|
||||
}
|
||||
|
||||
Util::writeLog('OC_USER_SQL', "Encrypting and checking password",
|
||||
Util::DEBUG);
|
||||
// Joomla 2.5.18 switched to phPass, which doesn't play nice with the
|
||||
// way we check passwords
|
||||
if($this -> settings['set_crypt_type'] === 'joomla2')
|
||||
{
|
||||
if(!class_exists('\PasswordHash'))
|
||||
require_once('PasswordHash.php');
|
||||
$hasher = new \PasswordHash(10, true);
|
||||
$ret = $hasher -> CheckPassword($password, $db_pass);
|
||||
}
|
||||
elseif($this -> settings['set_crypt_type'] === 'password_hash')
|
||||
{
|
||||
$ret = password_verify($password,$db_pass);
|
||||
}
|
||||
// Redmine stores the salt separatedly, this doesn't play nice with the
|
||||
// way we check passwords
|
||||
elseif($this -> settings['set_crypt_type'] === 'redmine')
|
||||
{
|
||||
$salt = $this -> helper -> runQuery('getRedmineSalt',
|
||||
array('uid' => $uid));
|
||||
if(!$salt)
|
||||
return false;
|
||||
$ret = sha1($salt['salt'].sha1($password)) === $db_pass;
|
||||
}
|
||||
|
||||
elseif($this -> settings['set_crypt_type'] == 'sha1')
|
||||
{
|
||||
$ret = $this->hash_equals(sha1($password) , $db_pass);
|
||||
} else
|
||||
|
||||
{
|
||||
// $ret = $this -> pacrypt($password, $db_pass) === $db_pass;
|
||||
$ret = $this->hash_equals($this -> pacrypt($password, $db_pass),
|
||||
$db_pass);
|
||||
}
|
||||
if($ret)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Passwords matching, return true",
|
||||
Util::DEBUG);
|
||||
if($this -> settings['set_strip_domain'] === 'true')
|
||||
{
|
||||
$uid = explode("@", $uid);
|
||||
$uid = $uid[0];
|
||||
}
|
||||
return $uid;
|
||||
} else
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Passwords do not match, return false",
|
||||
Util::DEBUG);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of users
|
||||
* @return int The user count
|
||||
*/
|
||||
public function countUsers()
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL', "Entering countUsers()",
|
||||
Util::DEBUG);
|
||||
|
||||
$search = "%".$this -> doUserDomainMapping("");
|
||||
$userCount = $this -> helper -> runQuery('countUsers',
|
||||
array('search' => $search));
|
||||
if($userCount === false)
|
||||
{
|
||||
$userCount = 0;
|
||||
}
|
||||
else {
|
||||
$userCount = reset($userCount);
|
||||
}
|
||||
|
||||
Util::writeLog('OC_USER_SQL', "Return usercount: ".$userCount,
|
||||
Util::DEBUG);
|
||||
return $userCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Entering getUsers() with Search: $search, ".
|
||||
"Limit: $limit, Offset: $offset", Util::DEBUG);
|
||||
$users = array();
|
||||
|
||||
if($search !== '')
|
||||
{
|
||||
$search = "%".$this -> doUserDomainMapping($search."%")."%";
|
||||
}
|
||||
else
|
||||
{
|
||||
$search = "%".$this -> doUserDomainMapping("")."%";
|
||||
}
|
||||
|
||||
$rows = $this -> helper -> runQuery('getUsers',
|
||||
array('search' => $search),
|
||||
false,
|
||||
true,
|
||||
array('limit' => $limit,
|
||||
'offset' => $offset));
|
||||
if($rows === false)
|
||||
return array();
|
||||
|
||||
foreach($rows as $row)
|
||||
{
|
||||
$uid = $row[$this -> settings['col_username']];
|
||||
if($this -> settings['set_strip_domain'] === 'true')
|
||||
{
|
||||
$uid = explode("@", $uid);
|
||||
$uid = $uid[0];
|
||||
}
|
||||
$users[] = strtolower($uid);
|
||||
}
|
||||
Util::writeLog('OC_USER_SQL', "Return list of results",
|
||||
Util::DEBUG);
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user exists
|
||||
* @param string $uid the username
|
||||
* @return boolean
|
||||
*/
|
||||
public function userExists($uid)
|
||||
{
|
||||
|
||||
$cacheKey = 'sql_user_exists_' . $uid;
|
||||
$cacheVal = $this -> getCache ($cacheKey);
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"userExists() for UID: $uid cacheVal: $cacheVal",
|
||||
Util::DEBUG);
|
||||
if(!is_null($cacheVal))
|
||||
return (bool)$cacheVal;
|
||||
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Entering userExists() for UID: $uid",
|
||||
Util::DEBUG);
|
||||
|
||||
// Only if the domain is removed for internal user handling,
|
||||
// we should add the domain back when checking existance
|
||||
if($this -> settings['set_strip_domain'] === 'true')
|
||||
{
|
||||
$uid = $this -> doUserDomainMapping($uid);
|
||||
}
|
||||
|
||||
$exists = (bool)$this -> helper -> runQuery('userExists',
|
||||
array('uid' => $uid));;
|
||||
$this -> setCache ($cacheKey, $exists, 60);
|
||||
|
||||
if(!$exists)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Empty row, user does not exists, return false",
|
||||
Util::DEBUG);
|
||||
return false;
|
||||
} else
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL', "User exists, return true",
|
||||
Util::DEBUG);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Entering getDisplayName() for UID: $uid",
|
||||
Util::DEBUG);
|
||||
|
||||
$this -> doEmailSync($uid);
|
||||
$uid = $this -> doUserDomainMapping($uid);
|
||||
|
||||
if(!$this -> userExists($uid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$row = $this -> helper -> runQuery('getDisplayName',
|
||||
array('uid' => $uid));
|
||||
|
||||
if(!$row)
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"Empty row, user has no display name or ".
|
||||
"does not exist, return false",
|
||||
Util::DEBUG);
|
||||
return false;
|
||||
} else
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"User exists, return true",
|
||||
Util::DEBUG);
|
||||
$displayName = $row[$this -> settings['col_displayname']];
|
||||
return $displayName; ;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getDisplayNames($search = '', $limit = null, $offset = null)
|
||||
{
|
||||
$uids = $this -> getUsers($search, $limit, $offset);
|
||||
$displayNames = array();
|
||||
foreach($uids as $uid)
|
||||
{
|
||||
$displayNames[$uid] = $this -> getDisplayName($uid);
|
||||
}
|
||||
return $displayNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the backend name
|
||||
* @return string
|
||||
*/
|
||||
public function getBackendName()
|
||||
{
|
||||
return 'SQL';
|
||||
}
|
||||
|
||||
/**
|
||||
* The following functions were directly taken from PostfixAdmin and just
|
||||
* slightly modified
|
||||
* to suit our needs.
|
||||
* Encrypt a password,using the apparopriate hashing mechanism as defined in
|
||||
* config.inc.php ($this->crypt_type).
|
||||
* When wanting to compare one pw to another, it's necessary to provide the
|
||||
* salt used - hence
|
||||
* the second parameter ($pw_db), which is the existing hash from the DB.
|
||||
*
|
||||
* @param string $pw cleartext password
|
||||
* @param string $pw_db encrypted password from database
|
||||
* @return string encrypted password.
|
||||
*/
|
||||
private function pacrypt($pw, $pw_db = "")
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL', "Entering private pacrypt()",
|
||||
Util::DEBUG);
|
||||
$pw = stripslashes($pw);
|
||||
$password = "";
|
||||
$salt = "";
|
||||
|
||||
if($this -> settings['set_crypt_type'] === 'md5crypt')
|
||||
{
|
||||
$split_salt = preg_split('/\$/', $pw_db);
|
||||
if(isset($split_salt[2]))
|
||||
{
|
||||
$salt = $split_salt[2];
|
||||
}
|
||||
$password = $this -> md5crypt($pw, $salt);
|
||||
} elseif($this -> settings['set_crypt_type'] === 'md5')
|
||||
{
|
||||
$password = md5($pw);
|
||||
} elseif($this -> settings['set_crypt_type'] === 'system')
|
||||
{
|
||||
// We never generate salts, as user creation is not allowed here
|
||||
$password = crypt($pw, $pw_db);
|
||||
} elseif($this -> settings['set_crypt_type'] === 'cleartext')
|
||||
{
|
||||
$password = $pw;
|
||||
}
|
||||
|
||||
// See
|
||||
// https://sourceforge.net/tracker/?func=detail&atid=937966&aid=1793352&group_id=191583
|
||||
// this is apparently useful for pam_mysql etc.
|
||||
elseif($this -> settings['set_crypt_type'] === 'mysql_encrypt')
|
||||
{
|
||||
if($pw_db !== "")
|
||||
{
|
||||
$salt = substr($pw_db, 0, 2);
|
||||
|
||||
$row = $this -> helper -> runQuery('mysqlEncryptSalt',
|
||||
array('pw' => $pw, 'salt' => $salt));
|
||||
} else
|
||||
{
|
||||
$row = $this -> helper -> runQuery('mysqlEncrypt',
|
||||
array('pw' => $pw));
|
||||
}
|
||||
|
||||
if($row === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$password = $row[0];
|
||||
} elseif($this -> settings['set_crypt_type'] === 'mysql_password')
|
||||
{
|
||||
$row = $this -> helper -> runQuery('mysqlPassword',
|
||||
array('pw' => $pw));
|
||||
|
||||
if($row === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$password = $row[0];
|
||||
}
|
||||
|
||||
// The following is by Frédéric France
|
||||
elseif($this -> settings['set_crypt_type'] === 'joomla')
|
||||
{
|
||||
$split_salt = preg_split('/:/', $pw_db);
|
||||
if(isset($split_salt[1]))
|
||||
{
|
||||
$salt = $split_salt[1];
|
||||
}
|
||||
$password = ($salt) ? md5($pw . $salt) : md5($pw);
|
||||
$password .= ':' . $salt;
|
||||
}
|
||||
|
||||
elseif($this-> settings['set_crypt_type'] === 'ssha256')
|
||||
{
|
||||
$salted_password = base64_decode(
|
||||
preg_replace('/{SSHA256}/i','',$pw_db));
|
||||
$salt = substr($salted_password,-(strlen($salted_password)-32));
|
||||
$password = $this->ssha256($pw,$salt);
|
||||
} else
|
||||
{
|
||||
Util::writeLog('OC_USER_SQL',
|
||||
"unknown/invalid crypt_type settings: ".
|
||||
$this->settings['set_crypt_type'],
|
||||
\OCP\Util::ERROR);
|
||||
die('unknown/invalid Encryption type setting: ' .
|
||||
$this -> settings['set_crypt_type']);
|
||||
}
|
||||
Util::writeLog('OC_USER_SQL', "pacrypt() done, return",
|
||||
Util::DEBUG);
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = "")
|
||||
{
|
||||
$MAGIC = "$1$";
|
||||
|
||||
if($magic === "")
|
||||
$magic = $MAGIC;
|
||||
if($salt === "")
|
||||
$salt = $this -> create_md5salt();
|
||||
$slist = explode("$", $salt);
|
||||
if($slist[0] === "1")
|
||||
$salt = $slist[1];
|
||||
|
||||
$salt = substr($salt, 0, 8);
|
||||
$ctx = $pw . $magic . $salt;
|
||||
$final = $this -> pahex2bin(md5($pw . $salt . $pw));
|
||||
|
||||
for($i = strlen($pw); $i > 0; $i -= 16)
|
||||
{
|
||||
if($i > 16)
|
||||
{
|
||||
$ctx .= substr($final, 0, 16);
|
||||
} else
|
||||
{
|
||||
$ctx .= substr($final, 0, $i);
|
||||
}
|
||||
}
|
||||
$i = strlen($pw);
|
||||
|
||||
while($i > 0)
|
||||
{
|
||||
if($i & 1)
|
||||
$ctx .= chr(0);
|
||||
else
|
||||
$ctx .= $pw[0];
|
||||
$i = $i>>1;
|
||||
}
|
||||
$final = $this -> pahex2bin(md5($ctx));
|
||||
|
||||
for($i = 0; $i < 1000; $i++)
|
||||
{
|
||||
$ctx1 = "";
|
||||
if($i & 1)
|
||||
{
|
||||
$ctx1 .= $pw;
|
||||
} else
|
||||
{
|
||||
$ctx1 .= substr($final, 0, 16);
|
||||
}
|
||||
if($i % 3)
|
||||
$ctx1 .= $salt;
|
||||
if($i % 7)
|
||||
$ctx1 .= $pw;
|
||||
if($i & 1)
|
||||
{
|
||||
$ctx1 .= substr($final, 0, 16);
|
||||
} else
|
||||
{
|
||||
$ctx1 .= $pw;
|
||||
}
|
||||
$final = $this -> pahex2bin(md5($ctx1));
|
||||
}
|
||||
$passwd = "";
|
||||
$passwd .= $this -> to64(((ord($final[0])<<16) |
|
||||
(ord($final[6])<<8) | (ord($final[12]))), 4);
|
||||
$passwd .= $this -> to64(((ord($final[1])<<16) |
|
||||
(ord($final[7])<<8) | (ord($final[13]))), 4);
|
||||
$passwd .= $this -> to64(((ord($final[2])<<16) |
|
||||
(ord($final[8])<<8) | (ord($final[14]))), 4);
|
||||
$passwd .= $this -> to64(((ord($final[3])<<16) |
|
||||
(ord($final[9])<<8) | (ord($final[15]))), 4);
|
||||
$passwd .= $this -> to64(((ord($final[4])<<16) |
|
||||
(ord($final[10])<<8) | (ord($final[5]))), 4);
|
||||
$passwd .= $this -> to64(ord($final[11]), 2);
|
||||
return "$magic$salt\$$passwd";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new salte
|
||||
* @return string The salt
|
||||
*/
|
||||
private function create_md5salt()
|
||||
{
|
||||
srand((double) microtime() * 1000000);
|
||||
$salt = substr(md5(rand(0, 9999999)), 0, 8);
|
||||
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'))
|
||||
{
|
||||
return hex2bin($str);
|
||||
} else
|
||||
{
|
||||
$len = strlen($str);
|
||||
$nstr = "";
|
||||
for($i = 0; $i < $len; $i += 2)
|
||||
{
|
||||
$num = sscanf(substr($str, $i, 2), "%x");
|
||||
$nstr .= chr($num[0]);
|
||||
}
|
||||
return $nstr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to 64?
|
||||
*/
|
||||
private function to64($v, $n)
|
||||
{
|
||||
$ITOA64 =
|
||||
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
$ret = "";
|
||||
while(($n - 1) >= 0)
|
||||
{
|
||||
$n--;
|
||||
$ret .= $ITOA64[$v & 0x3f];
|
||||
$v = $v>>6;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a value in memcache or the session, if no memcache is available
|
||||
* @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)
|
||||
{
|
||||
if ($this -> cache === NULL)
|
||||
{
|
||||
$_SESSION[$this -> session_cache_name][$key] = array(
|
||||
'value' => $value,
|
||||
'time' => time(),
|
||||
'ttl' => $ttl,
|
||||
);
|
||||
} else
|
||||
{
|
||||
$this -> cache -> set($key,$value,$ttl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a value from memcache or session, if memcache is not available.
|
||||
* Returns NULL if there's no value stored or the value expired.
|
||||
* @param string $key
|
||||
* @return mixed|NULL
|
||||
*/
|
||||
private function getCache($key)
|
||||
{
|
||||
$retVal = NULL;
|
||||
if ($this -> cache === NULL)
|
||||
{
|
||||
if (isset($_SESSION[$this -> session_cache_name],
|
||||
$_SESSION[$this -> session_cache_name][$key]))
|
||||
{
|
||||
$value = $_SESSION[$this -> session_cache_name][$key];
|
||||
if (time() < $value['time'] + $value['ttl'])
|
||||
{
|
||||
$retVal = $value['value'];
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
$retVal = $this -> cache -> get ($key);
|
||||
}
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
private function create_systemsalt($length=20)
|
||||
{
|
||||
$fp = fopen('/dev/urandom', 'r');
|
||||
$randomString = fread($fp, $length);
|
||||
fclose($fp);
|
||||
$salt = base64_encode($randomString);
|
||||
return $salt;
|
||||
}
|
||||
|
||||
private function pw_hash($password)
|
||||
{
|
||||
$options = [
|
||||
'cost' => 10,
|
||||
];
|
||||
return password_hash($password, PASSWORD_BCRYPT, $options);
|
||||
|
||||
}
|
||||
|
||||
function hash_equals( $a, $b ) {
|
||||
$a_length = strlen( $a );
|
||||
|
||||
if ( $a_length !== strlen( $b ) ) {
|
||||
return false;
|
||||
}
|
||||
$result = 0;
|
||||
|
||||
// Do not attempt to "optimize" this.
|
||||
for ( $i = 0; $i < $a_length; $i++ ) {
|
||||
$result |= ord( $a[ $i ] ) ^ ord( $b[ $i ] );
|
||||
}
|
||||
|
||||
//Hide the length of the string
|
||||
$additional_length=200-($a_length % 200);
|
||||
$tmp=0;
|
||||
$c="abCD";
|
||||
for ( $i = 0; $i < $additional_length; $i++ ) {
|
||||
$tmp |= ord( $c[ 0 ] ) ^ ord( $c[ 0 ] );
|
||||
}
|
||||
|
||||
return $result === 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user