Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/user_sql.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hg_archival.txt4
-rw-r--r--.hgignore2
-rw-r--r--.hgtags14
-rw-r--r--PasswordHash.php253
-rw-r--r--README.md19
-rw-r--r--ajax/settings.php260
-rw-r--r--appinfo/app.php31
-rw-r--r--appinfo/info.xml20
-rw-r--r--appinfo/routes.php8
-rw-r--r--appinfo/update.php71
-rw-r--r--css/settings.css24
-rw-r--r--js/settings.js314
-rw-r--r--lib/helper.php348
-rw-r--r--settings.php52
-rw-r--r--templates/settings.php168
-rw-r--r--user_sql.php782
16 files changed, 2370 insertions, 0 deletions
diff --git a/.hg_archival.txt b/.hg_archival.txt
new file mode 100644
index 0000000..8255758
--- /dev/null
+++ b/.hg_archival.txt
@@ -0,0 +1,4 @@
+repo: 3f045a88dd7014290a9bf60cbd740c8548dba967
+node: 77b8809852f4a8599200c9a538f94319ca5a4ff4
+branch: default
+tag: v.2.3.1
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..4f0682f
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,2 @@
+.project
+.settings
diff --git a/.hgtags b/.hgtags
new file mode 100644
index 0000000..a55e874
--- /dev/null
+++ b/.hgtags
@@ -0,0 +1,14 @@
+8401b998f40be1e93d05b5c4db61ba98932c3de3 v0.2
+c74b035788ebbcade6bfae22963632f4c5f49540 v0.3
+5ee6481afb63f32dcfeaf93a72c07c4bc2a1a993 v0.4
+5ee6481afb63f32dcfeaf93a72c07c4bc2a1a993 v0.4
+f9eb8e6e4b4f2bcb521554bc513cc94d9ccf6fbd v0.4
+df3dc7358b79abdd7075ba78b58fff1da7fa3fde to
+df3dc7358b79abdd7075ba78b58fff1da7fa3fde 0.5
+df3dc7358b79abdd7075ba78b58fff1da7fa3fde to
+0000000000000000000000000000000000000000 to
+df3dc7358b79abdd7075ba78b58fff1da7fa3fde 0.5
+0000000000000000000000000000000000000000 0.5
+845b75d72ec18f3fbf2a74f1a1ec3cf54ca4a304 v0.7
+599b774a4b6ac81b44971ac1a319fc41c04e3589 v2.3
+1fedd27cb5d8c69a998c8278463c8e8abd1f371a v2.3.0
diff --git a/PasswordHash.php b/PasswordHash.php
new file mode 100644
index 0000000..474b2e3
--- /dev/null
+++ b/PasswordHash.php
@@ -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 PasswordHash($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;
+ }
+}
+
+?>
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c6cf065
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+user_sql
+========
+
+Owncloud SQL authentification
+
+This is plugin is heavily based on user_imap, user_pwauth, user_ldap and user_redmine!
+
+Enable it in your Admin -> Apps section and configure your server's details.
+Currently, it supports most of postfixadmin's encryption options, except dovecot and saslauthd.
+It was tested and developed for a postfixadmin database.
+
+Password changing is disabled by default, but can be enabled in the Admin area.
+Caution: user_sql does not recreate password salts, which imposes a security risk.
+Password salts should be newly generated whenever the password changes.
+
+Credits
+
+ * Johan Hendriks provided his user_postfixadmin
+ * Ed Wildgoose for fixing possible SQL injection vulnerability
diff --git a/ajax/settings.php b/ajax/settings.php
new file mode 100644
index 0000000..28ecce3
--- /dev/null
+++ b/ajax/settings.php
@@ -0,0 +1,260 @@
+<?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
+
+// Check if we are a user
+\OCP\User::checkAdminUser();
+\OCP\JSON::checkAppEnabled('user_sql');
+
+// CSRF checks
+\OCP\JSON::callCheck();
+
+
+$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':
+ $parameters = array('host' => $_POST['sql_hostname'],
+ 'password' => $_POST['sql_password'],
+ 'user' => $_POST['sql_username'],
+ 'dbname' => $_POST['sql_database'],
+ 'tablePrefix' => ''
+ );
+
+ // Check if the table exists
+ if(!$helper->verifyTable($parameters, $_POST['sql_driver'], $_POST['sql_table']))
+ {
+ $response->setData(array('status' => 'error',
+ 'data' => array('message' => $l -> t('The selected SQL table '.$_POST['sql_table'].' does not exist!'))));
+ break;
+ }
+
+ // Retrieve all column settings
+ $columns = array();
+ foreach($params as $param)
+ {
+ if(strpos($param, 'col_') === 0)
+ {
+ if(isset($_POST[$param]) && $_POST[$param] !== '')
+ $columns[] = $_POST[$param];
+ }
+ }
+
+ // Check if the columns exist
+ $status = $helper->verifyColumns($parameters, $_POST['sql_driver'], $_POST['sql_table'], $columns);
+ if($status !== true)
+ {
+ $response->setData(array('status' => 'error',
+ 'data' => array('message' => $l -> t('The selected SQL column(s) do(es) not exist: '.$status))));
+ break;
+ }
+
+ // If we reach this point, all settings have been verified
+ foreach($params as $param)
+ {
+ // Special handling for checkbox fields
+ if(isset($_POST[$param]))
+ {
+ if($param === 'set_strip_domain')
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', 'set_strip_domain_'.$domain, 'true');
+ }
+ elseif($param === 'set_allow_pwchange')
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', 'set_allow_pwchange_'.$domain, 'true');
+ }
+ elseif($param === 'set_active_invert')
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', 'set_active_invert_'.$domain, 'true');
+ }
+ elseif($param === 'set_enable_gethome')
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', 'set_enable_gethome_'.$domain, 'true');
+ }
+ else
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', $param.'_'.$domain, $_POST[$param]);
+ }
+ } else
+ {
+ if($param === 'set_strip_domain')
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', 'set_strip_domain_'.$domain, 'false');
+ }
+ elseif($param === 'set_allow_pwchange')
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', 'set_allow_pwchange_'.$domain, 'false');
+ }
+ elseif($param === 'set_active_invert')
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', 'set_active_invert_'.$domain, 'false');
+ }
+ elseif($param === 'set_enable_gethome')
+ {
+ \OC::$server->getConfig()->setAppValue('user_sql', 'set_enable_gethome_'.$domain, 'false');
+ }
+ }
+ }
+ $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, '');
+ }
+ $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':
+
+
+ $parameters = array('host' => $_POST['sql_hostname'],
+ 'password' => $_POST['sql_password'],
+ 'user' => $_POST['sql_username'],
+ 'dbname' => $_POST['sql_database'],
+ 'tablePrefix' => ''
+ );
+
+ if($helper->verifyTable($parameters, $_POST['sql_driver'], $_POST['sql_table']))
+ $columns = $helper->getColumns($parameters, $_POST['sql_driver'], $_POST['sql_table']);
+ else
+ $columns = array();
+
+ $search = $_POST['request'];
+ $ret = array();
+
+ foreach($columns as $name)
+ {
+ if(($search === '') || ($search === 'search') || (strpos($name, $search) === 0))
+ {
+ $ret[] = array('label' => $name,
+ 'value' => $name);
+ }
+ }
+ $response -> setData($ret);
+ break;
+
+ // Get the autocompletion values for a table
+ case 'getTableAutocomplete':
+ $parameters = array('host' => $_POST['sql_hostname'],
+ 'password' => $_POST['sql_password'],
+ 'user' => $_POST['sql_username'],
+ 'dbname' => $_POST['sql_database'],
+ 'tablePrefix' => ''
+ );
+
+ $tables = $helper->getTables($parameters, $_POST['sql_driver']);
+
+ $search = $_POST['request'];
+ $ret = array();
+ foreach($tables as $name)
+ {
+ if(($search === '') || ($search === 'search') || (strpos($name, $search) === 0))
+ {
+ $ret[] = array('label' => $name,
+ 'value' => $name);
+ }
+ }
+ $response -> setData($ret);
+ break;
+ }
+
+} else
+{
+ // 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.'))));
+}
+
+// Return the JSON array
+echo $response->render();
diff --git a/appinfo/app.php b/appinfo/app.php
new file mode 100644
index 0000000..b21c7ea
--- /dev/null
+++ b/appinfo/app.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+* ownCloud - user_sql
+*
+* @author Andreas Böhler
+* @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/>.
+*
+*/
+
+require_once('apps/user_sql/user_sql.php');
+\OCP\App::registerAdmin('user_sql','settings');
+
+$backend = new \OCA\user_sql\OC_USER_SQL;
+
+
+// register user backend
+\OC_User::useBackend($backend);
diff --git a/appinfo/info.xml b/appinfo/info.xml
new file mode 100644
index 0000000..fd74bdd
--- /dev/null
+++ b/appinfo/info.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<info>
+ <id>user_sql</id>
+ <name>SQL user backend</name>
+ <summary>Authenticate Users by SQL</summary>
+ <description>Authenticate Users by SQL</description>
+ <version>2.3.1</version>
+ <licence>agpl</licence>
+ <author>Andreas Boehler &lt;dev (at) aboehler
+(dot) at &gt;</author>
+ <namespace>user_sql</namespace>
+ <types>
+ <authentication/>
+ </types>
+ <category>auth</category>
+ <dependencies>
+ <owncloud min-version="8.1" max-version="9.1"/>
+ <nextcloud min-version="9" max-version="10"/>
+ </dependencies>
+</info>
diff --git a/appinfo/routes.php b/appinfo/routes.php
new file mode 100644
index 0000000..afb4fea
--- /dev/null
+++ b/appinfo/routes.php
@@ -0,0 +1,8 @@
+<?php
+/**
+* Copyright (c) 2015, Andreas Böhler <dev@aboehler.at>
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file.
+*/
+/** @var $this \OCP\Route\IRouter */
+$this->create('user_sql_ajax_settings', 'ajax/settings.php')->actionInclude('user_sql/ajax/settings.php');
diff --git a/appinfo/update.php b/appinfo/update.php
new file mode 100644
index 0000000..6312a16
--- /dev/null
+++ b/appinfo/update.php
@@ -0,0 +1,71 @@
+<?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/>.
+ *
+ */
+
+$installedVersion = \OC::$server->getConfig()->getAppValue('user_sql', 'installed_version');
+
+$params = array('sql_host' => 'sql_hostname',
+ 'sql_user' => 'sql_username',
+ 'sql_database' => 'sql_database',
+ 'sql_password' => 'sql_password',
+ 'sql_table' => 'sql_table',
+ 'sql_column_username' => 'col_username',
+ 'sql_column_password' => 'col_password',
+ 'sql_type' => 'sql_driver',
+ 'sql_column_active' => 'col_active',
+ 'strip_domain' => 'set_strip_domain',
+ 'default_domain' => 'set_default_domain',
+ 'crypt_type' => 'set_crypt_type',
+ 'sql_column_displayname' => 'col_displayname',
+ 'allow_password_change' => 'set_allow_pwchange',
+ 'sql_column_active_invert' => 'set_active_invert',
+ 'sql_column_email' => 'col_email',
+ 'mail_sync_mode' => 'set_mail_sync_mode'
+ );
+
+$delParams = array('domain_settings',
+ 'map_array',
+ 'domain_array'
+ );
+
+if(version_compare($installedVersion, '1.99', '<'))
+{
+ foreach($params as $oldPar => $newPar)
+ {
+ $val = \OC::$server->getConfig()->getAppValue('user_sql', $oldPar);
+ if(($oldPar === 'strip_domain') || ($oldPar === 'allow_password_change') || ($oldPar === 'sql_column_active_invert'))
+ {
+ if($val)
+ $val = 'true';
+ else
+ $val = 'false';
+ }
+ if($val)
+ \OC::$server->getConfig()->setAppValue('user_sql', $newPar.'_default', $val);
+ \OC::$server->getConfig()->deleteAppValue('user_sql', $oldPar);
+ }
+
+ foreach($delParams as $param)
+ {
+ \OC::$server->getConfig()->deleteAppValue('user_sql', $param);
+ }
+}
diff --git a/css/settings.css b/css/settings.css
new file mode 100644
index 0000000..a899217
--- /dev/null
+++ b/css/settings.css
@@ -0,0 +1,24 @@
+.statusmessage {
+ background-color: #DDDDFF;
+}
+.errormessage {
+ background-color: #FFDDDD;
+}
+.successmessage {
+ background-color: #DDFFDD;
+}
+.statusmessage,.errormessage,.successmessage{
+ display:none;
+ padding: 1px;
+}
+
+#sql-1 p label:first-child,
+#sql-2 p label:first-child,
+#sql-3 p label:first-child,
+#sql-4 p label:first-child,
+#sql-5 p label:first-child {
+ display: inline-block;
+ text-align: right;
+ width: 300px;
+ padding-right: 10px;
+}
diff --git a/js/settings.js b/js/settings.js
new file mode 100644
index 0000000..a77bffb
--- /dev/null
+++ b/js/settings.js
@@ -0,0 +1,314 @@
+// settings.js of user_sql
+
+// declare namespace
+var user_sql = user_sql ||
+{
+};
+
+/**
+ * init admin settings view
+ */
+user_sql.adminSettingsUI = function()
+{
+
+ if($('#sqlDiv').length > 0)
+ {
+ // enable tabs on settings page
+ $('#sqlDiv').tabs();
+
+ // Attach auto-completion to all column fields
+ $('#col_username, #col_password, #col_displayname, #col_active, #col_email, #col_gethome').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 post = $('#sqlForm').serializeArray();
+ var domain = $('#sql_domain_chooser option:selected').val();
+
+ post.push({
+ name: 'function',
+ value: 'saveSettings'
+ });
+
+ post.push({
+ name: 'domain',
+ value: domain
+ });
+
+ $('#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)
+ {
+ $('#sql_update_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;
+ });
+
+ // Attach event handler to the domain chooser
+ $('#sql_domain_chooser').change(function() {
+ user_sql.loadDomainSettings($('#sql_domain_chooser option:selected').val());
+ });
+
+ $('#set_gethome_mode').change(function() {
+ user_sql.setGethomeMode();
+ });
+
+ $('#set_enable_gethome').change(function() {
+ user_sql.setGethomeMode();
+ });
+ }
+};
+
+user_sql.setGethomeMode = function()
+{
+ var enabled = $('#set_enable_gethome').prop('checked');
+ if(enabled)
+ {
+ $('#set_gethome_mode').prop('disabled', false);
+ var val = $('#set_gethome_mode option:selected').val();
+ if(val === 'query')
+ {
+ $('#set_gethome').prop('disabled', true);
+ $('#col_gethome').prop('disabled', false);
+ }
+ else if(val === 'static')
+ {
+ $('#set_gethome').prop('disabled', false);
+ $('#col_gethome').prop('disabled', true);
+ }
+ else
+ {
+ $('#set_gethome').prop('disabled', true);
+ $('#col_gethome').prop('disabled', true);
+ }
+ }
+ else
+ {
+ $('#set_gethome_mode').prop('disabled', true);
+ $('#set_gethome').prop('disabled', true);
+ $('#col_gethome').prop('disabled', true);
+ }
+};
+
+/**
+ * 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 = [
+ {
+ name: 'appname',
+ value: 'user_sql'
+ },
+ {
+ name: 'function',
+ value: 'loadSettingsForDomain'
+ },
+ {
+ name: 'domain',
+ value: domain
+ }
+ ];
+ $.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, function(data)
+ {
+ $('#sql_loading_message').hide();
+ if(data.status == 'success')
+ {
+ for(key in data.settings)
+ {
+ if(key == 'set_strip_domain')
+ {
+ if(data.settings[key] == 'true')
+ $('#' + key).prop('checked', true);
+ else
+ $('#' + key).prop('checked', false);
+ }
+ else if(key == 'set_allow_pwchange')
+ {
+ if(data.settings[key] == 'true')
+ $('#' + key).prop('checked', true);
+ else
+ $('#' + key).prop('checked', false);
+ }
+ else if(key == 'set_active_invert')
+ {
+ if(data.settings[key] == 'true')
+ $('#' + key).prop('checked', true);
+ else
+ $('#' + key).prop('checked', false);
+ }
+ else if(key == 'set_enable_gethome')
+ {
+ if(data.settings[key] == 'true')
+ $('#' + key).prop('checked', true);
+ else
+ $('#' + key).prop('checked', false);
+ }
+ else
+ {
+ $('#' + key).val(data.settings[key]);
+ }
+ }
+ }
+ else
+ {
+ $('#sql_error_message').html(data.data.message);
+ $('#sql_error_message').show();
+ }
+ user_sql.setGethomeMode();
+ }, 'json'
+ );
+};
+
+// Run our JS if the SQL settings are present
+$(document).ready(function()
+{
+ if($('#sqlDiv'))
+ {
+ user_sql.adminSettingsUI();
+ user_sql.loadDomainSettings($('#sql_domain_chooser option:selected').val());
+ user_sql.setGethomeMode();
+ }
+});
+
diff --git a/lib/helper.php b/lib/helper.php
new file mode 100644
index 0000000..8964ef9
--- /dev/null
+++ b/lib/helper.php
@@ -0,0 +1,348 @@
+<?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/>.
+ *
+ */
+
+namespace OCA\user_sql\lib;
+
+class Helper {
+
+ protected $db;
+ 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(
+ 'sql_hostname',
+ 'sql_username',
+ 'sql_password',
+ 'sql_database',
+ 'sql_table',
+ 'sql_driver',
+ 'col_username',
+ 'col_password',
+ 'col_active',
+ 'col_displayname',
+ 'col_email',
+ 'col_gethome',
+ 'set_active_invert',
+ 'set_allow_pwchange',
+ 'set_default_domain',
+ 'set_strip_domain',
+ 'set_crypt_type',
+ 'set_mail_sync_mode',
+ 'set_enable_gethome',
+ 'set_gethome_mode',
+ 'set_gethome'
+ );
+
+ 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);
+ $settings = array();
+ $sql_host = \OC::$server->getConfig()->getAppValue('user_sql', 'sql_hostname_'.$domain, '');
+ if($sql_host === '')
+ {
+ $domain = 'default';
+ }
+ $params = $this -> getParameterArray();
+ foreach($params as $param)
+ {
+ $settings[$param] = \OC::$server->getConfig()->getAppValue('user_sql', $param.'_'.$domain, '');
+ }
+ \OCP\Util::writeLog('OC_USER_SQL', "Loaded settings for domain: " . $domain, \OCP\Util::DEBUG);
+ 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);
+ if(!$this -> db_conn)
+ return false;
+
+ switch($type)
+ {
+ case 'getHome':
+ $query = "SELECT ".$this->settings['col_gethome']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid";
+ break;
+ case 'getMail':
+ $query = "SELECT ".$this->settings['col_email']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid";
+ break;
+
+ case 'setMail':
+ $query = "UPDATE ".$this->settings['sql_table']." SET ".$this->settings['col_email']." = :currMail WHERE ".$this->settings['col_username']." = :uid";
+ break;
+
+ case 'getPass':
+ $query = "SELECT ".$this->settings['col_password']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid";
+ if($this -> settings['col_active'] !== '')
+ $query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active'];
+ break;
+
+ case 'setPass':
+ $query = "UPDATE ".$this->settings['sql_table']." SET ".$this->settings['col_password']." = :enc_password WHERE ".$this->settings['col_username'] ." = :uid";
+ break;
+
+ case 'getRedmineSalt':
+ $query = "SELECT salt FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username'] ." = :uid;";
+ break;
+
+ case 'countUsers':
+ $query = "SELECT COUNT(*) FROM ".$this->settings['sql_table']." 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'];
+ break;
+
+ case 'getUsers':
+ $query = "SELECT ".$this->settings['col_username']." FROM ".$this->settings['sql_table'];
+ $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'];
+ break;
+
+ case 'userExists':
+ $query = "SELECT ".$this->settings['col_username']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid";
+ if($this -> settings['col_active'] !== '')
+ $query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active'];
+ break;
+
+ case 'getDisplayName':
+ $query = "SELECT ".$this->settings['col_displayname']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid";
+ if($this -> settings['col_active'] !== '')
+ $query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active'];
+ break;
+
+ case 'mysqlEncryptSalt':
+ $query = "SELECT ENCRYPT(:pw, :salt);";
+ break;
+
+ case 'mysqlEncrypt':
+ $query = "SELECT ENCRYPT(:pw);";
+ break;
+
+ case 'mysqlPassword':
+ $query = "SELECT PASSWORD(:pw);";
+ break;
+ }
+
+ if(isset($limits['limit']) && $limits['limit'] !== null)
+ {
+ $limit = intval($limits['limit']);
+ $query .= " LIMIT ".$limit;
+ }
+
+ if(isset($limits['offset']) && $limits['offset'] !== null)
+ {
+ $offset = intval($limits['offset']);
+ $query .= " OFFSET ".$offset;
+ }
+
+ \OCP\Util::writeLog('OC_USER_SQL', "Preparing query: $query", \OCP\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);
+ if(!$result -> execute())
+ {
+ $err = $result -> errorInfo();
+ \OCP\Util::writeLog('OC_USER_SQL', "Query failed: " . $err[2], \OCP\Util::DEBUG);
+ return false;
+ }
+ if($execOnly === true)
+ {
+ return true;
+ }
+ \OCP\Util::writeLog('OC_USER_SQL', "Fetching result...", \OCP\Util::DEBUG);
+ if($fetchArray === true)
+ $row = $result -> fetchAll();
+ else
+ $row = $result -> fetch();
+
+ if(!$row)
+ {
+ return false;
+ }
+ 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;
+ $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 = $cm -> getConnection($this -> settings['sql_driver'], $parameters);
+ $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);
+ $this -> db_conn = false;
+ return false;
+ }
+ }
+
+ /**
+ * Check if all of the given columns exist
+ * @param array $parameters The connection parameters
+ * @param string $sql_driver The SQL driver to use
+ * @param string $table The table name to check
+ * @param array $cols The columns to check
+ * @param array True if found, otherwise false
+ */
+ public function verifyColumns($parameters, $sql_driver, $table, $cols)
+ {
+ $columns = $this->getColumns($parameters, $sql_driver, $table);
+ $res = true;
+ $err = '';
+ foreach($cols as $col)
+ {
+ if(!in_array($col, $columns, true))
+ {
+ $res = false;
+ $err .= $col.' ';
+ }
+ }
+ if($res)
+ return true;
+ else
+ return $err;
+ }
+
+ /**
+ * Check if a given table exists
+ * @param array $parameters The connection parameters
+ * @param string $sql_driver The SQL driver to use
+ * @param string $table The table name to check
+ * @param array True if found, otherwise false
+ */
+ public function verifyTable($parameters, $sql_driver, $table)
+ {
+ $tables = $this->getTables($parameters, $sql_driver);
+ return in_array($table, $tables, true);
+ }
+
+ /**
+ * Retrieve a list of tables for the given connection parameters
+ * @param array $parameters The connection parameters
+ * @param string $sql_driver The SQL driver to use
+ * @return array The found tables, empty if an error occured
+ */
+ public function getTables($parameters, $sql_driver)
+ {
+ $cm = new \OC\DB\ConnectionFactory();
+ try {
+ $conn = $cm -> getConnection($sql_driver, $parameters);
+ $platform = $conn -> getDatabasePlatform();
+ $query = $platform -> getListTablesSQL();
+ $result = $conn -> executeQuery($query);
+ $ret = array();
+ while($row = $result -> fetch())
+ {
+ $name = $row['Tables_in_'.$parameters['dbname']];
+ $ret[] = $name;
+ }
+ return $ret;
+ }
+ catch(\Exception $e)
+ {
+ return array();
+ }
+ }
+
+ /**
+ * Retrieve a list of columns for the given connection parameters
+ * @param array $parameters The connection parameters
+ * @param string $sql_driver The SQL driver to use
+ * @param string $table The SQL table to work with
+ * @return array The found column, empty if an error occured
+ */
+ public function getColumns($parameters, $sql_driver, $table)
+ {
+ $cm = new \OC\DB\ConnectionFactory();
+ try {
+ $conn = $cm -> getConnection($sql_driver, $parameters);
+ $platform = $conn -> getDatabasePlatform();
+ $query = $platform -> getListTableColumnsSQL($table);
+ $result = $conn -> executeQuery($query);
+ $ret = array();
+ while($row = $result -> fetch())
+ {
+ $name = $row['Field'];
+ $ret[] = $name;
+ }
+ return $ret;
+ }
+ catch(\Exception $e)
+ {
+ return array();
+ }
+ }
+
+
+}
diff --git a/settings.php b/settings.php
new file mode 100644
index 0000000..d62d81a
--- /dev/null
+++ b/settings.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * ownCloud - user_sql
+ *
+ * @author Andreas Böhler
+ * @copyright 2012 Andreas Böhler <andreas (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/>.
+ *
+ */
+
+namespace OCA\user_sql;
+
+use OCA\user_sql\lib\Helper;
+
+$helper = new \OCA\user_sql\lib\Helper();
+$params = $helper -> getParameterArray();
+$settings = $helper -> loadSettingsForDomain('default');
+
+\OCP\Util::addStyle('user_sql', 'settings');
+\OCP\Util::addScript('user_sql', 'settings');
+\OCP\User::checkAdminUser();
+
+// fill template
+$tmpl = new \OCP\Template('user_sql', 'settings');
+foreach($params as $param)
+{
+ $value = htmlentities($settings[$param]);
+ $tmpl -> assign($param, $value);
+}
+
+$trusted_domains = \OC::$server->getConfig()->getSystemValue('trusted_domains');
+$inserted = array('default');
+array_splice($trusted_domains, 0, 0, $inserted);
+$tmpl -> assign('allowed_domains', array_unique($trusted_domains));
+// workaround to detect OC version
+$ocVersion = @reset(\OCP\Util::getVersion());
+$tmpl -> assign('ocVersion', $ocVersion);
+
+return $tmpl -> fetchPage();
diff --git a/templates/settings.php b/templates/settings.php
new file mode 100644
index 0000000..c555c5c
--- /dev/null
+++ b/templates/settings.php
@@ -0,0 +1,168 @@
+<?php $ocVersion = $_['ocVersion'];
+$cfgClass = $ocVersion >= 7 ? 'section' : 'personalblock';
+?>
+
+<div class="<?php p($cfgClass); ?>">
+ <h2><?php p($l->t('SQL User Backend')); ?></h2>
+
+<form id="sqlForm" action="#" method="post" class="<?php p($cfgClass); ?>">
+
+ <div id="sqlDiv" class="<?php p($cfgClass); ?>">
+ <label for="sql_domain_chooser"><?php p($l -> t('Settings for Domain')) ?></label>
+ <select id="sql_domain_chooser" name="sql_domain_chooser">
+ <?php foreach ($_['allowed_domains'] as $domain): ?>
+ <option value="<?php p($domain); ?>"><?php p($domain); ?></option>
+ <?php endforeach ?>
+ </select>
+ <ul>
+ <li><a id="sqlBasicSettings" href="#sql-1"><?php p($l -> t('Connection Settings')); ?></a></li>
+ <li><a id="sqlColSettings" href="#sql-2"><?php p($l -> t('Column Settings')); ?></a></li>
+ <li><a id="sqlEmailSettings" href="#sql-3"><?php p($l -> t('E-Mail Settings')); ?></a></li>
+ <li><a id="sqlDomainSettings" href="#sql-4"><?php p($l -> t('Domain Settings')); ?></a></li>
+ <li><a id="sqlGethomeSettings" href="#sql-5"><?php p($l -> t('getHome Settings')); ?></a></li>
+ </ul>
+
+ <fieldset id="sql-1">
+ <p><label for="sql_driver"><?php p($l -> t('SQL Driver')); ?></label>
+ <?php $db_driver = array('mysql' => 'MySQL', 'pgsql' => 'PostgreSQL'); ?>
+ <select id="sql_driver" name="sql_driver">
+ <?php
+ foreach ($db_driver as $driver => $name):
+ //echo $_['sql_driver'];
+ if($_['sql_driver'] === $driver): ?>
+ <option selected="selected" value="<?php p($driver); ?>"><?php p($name); ?></option>
+ <?php else: ?>
+ <option value="<?php p($driver); ?>"><?php p($name); ?></option>
+ <?php endif;
+ endforeach;
+ ?>
+ </select>
+ </p>
+
+ <p><label for="sql_hostname"><?php p($l -> t('Host')); ?></label><input type="text" id="sql_hostname" name="sql_hostname" value="<?php p($_['sql_hostname']); ?>"></p>
+
+ <p><label for="sql_username"><?php p($l -> t('Username')); ?></label><input type="text" id="sql_username" name="sql_username" value="<?php p($_['sql_username']); ?>" /></p>
+
+ <p><label for="sql_database"><?php p($l -> t('Database')); ?></label><input type="text" id="sql_database" name="sql_database" value="<?php p($_['sql_database']); ?>" /></p>
+
+ <p><label for="sql_password"><?php p($l -> t('Password')); ?></label><input type="password" id="sql_password" name="sql_password" value="<?php p($_['sql_password']); ?>" /></p>
+
+ <p><input type="submit" id="sqlVerify" value="<?php p($l -> t('Verify Settings')); ?>"></p>
+
+ </fieldset>
+ <fieldset id="sql-2">
+ <p><label for="sql_table"><?php p($l -> t('Table')); ?></label><input type="text" id="sql_table" name="sql_table" value="<?php p($_['sql_table']); ?>" /></p>
+
+ <p><label for="col_username"><?php p($l -> t('Username Column')); ?></label><input type="text" id="col_username" name="col_username" value="<?php p($_['col_username']); ?>" /></p>
+
+ <p><label for="col_password"><?php p($l -> t('Password Column')); ?></label><input type="text" id="col_password" name="col_password" value="<?php p($_['col_password']); ?>" /></p>
+
+ <p><label for="set_allow_pwchange"><?php p($l -> t('Allow password changing (read README!)')); ?></label><input type="checkbox" id="set_allow_pwchange" name="set_allow_pwchange" value="1"<?php
+ if($_['set_allow_pwchange'])
+ p(' checked');
+ ?>><br>
+ <em><?php p($l -> t('Allow changing passwords. Imposes a security risk as password salts are not recreated')); ?></em></p>
+
+ <p><label for="col_displayname"><?php p($l -> t('Real Name Column')); ?></label><input type="text" id="col_displayname" name="col_displayname" value="<?php p($_['col_displayname']); ?>" /></p>
+
+ <p><label for="set_crypt_type"><?php p($l -> t('Encryption Type')); ?></label>
+ <?php $crypt_types = array('md5' => 'MD5', 'md5crypt' => 'MD5 Crypt', 'cleartext' => 'Cleartext', 'mysql_encrypt' => 'mySQL ENCRYPT()', 'system' => 'System (crypt)', 'mysql_password' => 'mySQL PASSWORD()', 'joomla' => 'Joomla MD5 Encryption', 'joomla2' => 'Joomla > 2.5.18 phpass', 'ssha256' => 'Salted SSHA256', 'redmine' => 'Redmine'); ?>
+ <select id="set_crypt_type" name="set_crypt_type">
+ <?php
+ foreach ($crypt_types as $driver => $name):
+ //echo $_['set_crypt_type'];
+ if($_['set_crypt_type'] === $driver): ?>
+ <option selected="selected" value="<?php p($driver); ?>"><?php p($name); ?></option>
+ <?php else: ?>
+ <option value="<?php p($driver); ?>"><?php p($name); ?></option>
+ <?php endif;
+ endforeach;
+ ?>
+ </select>
+ </p>
+
+ <p><label for="col_active"><?php p($l -> t('User Active Column')); ?></label><input type="text" id="col_active" name="col_active" value="<?php p($_['col_active']); ?>" /></p>
+
+ <p><label for="set_active_invert"><?php p($l -> t('Invert Active Value')); ?></label><input type="checkbox" id="set_active_invert" name="set_active_invert" value="1"<?php
+ if($_['set_active_invert'])
+ p(' checked');
+ ?> /><br>
+ <em><?php p($l -> t("Invert the logic of the active column (for blocked users in the SQL DB)")); ?></em></p>
+
+ </fieldset>
+
+ <fieldset id="sql-3">
+
+ <p><label for="col_email"><?php p($l -> t('E-Mail Column')); ?></label><input type="text" id="col_email" name="col_email" value="<?php p($_['col_email']); ?>" /></p>
+
+ <p><label for="set_mail_sync_mode"><?php p($l -> t('E-Mail address sync mode')); ?></label>
+ <?php $mail_modes = array('none' => 'No Synchronisation', 'initial' => 'Synchronise only once', 'forceoc' => 'ownCloud always wins', 'forcesql' => 'SQL always wins'); ?>
+ <select id="set_mail_sync_mode" name="set_mail_sync_mode">
+ <?php
+ foreach ($mail_modes as $mode => $name):
+ //echo $_['set_mail_sync_mode'];
+ if($_['set_mail_sync_mode'] === $mode): ?>
+ <option selected="selected" value="<?php p($mode); ?>"><?php p($name); ?></option>
+ <?php else: ?>
+ <option value="<?php p($mode); ?>"><?php p($name); ?></option>
+ <?php endif;
+ endforeach;
+ ?>
+ </select>
+ </p>
+
+ </fieldset>
+
+ <fieldset id="sql-4">
+
+ <p><label for="set_default_domain"><?php p($l -> t('Append Default Domain')); ?></label><input type="text" id="set_default_domain", name="set_default_domain" value="<?php p($_['set_default_domain']); ?>" /><br>
+ <em><?php p($l -> t('Append this string, e.g. a domain name, to each user name. The @-sign is automatically inserted.')); ?></em>
+ </p>
+
+ <p><label for="set_strip_domain"><?php p($l -> t('Strip Domain Part from Username')); ?></label><input type="checkbox" id="set_strip_domain" name="set_strip_domain" value="1"<?php
+ if($_['set_strip_domain'])
+ p(' checked');
+ ?> /><br>
+ <em><?php p($l -> t("Strip Domain Part including @-sign from Username when logging in and retrieving username lists")); ?></em></p>
+
+ </fieldset>
+
+ <fieldset id="sql-5">
+ <p><label for="set_enable_gethome"><?php p($l -> t('Enable support for getHome()')); ?></label><input type="checkbox" id="set_enable_gethome", name="set_enable_gethome" value="1" <?php
+ if($_['set_enable_gethome'])
+ p(' checked');
+ ?>/></p>
+
+ <p><label for="set_gethome_mode"><?php p($l -> t('Method for getHome')); ?></label>
+ <?php $gethome_modes = array('query' => 'SQL Column', 'static' => 'Static (with Variables)'); ?>
+ <select id="set_gethome_mode" name="set_gethome_mode">
+ <?php
+ foreach ($gethome_modes as $mode => $name):
+ //echo $_['set_mail_sync_mode'];
+ if($_['set_gethome_mode'] === $mode): ?>
+ <option selected="selected" value="<?php p($mode); ?>"><?php p($name); ?></option>
+ <?php else: ?>
+ <option value="<?php p($mode); ?>"><?php p($name); ?></option>
+ <?php endif;
+ endforeach;
+ ?>
+ </select>
+ </p>
+
+ <p><label for="col_gethome"><?php p($l -> t('Home Column')); ?></label><input type="text" id="col_gethome" name="col_gethome" value="<?php p($_['col_gethome']); ?>"></p>
+
+ <p><label for="set_gethome"><?php p($l -> t('Home Dir')); ?></label><input type="text" id="set_gethome" name="set_gethome" value="<?php p($_['set_gethome']); ?>"><br>
+ <em><?php p($l -> t('You can use the placeholders %%u to specify the user ID (before appending the default domain), %%ud to specify the user ID (after appending the default domain) and %%d to specify the default domain')); ?></em></p>
+
+ </fieldset>
+ <input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>" id="requesttoken" />
+ <input type="hidden" name="appname" value="user_sql" />
+ <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>
+</form>
+</div>
diff --git a/user_sql.php b/user_sql.php
new file mode 100644
index 0000000..9283c66
--- /dev/null
+++ b/user_sql.php
@@ -0,0 +1,782 @@
+<?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 \OCA\user_sql\lib\Helper;
+
+class OC_USER_SQL extends \OC_User_Backend 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)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering doEmailSync for UID: $uid", \OCP\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)
+ {
+ \OCP\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'] !== '')
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Append default domain: ".$this -> settings['set_default_domain'], \OCP\Util::DEBUG);
+ if(strpos($uid, '@') === false)
+ {
+ $uid .= "@" . $this -> settings['set_default_domain'];
+ }
+ }
+
+ $uid = strtolower($uid);
+ \OCP\Util::writeLog('OC_USER_SQL', 'Returning mapped UID: ' . $uid, \OCP\Util::DEBUG);
+ return $uid;
+ }
+
+ /**
+ * Return the actions implemented by this backend
+ * @param $actions
+ * @return bool
+ */
+ public function implementsAction($actions)
+ {
+ return (bool)((\OC_User_Backend::CHECK_PASSWORD
+ | \OC_User_Backend::GET_DISPLAYNAME
+ | \OC_User_Backend::COUNT_USERS
+ | $this -> settings['set_allow_pwchange'] === 'true' ? \OC_User_Backend::SET_PASSWORD : 0
+ | $this -> settings['set_enable_gethome'] === 'true' ? \OC_User_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)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering getHome for UID: $uid", \OCP\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':
+ \OCP\Util::writeLog('OC_USER_SQL', "getHome with Query selected, running Query...", \OCP\Util::DEBUG);
+ $row = $this -> helper -> runQuery('getHome', array('uid' => $uidMapped));
+ if($row === false)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Got no row, return false", \OCP\Util::DEBUG);
+ return false;
+ }
+ $home = $row[$this -> settings['col_gethome']];
+ break;
+
+ case 'static':
+ \OCP\Util::writeLog('OC_USER_SQL', "getHome with static selected", \OCP\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;
+ }
+ \OCP\Util::writeLog('OC_USER_SQL', "Returning getHome for UID: $uid with Home $home", \OCP\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
+ \OCP\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
+ \OCP\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
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering setPassword for UID: $uid", \OCP\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));
+ } else
+ {
+ $enc_password = $this -> pacrypt($password, $old_password);
+ }
+ $res = $this -> helper -> runQuery('setPass', array('uid' => $uid, 'enc_password' => $enc_password), true);
+ if($res === false)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Could not update password!", \OCP\Util::ERROR);
+ return false;
+ }
+ \OCP\Util::writeLog('OC_USER_SQL', "Updated password successfully, return true", \OCP\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)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering checkPassword() for UID: $uid", \OCP\Util::DEBUG);
+
+ $uid = $this -> doUserDomainMapping($uid);
+
+ $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']];
+ \OCP\Util::writeLog('OC_USER_SQL', "Encrypting and checking password", \OCP\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);
+ }
+ // 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;
+ } else
+ {
+ $ret = $this -> pacrypt($password, $db_pass) === $db_pass;
+ }
+ if($ret)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Passwords matching, return true", \OCP\Util::DEBUG);
+ if($this -> settings['set_strip_domain'] === 'true')
+ {
+ $uid = explode("@", $uid);
+ $uid = $uid[0];
+ }
+ return $uid;
+ } else
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Passwords do not match, return false", \OCP\Util::DEBUG);
+ return false;
+ }
+ }
+
+ /**
+ * Count the number of users
+ * @return int The user count
+ */
+ public function countUsers()
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering countUsers()", \OCP\Util::DEBUG);
+
+ $search = "%".$this -> doUserDomainMapping("");
+ $userCount = $this -> helper -> runQuery('countUsers', array('search' => $search));
+ if($userCount === false)
+ {
+ $userCount = 0;
+ }
+ else {
+ $userCount = reset($userCount);
+ }
+
+ \OCP\Util::writeLog('OC_USER_SQL', "Return usercount: ".$userCount, \OCP\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)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering getUsers() with Search: $search, Limit: $limit, Offset: $offset", \OCP\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);
+ }
+ \OCP\Util::writeLog('OC_USER_SQL', "Return list of results", \OCP\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);
+ \OCP\Util::writeLog('OC_USER_SQL', "userExists() for UID: $uid cacheVal: $cacheVal", \OCP\Util::DEBUG);
+ if(!is_null($cacheVal))
+ return (bool)$cacheVal;
+
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering userExists() for UID: $uid", \OCP\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)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Empty row, user does not exists, return false", \OCP\Util::DEBUG);
+ return false;
+ } else
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "User exists, return true", \OCP\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)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering getDisplayName() for UID: $uid", \OCP\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)
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Empty row, user has no display name or does not exist, return false", \OCP\Util::DEBUG);
+ return false;
+ } else
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "User exists, return true", \OCP\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 = "")
+ {
+ \OCP\Util::writeLog('OC_USER_SQL', "Entering private pacrypt()", \OCP\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
+ {
+ \OCP\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']);
+ }
+ \OCP\Util::writeLog('OC_USER_SQL', "pacrypt() done, return", \OCP\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_salt();
+ $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_salt()
+ {
+ 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;
+ }
+
+}
+?>