From: Arthur Schiwon Date: Fri, 4 Oct 2013 14:33:37 +0000 (+0200) Subject: LDAP Wizard: add detection, load and save of LDAP objectClasses for filter purposes X-Git-Tag: v6.0.0beta2~46^2~27 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=3b1822cf91407f243f18311b8abc273ad2eb1b11;p=nextcloud-server.git LDAP Wizard: add detection, load and save of LDAP objectClasses for filter purposes --- diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php index 7df922f17af..807f04ca696 100644 --- a/apps/user_ldap/ajax/wizard.php +++ b/apps/user_ldap/ajax/wizard.php @@ -46,6 +46,7 @@ $wizard = new \OCA\user_ldap\lib\Wizard($configuration, $ldapWrapper); switch($action) { case 'guessPortAndTLS': case 'guessBaseDN': + case 'determineObjectClasses': try { $result = $wizard->$action(); if($result !== false) { diff --git a/apps/user_ldap/css/settings.css b/apps/user_ldap/css/settings.css index aa6c4687cf9..f6c9f756339 100644 --- a/apps/user_ldap/css/settings.css +++ b/apps/user_ldap/css/settings.css @@ -1,5 +1,6 @@ .table { display: table; + width: 60%; } .tablecell { @@ -18,7 +19,7 @@ height: 15px; } -.hidden { +.hidden, .invisible { visibility: hidden; } @@ -55,6 +56,16 @@ width: 96.5% !important; } +.tableCellInput { + margin-left: -40%; + width: 100%; +} + +.tableCellLabel { + text-align: right; + padding-right: 25%; +} + .ldapIndent { margin-left: 50px; } @@ -81,4 +92,11 @@ #ldap fieldset p input[type=checkbox] { vertical-align: bottom; +} + +select[multiple=multiple] + button { + height: 28px; + padding-top: 6px !important; + min-width: 40%; + max-width: 40%; } \ No newline at end of file diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 45b1a9239f5..88f63e25ca2 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -144,7 +144,10 @@ var LdapWizard = { applyChanges: function (result) { for (id in result.changes) { - LdapWizard.saveBlacklist[id] = true; + if(!$.isArray(result.changes[id])) { + //no need to blacklist multiselect + LdapWizard.saveBlacklist[id] = true; + } $('#'+id).val(result.changes[id]); } }, @@ -162,12 +165,12 @@ var LdapWizard = { function(result) { LdapWizard.applyChanges(result); if($('#ldap_base').val()) { - $('#ldap_base').removeClass('hidden'); + $('#ldap_base').removeClass('invisible'); LdapWizard.hideInfoBox(); } }, function (result) { - $('#ldap_base').removeClass('hidden'); + $('#ldap_base').removeClass('invisible'); LdapWizard.showInfoBox('Please specify a port'); } ); @@ -187,28 +190,59 @@ var LdapWizard = { function(result) { LdapWizard.applyChanges(result); if($('#ldap_port').val()) { - $('#ldap_port').removeClass('hidden'); + $('#ldap_port').removeClass('invisible'); LdapWizard.hideInfoBox(); } }, function (result) { - $('#ldap_port').removeClass('hidden'); + $('#ldap_port').removeClass('invisible'); LdapWizard.showInfoBox('Please specify the BaseDN'); } ); } }, + findObjectClasses: function() { + param = 'action=determineObjectClasses'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.ajax(param, + function(result) { + $('#ldap_userfilter_objectclass').find('option').remove(); + for (i in result.options['ldap_userfilter_objectclass']) { + //FIXME: move HTML into template + objc = result.options['ldap_userfilter_objectclass'][i]; + $('#ldap_userfilter_objectclass').append(""); + } + LdapWizard.applyChanges(result); + $('#ldap_userfilter_objectclass').multiselect('refresh'); + }, + function (result) { + //TODO: error handling + } + ); + }, + hideInfoBox: function() { if(LdapWizard.checkInfoShown) { - $('#ldapWizard1 .ldapWizardInfo').addClass('hidden'); + $('#ldapWizard1 .ldapWizardInfo').addClass('invisible'); LdapWizard.checkInfoShown = false; } }, init: function() { if($('#ldap_port').val()) { - $('#ldap_port').removeClass('hidden'); + $('#ldap_port').removeClass('invisible'); + } + }, + + initUserFilter: function() { + LdapWizard.findObjectClasses(); + }, + + onTabChange: function(event, ui) { + if(ui.newTab[0].id === '#ldapWizard2') { + LdapWizard.initUserFilter(); } }, @@ -227,8 +261,20 @@ var LdapWizard = { delete LdapWizard.saveBlacklist[inputObj.id]; return; } - param = 'cfgkey='+inputObj.id+ - '&cfgval='+$(inputObj).val()+ + LdapWizard._save(inputObj, $(inputObj).val()); + }, + + saveMultiSelect: function(originalObj, resultObj) { + values = ''; + for(i = 0; i < resultObj.length; i++) { + values = values + "\n" + resultObj[i].value; + } + LdapWizard._save($('#'+originalObj)[0], $.trim(values)); + }, + + _save: function(object, value) { + param = 'cfgkey='+object.id+ + '&cfgval='+value+ '&action=save'+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); @@ -237,7 +283,7 @@ var LdapWizard = { param, function(result) { if(result.status == 'success') { - LdapWizard.processChanges(inputObj); + LdapWizard.processChanges(object); } else { // alert('Oooooooooooh :('); } @@ -247,17 +293,28 @@ var LdapWizard = { showInfoBox: function(text) { $('#ldapWizard1 .ldapWizardInfo').text(t('user_ldap', text)); - $('#ldapWizard1 .ldapWizardInfo').removeClass('hidden'); + $('#ldapWizard1 .ldapWizardInfo').removeClass('invisible'); LdapWizard.checkInfoShown = true; } }; $(document).ready(function() { $('#ldapAdvancedAccordion').accordion({ heightStyle: 'content', animate: 'easeInOutCirc'}); - $('#ldapSettings').tabs(); + $('#ldapSettings').tabs({ beforeActivate: LdapWizard.onTabChange }); $('#ldap_submit').button(); $('#ldap_action_test_connection').button(); $('#ldap_action_delete_configuration').button(); + $('#ldap_userfilter_groups').multiselect(); + $('#ldap_userfilter_objectclass').multiselect({ + header: false, + selectedList: 9, + noneSelectedText: t('user_ldap', 'Select object classes'), + click: function(event, ui) { + LdapWizard.saveMultiSelect('ldap_userfilter_objectclass', + $('#ldap_userfilter_objectclass').multiselect("getChecked") + ); + } + }); $('.lwautosave').change(function() { LdapWizard.save(this); }); LdapConfiguration.refreshConfig(); $('#ldap_action_test_connection').click(function(event){ diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index ed6f384da88..e67e0d8d00b 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -44,6 +44,7 @@ class Configuration { 'turnOffCertCheck' => null, 'ldapIgnoreNamingRules' => null, 'ldapUserDisplayName' => null, + 'ldapUserFilterObjectclass' => null, 'ldapUserFilter' => null, 'ldapGroupFilter' => null, 'ldapGroupDisplayName' => null, @@ -121,6 +122,7 @@ class Configuration { case 'ldapBaseGroups': case 'ldapAttributesForUserSearch': case 'ldapAttributesForGroupSearch': + case 'ldapUserFilterObjectclass': $setMethod = 'setMultiLine'; default: $this->$setMethod($key, $val); @@ -136,19 +138,18 @@ class Configuration { if(!$this->configRead && !is_null($this->configPrefix)) { $cta = array_flip($this->getConfigTranslationArray()); foreach($this->config as $key => $val) { -// if($this->configPrefix == 's04') var_dump($key); if(!isset($cta[$key])) { //some are determined continue; } $dbkey = $cta[$key]; -// if($this->configPrefix == 's04') var_dump($dbkey); switch($key) { case 'ldapBase': case 'ldapBaseUsers': case 'ldapBaseGroups': case 'ldapAttributesForUserSearch': case 'ldapAttributesForGroupSearch': + case 'ldapUserFilterObjectclass': $readMethod = 'getMultiLine'; break; case 'ldapIgnoreNamingRules': @@ -166,16 +167,10 @@ class Configuration { $readMethod = 'getValue'; break; } -// if($this->configPrefix == 's04') var_dump($readMethod); $this->config[$key] = $this->$readMethod($dbkey); } $this->configRead = true; } - if($this->configPrefix == 's03') { -// var_dump($this->config); - -// die; - } } /** @@ -193,6 +188,7 @@ class Configuration { case 'ldapBaseGroups': case 'ldapAttributesForUserSearch': case 'ldapAttributesForGroupSearch': + case 'ldapUserFilterObjectclass': if(is_array($value)) { $value = implode("\n", $value); } @@ -250,12 +246,6 @@ class Configuration { if(is_null($defaults)) { $defaults = $this->getDefaults(); } -// if($this->configPrefix == 's04') var_dump($this->configPrefix.$varname); -// if(0 == $this->configKeyToDBKey($varname)) { -// var_dump($varname); -// print("
");
-// 			debug_print_backtrace(); die;
-// 		}
 		return \OCP\Config::getAppValue('user_ldap',
 										$this->configPrefix.$varname,
 										$defaults[$varname]);
@@ -288,6 +278,7 @@ class Configuration {
 			'ldap_base_users'					=> '',
 			'ldap_base_groups'					=> '',
 			'ldap_userlist_filter'				=> 'objectClass=person',
+			'ldap_userfilter_objectclass'		=> '',
 			'ldap_login_filter'					=> 'uid=%uid',
 			'ldap_group_filter'					=> 'objectClass=posixGroup',
 			'ldap_display_name'					=> 'cn',
@@ -327,6 +318,7 @@ class Configuration {
 			'ldap_base'							=> 'ldapBase',
 			'ldap_base_users'					=> 'ldapBaseUsers',
 			'ldap_base_groups'					=> 'ldapBaseGroups',
+			'ldap_userfilter_objectclass' 		=> 'ldapUserFilterObjectclass',
 			'ldap_userlist_filter'				=> 'ldapUserFilter',
 			'ldap_login_filter'					=> 'ldapLoginFilter',
 			'ldap_group_filter'					=> 'ldapGroupFilter',
diff --git a/apps/user_ldap/lib/ildapwrapper.php b/apps/user_ldap/lib/ildapwrapper.php
index 5e12c7c63b9..20587cba7db 100644
--- a/apps/user_ldap/lib/ildapwrapper.php
+++ b/apps/user_ldap/lib/ildapwrapper.php
@@ -105,6 +105,14 @@ interface ILDAPWrapper {
 	 * */
 	public function getAttributes($link, $result);
 
+	/**
+	 * @brief Get the DN of a result entry
+	 * @param $link LDAP link resource
+	 * @param $result LDAP result resource
+	 * @return string containing the DN, false on error
+	 */
+	public function getDN($link, $result);
+
 	/**
 	 * @brief Get all result entries
 	 * @param $link LDAP link resource
@@ -113,6 +121,14 @@ interface ILDAPWrapper {
 	 */
 	public function getEntries($link, $result);
 
+	/**
+	 * @brief Return next result id
+	 * @param $link LDAP link resource
+	 * @param $result LDAP entry result resource
+	 * @return an LDAP search result resource
+	 * */
+	public function nextEntry($link, $result);
+
 	/**
 	 * @brief Read an entry
 	 * @param $link LDAP link resource
diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php
index 13314462b8c..bc963191722 100644
--- a/apps/user_ldap/lib/ldap.php
+++ b/apps/user_ldap/lib/ldap.php
@@ -69,10 +69,18 @@ class LDAP implements ILDAPWrapper {
 		return $this->invokeLDAPMethod('get_attributes', $link, $result);
 	}
 
+	public function getDN($link, $result) {
+		return $this->invokeLDAPMethod('get_dn', $link, $result);
+	}
+
 	public function getEntries($link, $result) {
 		return $this->invokeLDAPMethod('get_entries', $link, $result);
 	}
 
+	public function nextEntry($link, $result) {
+		return $this->invokeLDAPMethod('next_entry', $link, $result);
+	}
+
 	public function read($link, $baseDN, $filter, $attr) {
 		return $this->invokeLDAPMethod('read', $link, $baseDN, $filter, $attr);
 	}
diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php
index ad71fd10f63..170af44e118 100644
--- a/apps/user_ldap/lib/wizard.php
+++ b/apps/user_ldap/lib/wizard.php
@@ -28,6 +28,10 @@ class Wizard extends LDAPUtility {
 	protected $configuration;
 	protected $result;
 
+	const LRESULT_PROCESSED_OK = 0;
+	const LRESULT_PROCESSED_INVALID = 1;
+	const LRESULT_PROCESSED_SKIP = 2;
+
 	/**
 	 * @brief Constructor
 	 * @param $configuration an instance of Configuration
@@ -48,6 +52,51 @@ class Wizard extends LDAPUtility {
 		}
 	}
 
+	public function determineObjectClasses() {
+		if(!$this->checkRequirements(array('ldapHost',
+										   'ldapPort',
+										   'ldapAgentName',
+										   'ldapAgentPassword',
+										   'ldapBase',
+										   ))) {
+			return  false;
+		}
+		$cr = $this->getConnection();
+		if(!$cr) {
+			throw new \Excpetion('Could not connect to LDAP');
+		}
+
+		$p = 'objectclass=';
+		$obclasses = array($p.'inetOrgPerson',        $p.'person',
+						   $p.'organizationalPerson', $p.'user',
+						   $p.'posixAccount',         $p.'*');
+
+		$maxEntryObjC = '';
+		$availableObjectClasses =
+			$this->cumulativeSearchOnAttribute($obclasses, 'objectclass',
+												true, $maxEntryObjC);
+		if(is_array($availableObjectClasses)
+		   && count($availableObjectClasses) > 0) {
+			$this->result->addOptions('ldap_userfilter_objectclass',
+										$availableObjectClasses);
+		} else {
+			throw new \Exception(self::$l->t('Could not find any objectClass'));
+		}
+		$setOCs = $this->configuration->ldapUserFilterObjectclass;
+		file_put_contents('/tmp/set', print_r($setOCs, true));
+		if(is_array($setOCs) && !empty($setOCs)) {
+			//something is already configured? pre-select it.
+			$this->result->addChange('ldap_userfilter_objectclass', $setOCs);
+		} else if(!empty($maxEntryObjC)) {
+			//new? pre-select something hopefully sane
+			$maxEntryObjC = str_replace($p, '', $maxEntryObjC);
+			$this->result->addChange('ldap_userfilter_objectclass',
+									 $maxEntryObjC);
+		}
+
+		return $this->result;
+	}
+
 	/**
 	 * Tries to determine the port, requires given Host, User DN and Password
 	 * @returns mixed WizardResult on success, false otherwise
@@ -55,7 +104,8 @@ class Wizard extends LDAPUtility {
 	public function guessPortAndTLS() {
 		if(!$this->checkRequirements(array('ldapHost',
 										   'ldapAgentName',
-										   'ldapAgentPassword'))) {
+										   'ldapAgentPassword'
+										   ))) {
 			return false;
 		}
 		$this->checkHost();
@@ -266,6 +316,104 @@ class Wizard extends LDAPUtility {
 		return true;
 	}
 
+	/**
+	 * @brief does a cumulativeSearch on LDAP to get different values of a
+	 * specified attribute
+	 * @param $filters array, the filters that shall be used in the search
+	 * @param $attr the attribute of which a list of values shall be returned
+	 * @param $lfw bool, whether the last filter is a wildcard which shall not
+	 * be processed if there were already findings, defaults to true
+	 * @param $maxF string. if not null, this variable will have the filter that
+	 * yields most result entries
+	 * @return mixed, an array with the values on success, false otherwise
+	 *
+	 */
+	private function cumulativeSearchOnAttribute($filters, $attr, $lfw = true, &$maxF = null) {
+		$dnRead = array();
+		$foundItems = array();
+		$maxEntries = 0;
+		if(!is_array($this->configuration->ldapBase) || !isset($this->configuration->ldapBase[0])) {
+			return false;
+		}
+		$base = $this->configuration->ldapBase[0];
+		$cr = $this->getConnection();
+		if(!is_resource($cr)) {
+			return false;
+		}
+		foreach($filters as $filter) {
+			if($lfw && count($foundItems) > 0) {
+				continue;
+			}
+			$rr = $this->ldap->search($cr, $base, $filter, array($attr));
+			if(!$this->ldap->isResource($rr)) {
+				\OCP\Util::writeLog('user_ldap', 'Search failed, Base '.$base, \OCP\Util::DEBUG);
+				continue;
+			}
+			$entries = $this->ldap->countEntries($cr, $rr);
+			$getEntryFunc = 'firstEntry';
+			if(($entries !== false) && ($entries > 0)) {
+				if(!is_null($maxF) && $entries > $maxEntries) {
+					$maxEntries = $entries;
+					$maxF = $filter;
+				}
+				do {
+					$entry = $this->ldap->$getEntryFunc($cr, $rr);
+					if(!$this->ldap->isResource($entry)) {
+						continue 2;
+					}
+					$attributes = $this->ldap->getAttributes($cr, $entry);
+					$dn = $this->ldap->getDN($cr, $entry);
+					if($dn === false || in_array($dn, $dnRead)) {
+						continue;
+					}
+					$state = $this->getAttributeValuesFromEntry($attributes,
+																$attr,
+																$foundItems);
+					$dnRead[] = $dn;
+					$getEntryFunc = 'nextEntry';
+					$rr = $entry; //will be expected by nextEntry next round
+				} while($state === self::LRESULT_PROCESSED_SKIP
+						|| $this->ldap->isResource($entry));
+			}
+		}
+
+		return $foundItems;
+	}
+
+	/**
+	 * @brief appends a list of values fr
+	 * @param $result resource, the return value from ldap_get_attributes
+	 * @param $attribute string, the attribute values to look for
+	 * @param &$known array, new values will be appended here
+	 * @return int, state on of the class constants LRESULT_PROCESSED_OK,
+	 * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
+	 */
+	private function getAttributeValuesFromEntry($result, $attribute, &$known) {
+		if(!is_array($result)
+		   || !isset($result['count'])
+		   || !$result['count'] > 0) {
+			return self::LRESULT_PROCESSED_INVALID;
+		}
+
+		//strtolower on all keys for proper comparison
+		$result = \OCP\Util::mb_array_change_key_case($result);
+		$attribute = strtolower($attribute);
+		if(isset($result[$attribute])) {
+			foreach($result[$attribute] as $key => $val) {
+				if($key === 'count') {
+					continue;
+				}
+				if(!in_array($val, $known)) {
+					\OCP\Util::writeLog('user_ldap', 'Found objclass '.$val, \OCP\Util::DEBUG);
+					$known[] = $val;
+				}
+			}
+			return self::LRESULT_PROCESSED_OK;
+		} else {
+			return self::LRESULT_PROCESSED_SKIP;
+		}
+	}
+
 	private function getConnection() {
 		$cr = $this->ldap->connect(
 			$this->configuration->ldapHost.':'.$this->configuration->ldapPort,
diff --git a/apps/user_ldap/lib/wizardresult.php b/apps/user_ldap/lib/wizardresult.php
index 2140f654fd0..4c3b563c0c2 100644
--- a/apps/user_ldap/lib/wizardresult.php
+++ b/apps/user_ldap/lib/wizardresult.php
@@ -25,11 +25,19 @@ namespace OCA\user_ldap\lib;
 
 class WizardResult {
 	protected $changes = array();
+	protected $options = array();
 
 	public function addChange($key, $value) {
 		$this->changes[$key] = $value;
 	}
 
+	public function addOptions($key, $values) {
+		if(!is_array($values)) {
+			$values = array($values);
+		}
+		$this->options[$key] = $values;
+	}
+
 	public function hasChanges() {
 		return count($this->changes) > 0;
 	}
@@ -37,6 +45,9 @@ class WizardResult {
 	public function getResultArray() {
 		$result = array();
 		$result['changes'] = $this->changes;
+		if(count($this->options) > 0) {
+			$result['options'] = $this->options;
+		}
 		return $result;
 	}
 }
\ No newline at end of file
diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php
index 8a418a65005..ebba1dbd3a6 100644
--- a/apps/user_ldap/settings.php
+++ b/apps/user_ldap/settings.php
@@ -25,8 +25,11 @@
 
 OC_Util::checkAdminUser();
 
-OCP\Util::addscript('user_ldap', 'settings');
-OCP\Util::addstyle('user_ldap', 'settings');
+OCP\Util::addScript('user_ldap', 'settings');
+OCP\Util::addScript('core', 'jquery.multiselect');
+OCP\Util::addStyle('user_ldap', 'settings');
+OCP\Util::addStyle('core', 'jquery.multiselect');
+OCP\Util::addStyle('core', 'jquery-ui-1.10.0.custom');
 
 // fill template
 $tmpl = new OCP\Template('user_ldap', 'settings');
@@ -49,6 +52,11 @@ $wizard1->assign('wizardControls', $wControls);
 $wizardHtml .= $wizard1->fetchPage();
 $toc['#ldapWizard1'] = 'Server';
 
+$wizard2 = new OCP\Template('user_ldap', 'part.wizard-userfilter');
+$wizard2->assign('wizardControls', $wControls);
+$wizardHtml .= $wizard2->fetchPage();
+$toc['#ldapWizard2'] = 'User Filter';
+
 $tmpl->assign('tabs', $wizardHtml);
 $tmpl->assign('toc', $toc);
 
diff --git a/apps/user_ldap/templates/part.wizard-server.php b/apps/user_ldap/templates/part.wizard-server.php
index ae0a7e650c9..c6900fd24e8 100644
--- a/apps/user_ldap/templates/part.wizard-server.php
+++ b/apps/user_ldap/templates/part.wizard-server.php
@@ -33,7 +33,7 @@
 							/>
 						
 							
 						
@@ -68,7 +68,7 @@
 			
 
 			
-
diff --git a/apps/user_ldap/templates/part.wizard-userfilter.php b/apps/user_ldap/templates/part.wizard-userfilter.php new file mode 100644 index 00000000000..b58784b680f --- /dev/null +++ b/apps/user_ldap/templates/part.wizard-userfilter.php @@ -0,0 +1,51 @@ +
+ +
+

+ t('Limit the access to ownCloud to users meetignthis criteria:'));?> +

+ +

+ + + +

+ +

+ + + +

+ +

+ +

+ + + +

+

+

+ +
+
\ No newline at end of file diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 9ca9673ada2..f63a4bdef1f 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -2,7 +2,7 @@