From 53db1fe5ac24f569918fa7cfb3dcd67054099836 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 27 Sep 2013 18:30:59 +0200 Subject: [PATCH] First stage of new Wizard, neither feature complete nor ready --- apps/user_ldap/ajax/wizard.php | 83 ++++++ apps/user_ldap/css/settings.css | 79 +++++- apps/user_ldap/js/settings.js | 88 ++++++ apps/user_ldap/lib/wizard.php | 267 ++++++++++++++++++ apps/user_ldap/lib/wizardresult.php | 50 ++++ apps/user_ldap/settings.php | 20 ++ .../templates/part.settingcontrols.php | 12 + .../templates/part.wizard-server.php | 76 +++++ .../templates/part.wizardcontrols.php | 14 + apps/user_ldap/templates/settings.php | 14 +- 10 files changed, 688 insertions(+), 15 deletions(-) create mode 100644 apps/user_ldap/ajax/wizard.php create mode 100644 apps/user_ldap/lib/wizard.php create mode 100644 apps/user_ldap/lib/wizardresult.php create mode 100644 apps/user_ldap/templates/part.settingcontrols.php create mode 100644 apps/user_ldap/templates/part.wizard-server.php create mode 100644 apps/user_ldap/templates/part.wizardcontrols.php diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php new file mode 100644 index 0000000000..53c66c34f8 --- /dev/null +++ b/apps/user_ldap/ajax/wizard.php @@ -0,0 +1,83 @@ +. + * + */ + +// Check user and app status +OCP\JSON::checkAdminUser(); +OCP\JSON::checkAppEnabled('user_ldap'); +OCP\JSON::callCheck(); + +$l=OC_L10N::get('user_ldap'); + +if(!isset($_POST['action'])) { + \OCP\JSON::error(array('message' => $l->t('No action specified'))); +} +$action = $_POST['action']; + + +if(!isset($_POST['ldap_serverconfig_chooser'])) { + \OCP\JSON::error(array('message' => $l->t('No configuration specified'))); +} +$prefix = $_POST['ldap_serverconfig_chooser']; + +$ldapWrapper = new OCA\user_ldap\lib\LDAP(); +$configuration = new \OCA\user_ldap\lib\Configuration($prefix); +$wizard = new \OCA\user_ldap\lib\Wizard($configuration, $ldapWrapper); + +switch($action) { + case 'guessPortAndTLS': + try { + $result = $wizard->$action(); + if($result !== false) { + OCP\JSON::success($result->getResultArray()); + exit; + } + } catch (\Exception $e) { + \OCP\JSON::error(array('message' => $e->getMessage())); + exit; + } + \OCP\JSON::error(); + exit; + break; + + case 'save': + $key = isset($_POST['cfgkey']) ? $_POST['cfgkey'] : false; + $val = isset($_POST['cfgval']) ? $_POST['cfgval'] : null; + if($key === false || is_null($val)) { + \OCP\JSON::error(array('message' => $l->t('No data specified'))); + exit; + } + $cfg = array($key => $val); + $setParameters = array(); + $configuration->setConfiguration($cfg, $setParameters); + if(!in_array($key, $setParameters)) { + \OCP\JSON::error(array('message' => $l->t($key.' Could not set configuration '.$setParameters[0]))); + exit; + } + $configuration->saveConfiguration(); + OCP\JSON::success(); + break; + default: + //TODO: return 4xx error + break; +} + diff --git a/apps/user_ldap/css/settings.css b/apps/user_ldap/css/settings.css index 6086c7b74e..aa6c4687cf 100644 --- a/apps/user_ldap/css/settings.css +++ b/apps/user_ldap/css/settings.css @@ -1,3 +1,69 @@ +.table { + display: table; +} + +.tablecell { + display: table-cell !important; +} + +.tablerow { + display: table-row; +} + +.tablerow input, .tablerow textarea { + width: 100% !important; +} + +.tablerow textarea { + height: 15px; +} + +.hidden { + visibility: hidden; +} + +.ldapSettingsTabs { + float: right !important; +} + +.ldapWizardControls { + width: 60%; + text-align: right; +} + +.ldapWizardInfo { + width: 100% !important; + height: 50px; + background-color: lightyellow; + border-radius: 0.5em; + padding: 0.6em 0.5em 0.4em !important; + margin-bottom: 0.3em; +} + +#ldapWizard1 .hostPortCombinator { + width: 60%; + display: table; +} + +#ldapWizard1 .hostPortCombinator div span { + width: 7%; + display: table-cell; + text-align: right; +} + +#ldapWizard1 .host { + width: 96.5% !important; +} + +.ldapIndent { + margin-left: 50px; +} + +.ldapwarning { + margin-left: 1.4em; + color: #FF3B3B; +} + #ldap fieldset p label { width: 20%; max-width: 200px; @@ -9,19 +75,10 @@ } #ldap fieldset input, #ldap fieldset textarea { - width: 60%; + width: 60%; display: inline-block; } #ldap fieldset p input[type=checkbox] { vertical-align: bottom; -} - -.ldapIndent { - margin-left: 50px; -} - -.ldapwarning { - margin-left: 1.4em; - color: #FF3B3B; -} +} \ No newline at end of file diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 20d6f76dcd..97470b4da4 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -30,6 +30,7 @@ var LdapConfiguration = { // assign the value $('#'+configkey).val(configvalue); }); + LdapWizard.init(); } } ); @@ -91,6 +92,7 @@ var LdapConfiguration = { $('#ldap_serverconfig_chooser option:selected').removeAttr('selected'); var html = ''; $('#ldap_serverconfig_chooser option:last').before(html); + LdapWizard.init(); } else { OC.dialogs.alert( result.message, @@ -122,12 +124,98 @@ var LdapConfiguration = { } }; +var LdapWizard = { + checkPortInfoShown: false, + changeIndicators: {}, + + ajax: function(param, fnOnSuccess, fnOnError) { + $.post( + OC.filePath('user_ldap','ajax','wizard.php'), + param, + function(result) { + if(result.status == 'success') { + fnOnSuccess(result); + } else { + fnOnError(result); + } + } + ); + }, + + applyChanges: function (result) { + for (id in result.changes) { + $('#'+id).val(result.changes[id]); + } + }, + + checkPort: function() { + host = $('#ldap_host').val(); + user = $('#ldap_dn').val(); + pass = $('#ldap_agent_password').val(); + + if(host && user && pass) { + param = 'action=guessPortAndTLS'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.ajax(param, + function(result) { + LdapWizard.applyChanges(result); + if($('#ldap_port').val()) { + $('#ldap_port').removeClass('hidden'); + if(LdapWizard.checkPortInfoShown) { + $('#ldapWizard1 .ldapWizardInfo').addClass('hidden'); + LdapWizard.checkPortInfoShown = false; + } + } + }, + function (result) { + $('#ldap_port').removeClass('hidden'); + $('#ldapWizard1 .ldapWizardInfo').text(t('user_ldap', + 'Please specify a port')); + $('#ldapWizard1 .ldapWizardInfo').removeClass('hidden'); + LdapWizard.checkPortInfoShown = true; + } + ); + } + }, + + init: function() { + if($('#ldap_port').val()) { + $('#ldap_port').removeClass('hidden'); + } + }, + + save: function(inputObj) { + param = 'cfgkey='+inputObj.id+ + '&cfgval='+$(inputObj).val()+ + '&action=save'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + $.post( + OC.filePath('user_ldap','ajax','wizard.php'), + param, + function(result) { + if(result.status == 'success') { + if(inputObj.id == 'ldap_host' + || inputObj.id == 'ldap_dn' + || inputObj.id == 'ldap_agent_password') { + LdapWizard.checkPort(); + } + } else { +// alert('Oooooooooooh :('); + } + } + ); + } +}; + $(document).ready(function() { $('#ldapAdvancedAccordion').accordion({ heightStyle: 'content', animate: 'easeInOutCirc'}); $('#ldapSettings').tabs(); $('#ldap_submit').button(); $('#ldap_action_test_connection').button(); $('#ldap_action_delete_configuration').button(); + $('.lwautosave').change(function() { LdapWizard.save(this); }); LdapConfiguration.refreshConfig(); $('#ldap_action_test_connection').click(function(event){ event.preventDefault(); diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php new file mode 100644 index 0000000000..ff81df8bf0 --- /dev/null +++ b/apps/user_ldap/lib/wizard.php @@ -0,0 +1,267 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +class Wizard extends LDAPUtility { + static protected $l; + protected $configuration; + protected $result; + + /** + * @brief Constructor + * @param $configuration an instance of Configuration + * @param $ldap an instance of ILDAPWrapper + */ + public function __construct(Configuration $configuration, ILDAPWrapper $ldap) { + parent::__construct($ldap); + $this->configuration = $configuration; + if(is_null(Wizard::$l)) { + Wizard::$l = \OC_L10N::get('user_ldap'); + } + $this->result = new WizardResult; + } + + public function __destruct() { + if($this->result->hasChanges()) { + $this->configuration->saveConfiguration(); + } + } + + /** + * Tries to determine the port, requires given Host, User DN and Password + * @returns mixed WizardResult on success, false otherwise + */ + public function guessPortAndTLS() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapAgentName', + 'ldapAgentPassword'))) { + return false; + } + $this->checkHost(); + $portSettings = $this->getPortSettingsToTry(); + file_put_contents('/tmp/ps', print_r($portSettings, true).PHP_EOL, FILE_APPEND); + + if(!is_array($portSettings)) { + throw new \Exception(print_r($portSettings, true)); + } + + //proceed from the best configuration and return on first success + foreach($portSettings as $setting) { + $p = $setting['port']; + $t = $setting['tls']; + \OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, \OCP\Util::DEBUG); + //connectAndBind may throw Exception, it needs to be catched by the + //callee of this method + if($this->connectAndBind($p, $t) === true) { + $config = array('ldapPort' => $p, + 'ldapTLS' => intval($t) + ); + $this->configuration->setConfiguration($config); + \OCP\Util::writeLog('user_ldap', 'Wiz: detected Port '. $p, \OCP\Util::DEBUG); + $this->result->addChange('ldap_port', $p); + $this->result->addChange('ldap_tls', intval($t)); +// $this->result->addSpecific('port', $p); + return $this->result; + } + } + + //custom port, undetected (we do not brute force) + return false; + } + + public function guessBaseDN() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapPort', + ))) { + return false; + } + $cr = $this->getConnection(); + if(!$cr) { + return false; + } + } + + /** + * @brief Checks, whether a port was entered in the Host configuration + * field. In this case the port will be stripped off, but also stored as + * setting. + */ + private function checkHost() { + $host = $this->configuration->ldapHost; + $hostInfo = parse_url($host); + + //removes Port from Host + if(is_array($hostInfo) && isset($hostInfo['port'])) { + $port = $hostInfo['port']; + $host = str_replace(':'.$port, '', $host); + $config = array('ldapHost' => $host, + 'ldapPort' => $port, + ); + $this->result->addChange('ldap_host', $host); + $this->result->addChange('ldap_port', $port); + $this->configuration->setConfiguration($config); + } + } + + /** + * Connects and Binds to an LDAP Server + * @param $port the port to connect with + * @param $tls whether startTLS is to be used + * @return + */ + private function connectAndBind($port = 389, $tls = false, $ncc = false) { + if($ncc) { + //No certificate check + //FIXME: undo afterwards + putenv('LDAPTLS_REQCERT=never'); + } + + //connect, does not really trigger any server communication + \OCP\Util::writeLog('user_ldap', 'Wiz: Checking Host Info ', \OCP\Util::DEBUG); + $host = $this->configuration->ldapHost; + $hostInfo = parse_url($host); + if(!$hostInfo) { + throw new \Exception($this->l->t('Invalid Host')); + } + if(isset($hostInfo['scheme'])) { + if(isset($hostInfo['port'])) { + //problem + } else { + $host .= ':' . $port; + } + } + \OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG); + $cr = $this->ldap->connect($host, $port); + if(!is_resource($cr)) { + throw new \Exception($this->l->t('Invalid Host')); + } + + \OCP\Util::writeLog('user_ldap', 'Wiz: Setting LDAP Options ', \OCP\Util::DEBUG); + //set LDAP options + if($this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3)) { + if($tls) { + $this->ldap->startTls($cr); + } + } + + \OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG); + //interesting part: do the bind! + $login = $this->ldap->bind($cr, + $this->configuration->ldapAgentName, + $this->configuration->ldapAgentPassword); + + if($login === true) { + $this->ldap->unbind($cr); + if($ncc) { + throw new \Exception('Certificate cannot be validated.'); + } + \OCP\Util::writeLog('user_ldap', 'Wiz: Bind succesfull with Port '. $port, \OCP\Util::DEBUG); + return true; + } + + $errno = $this->ldap->errno($cr); + $error = ldap_error($cr); + $this->ldap->unbind($cr); + if($errno === -1 || ($errno === 2 && $ncc)) { + //host, port or TLS wrong + return false; + } else if ($errno === 2) { + return $this->connectAndBind($port, $tls, true); + } + throw new \Exception($error); + } + + private function checkRequirements($reqs) { + foreach($reqs as $option) { + $value = $this->configuration->$option; + if(empty($value)) { + return false; + } + } + return true; + } + + private function getConnection() { + $cr = $this->ldap->connect( + $this->configuration->ldapHost.':'.$this->configuration->ldapPort, + $this->configuration->ldapPort); + + if($this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3)) { + if($this->configuration->ldapTLS === 1) { + $this->ldap->startTls($cr); + } + } + + $lo = @$this->ldap->bind($cr, + $this->configuration->ldapAgentName, + $this->configuration->ldapAgentPassword); + if($lo === true) { + return $cr; + } + + return false; + } + + private function getDefaultLdapPortSettings() { + static $settings = array( + array('port' => 7636, 'tls' => false), + array('port' => 636, 'tls' => false), + array('port' => 7389, 'tls' => true), + array('port' => 389, 'tls' => true), + array('port' => 7389, 'tls' => false), + array('port' => 389, 'tls' => false), + ); + return $settings; + } + + private function getPortSettingsToTry() { + //389 ← LDAP / Unencrypted or StartTLS + //636 ← LDAPS / SSL + //7xxx ← UCS. need to be checked first, because both ports may be open + $host = $this->configuration->ldapHost; + $port = intval($this->configuration->ldapPort); + $portSettings = array(); + + //In case the port is already provided, we will check this first + if($port > 0) { + $hostInfo = parse_url($host); + if(is_array($hostInfo) + && isset($hostInfo['scheme']) + && stripos($hostInfo['scheme'], 'ldaps') === false) { + $portSettings[] = array('port' => $port, 'tls' => true); + } + $portSettings[] =array('port' => $port, 'tls' => false); + } + + //default ports + $portSettings = array_merge($portSettings, + $this->getDefaultLdapPortSettings()); + + return $portSettings; + } + + +} \ No newline at end of file diff --git a/apps/user_ldap/lib/wizardresult.php b/apps/user_ldap/lib/wizardresult.php new file mode 100644 index 0000000000..1d683fee97 --- /dev/null +++ b/apps/user_ldap/lib/wizardresult.php @@ -0,0 +1,50 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +class WizardResult { + protected $changes = array(); + protected $specifics = array(); + + public function addChange($key, $value) { + $this->changes[$key] = $value; + } + + public function hasChanges() { + return count($this->changes) > 0; + } + + public function addSpecific($key, $value) { + $this->specifics[$key] = $value; + } + + public function getResultArray() { + $result = array(); + $result['changes'] = $this->changes; + foreach($this->specifics as $key => $value) { + $result[$key] = $value; + } + return $result; + } +} \ No newline at end of file diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index 45d874eff0..8a418a6500 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -33,8 +33,28 @@ $tmpl = new OCP\Template('user_ldap', 'settings'); $prefixes = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(); $hosts = \OCA\user_ldap\lib\Helper::getServerConfigurationHosts(); + +$wizardHtml = ''; +$toc = array(); + +$wControls = new OCP\Template('user_ldap', 'part.wizardcontrols'); +$wControls = $wControls->fetchPage(); +$sControls = new OCP\Template('user_ldap', 'part.settingcontrols'); +$sControls = $sControls->fetchPage(); + +$wizard1 = new OCP\Template('user_ldap', 'part.wizard-server'); +$wizard1->assign('serverConfigurationPrefixes', $prefixes); +$wizard1->assign('serverConfigurationHosts', $hosts); +$wizard1->assign('wizardControls', $wControls); +$wizardHtml .= $wizard1->fetchPage(); +$toc['#ldapWizard1'] = 'Server'; + +$tmpl->assign('tabs', $wizardHtml); +$tmpl->assign('toc', $toc); + $tmpl->assign('serverConfigurationPrefixes', $prefixes); $tmpl->assign('serverConfigurationHosts', $hosts); +$tmpl->assign('settingControls', $sControls); // assign default values $config = new \OCA\user_ldap\lib\Configuration('', false); diff --git a/apps/user_ldap/templates/part.settingcontrols.php b/apps/user_ldap/templates/part.settingcontrols.php new file mode 100644 index 0000000000..017f21c8b1 --- /dev/null +++ b/apps/user_ldap/templates/part.settingcontrols.php @@ -0,0 +1,12 @@ +
+ + + + + t('Help'));?> + +
\ No newline at end of file diff --git a/apps/user_ldap/templates/part.wizard-server.php b/apps/user_ldap/templates/part.wizard-server.php new file mode 100644 index 0000000000..ae0a7e650c --- /dev/null +++ b/apps/user_ldap/templates/part.wizard-server.php @@ -0,0 +1,76 @@ +
+

+ + +

+ +
+
+
+
+ + + + +
+
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
\ No newline at end of file diff --git a/apps/user_ldap/templates/part.wizardcontrols.php b/apps/user_ldap/templates/part.wizardcontrols.php new file mode 100644 index 0000000000..588c70916f --- /dev/null +++ b/apps/user_ldap/templates/part.wizardcontrols.php @@ -0,0 +1,14 @@ +
+ + + + + t('Help'));?> + +
\ No newline at end of file diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 319dc38a62..9ca9673ada 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -1,9 +1,12 @@
'.$l->t('Warning: Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behavior. Please ask your system administrator to disable one of them.').'

'); @@ -12,6 +15,7 @@ print_unescaped('

'.$l->t('Warning: The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it.').'

'); } ?> +

" />

+
@@ -93,6 +98,7 @@

+

t('Internal Username'));?>

@@ -105,8 +111,8 @@

t('Username-LDAP User Mapping'));?>

t('Usernames are used to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have a internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage.'));?>


+
- t('Help'));?>