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 @@ +