From 02f292d0fdb3bdc2501364bf0dff96e1e2fcf502 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 17 Sep 2013 19:25:15 +0200 Subject: [PATCH 01/82] Doc fix --- apps/user_ldap/lib/connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 93efdb4c9c..468f4b004b 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -1,7 +1,7 @@ Date: Thu, 26 Sep 2013 22:31:57 +0200 Subject: [PATCH 02/82] LDAP: move Configuration out of Connection into class of its own. The new wizard requires it. --- apps/user_ldap/lib/access.php | 2 +- apps/user_ldap/lib/configuration.php | 352 +++++++++++++++ apps/user_ldap/lib/connection.php | 648 ++++++++++----------------- apps/user_ldap/settings.php | 3 +- 4 files changed, 587 insertions(+), 418 deletions(-) create mode 100644 apps/user_ldap/lib/configuration.php diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index f75a78bcb0..6177693c48 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -831,7 +831,7 @@ class Access extends LDAPUtility { private function combineFilter($filters, $operator) { $combinedFilter = '('.$operator; foreach($filters as $filter) { - if($filter[0] !== '(') { + if(!empty($filter) && $filter[0] !== '(') { $filter = '('.$filter.')'; } $combinedFilter.=$filter; diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php new file mode 100644 index 0000000000..e9e494c2f6 --- /dev/null +++ b/apps/user_ldap/lib/configuration.php @@ -0,0 +1,352 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +class Configuration { + + protected $configPrefix = null; + protected $configRead = false; + + //settings + protected $config = array( + 'ldapHost' => null, + 'ldapPort' => null, + 'ldapBackupHost' => null, + 'ldapBackupPort' => null, + 'ldapBase' => null, + 'ldapBaseUsers' => null, + 'ldapBaseGroups' => null, + 'ldapAgentName' => null, + 'ldapAgentPassword' => null, + 'ldapTLS' => null, + 'ldapNoCase' => null, + 'turnOffCertCheck' => null, + 'ldapIgnoreNamingRules' => null, + 'ldapUserDisplayName' => null, + 'ldapUserFilter' => null, + 'ldapGroupFilter' => null, + 'ldapGroupDisplayName' => null, + 'ldapGroupMemberAssocAttr' => null, + 'ldapLoginFilter' => null, + 'ldapQuotaAttribute' => null, + 'ldapQuotaDefault' => null, + 'ldapEmailAttribute' => null, + 'ldapCacheTTL' => null, + 'ldapUuidAttribute' => 'auto', + 'ldapOverrideUuidAttribute' => null, + 'ldapOverrideMainServer' => false, + 'ldapConfigurationActive' => false, + 'ldapAttributesForUserSearch' => null, + 'ldapAttributesForGroupSearch' => null, + 'homeFolderNamingRule' => null, + 'hasPagedResultSupport' => false, + 'ldapExpertUsernameAttr' => null, + 'ldapExpertUUIDAttr' => null, + ); + + public function __construct($configPrefix, $autoread = true) { + $this->configPrefix = $configPrefix; + if($autoread) { + $this->readConfiguration(); + } + } + + public function __get($name) { + if(isset($this->config[$name])) { + return $this->config[$name]; + } + } + + public function __set($name, $value) { + $this->setConfiguration(array($name => $value)); + } + + public function getConfiguration() { + return $this->config; + } + + /** + * @brief set LDAP configuration with values delivered by an array, not read + * from configuration. It does not save the configuration! To do so, you + * must call saveConfiguration afterwards. + * @param $config array that holds the config parameters in an associated + * array + * @param &$applied optional; array where the set fields will be given to + * @return null + */ + public function setConfiguration($config, &$applied = null) { + if(!is_array($config)) { + return false; + } + + $cta = $this->getConfigTranslationArray(); + foreach($config as $inputkey => $val) { + if(strpos($inputkey, '_') !== false && isset($cta[$inputkey])) { + $key = $cta[$inputkey]; + } elseif(isset($this->config[$inputkey])) { + $key = $inputkey; + } else { + continue; + } + + $setMethod = 'setValue'; + switch($key) { + case 'homeFolderNamingRule': + if(!empty($val) && strpos($val, 'attr:') === false) { + $val = 'attr:'.$val; + } + case 'ldapBase': + case 'ldapBaseUsers': + case 'ldapBaseGroups': + case 'ldapAttributesForUserSearch': + case 'ldapAttributesForGroupSearch': + $setMethod = 'setMultiLine'; + default: + $this->$setMethod($key, $val); + if(is_array($applied)) { + $applied[] = $inputkey; + } + } + } + + } + + public function readConfiguration() { + 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': + $readMethod = 'getMultiLine'; + break; + case 'ldapIgnoreNamingRules': + $readMethod = 'getSystemValue'; + $dbkey = $key; + break; + case 'ldapAgentPassword': + $readMethod = 'getPwd'; + break; + case 'ldapUserDisplayName': + case 'ldapGroupDisplayName': + $readMethod = 'getLcValue'; + break; + default: + $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; + } + } + + /** + * @brief saves the current Configuration in the database + */ + public function saveConfiguration() { + $cta = array_flip($this->getConfigTranslationArray()); + foreach($this->config as $key => $value) { + switch ($key) { + case 'ldapAgentPassword': + $value = base64_encode($value); + break; + case 'ldapBase': + case 'ldapBaseUsers': + case 'ldapBaseGroups': + case 'ldapAttributesForUserSearch': + case 'ldapAttributesForGroupSearch': + if(is_array($value)) { + $value = implode("\n", $value); + } + break; + //following options are not stored but detected, skip them + case 'ldapIgnoreNamingRules': + case 'ldapOverrideUuidAttribute': + case 'hasPagedResultSupport': + continue 2; + } + if(is_null($value)) { + $value = ''; + } + $this->saveValue($cta[$key], $value); + } + } + + protected function getMultiLine($varname) { + $value = $this->getValue($varname); + if(empty($value)) { + $value = ''; + } else { + $value = preg_split('/\r\n|\r|\n/', $value); + } + + return $value; + } + + protected function setMultiLine($varname, $value) { + if(empty($value)) { + $value = ''; + } else { + $value = preg_split('/\r\n|\r|\n/', $value); + } + + $this->setValue($varname, $value); + } + + protected function getPwd($varname) { + return base64_decode($this->getValue($varname)); + } + + protected function getLcValue($varname) { + return mb_strtolower($this->getValue($varname), 'UTF-8'); + } + + protected function getSystemValue($varname) { + //FIXME: if another system value is added, softcode the default value + return \OCP\Config::getSystemValue($varname, false); + } + + protected function getValue($varname) { + static $defaults; + 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]);
+	}
+
+	protected function setValue($varname, $value) {
+		$this->config[$varname] = $value;
+	}
+
+	protected function saveValue($varname, $value) {
+		return \OCP\Config::setAppValue('user_ldap',
+										$this->configPrefix.$varname,
+										$value);
+	}
+
+	/**
+	 * @returns an associative array with the default values. Keys are correspond
+	 * to config-value entries in the database table
+	 */
+	public function getDefaults() {
+		return array(
+			'ldap_host'							=> '',
+			'ldap_port'							=> '389',
+			'ldap_backup_host'					=> '',
+			'ldap_backup_port'					=> '',
+			'ldap_override_main_server'			=> '',
+			'ldap_dn'							=> '',
+			'ldap_agent_password'				=> '',
+			'ldap_base'							=> '',
+			'ldap_base_users'					=> '',
+			'ldap_base_groups'					=> '',
+			'ldap_userlist_filter'				=> 'objectClass=person',
+			'ldap_login_filter'					=> 'uid=%uid',
+			'ldap_group_filter'					=> 'objectClass=posixGroup',
+			'ldap_display_name'					=> 'cn',
+			'ldap_group_display_name'			=> 'cn',
+			'ldap_tls'							=> 1,
+			'ldap_nocase'						=> 0,
+			'ldap_quota_def'					=> '',
+			'ldap_quota_attr'					=> '',
+			'ldap_email_attr'					=> '',
+			'ldap_group_member_assoc_attribute'	=> 'uniqueMember',
+			'ldap_cache_ttl'					=> 600,
+			'ldap_uuid_attribute'				=> 'auto',
+			'ldap_override_uuid_attribute'		=> 0,
+			'home_folder_naming_rule'			=> '',
+			'ldap_turn_off_cert_check'			=> 0,
+			'ldap_configuration_active'			=> 1,
+			'ldap_attributes_for_user_search'	=> '',
+			'ldap_attributes_for_group_search'	=> '',
+			'ldap_expert_username_attr'			=> '',
+			'ldap_expert_uuid_attr'				=> '',
+		);
+	}
+
+	/**
+	 * @return returns an array that maps internal variable names to database fields
+	 */
+	public function getConfigTranslationArray() {
+		//TODO: merge them into one representation
+		static $array = array(
+			'ldap_host'							=> 'ldapHost',
+			'ldap_port'							=> 'ldapPort',
+			'ldap_backup_host'					=> 'ldapBackupHost',
+			'ldap_backup_port'					=> 'ldapBackupPort',
+			'ldap_override_main_server' 		=> 'ldapOverrideMainServer',
+			'ldap_dn'							=> 'ldapAgentName',
+			'ldap_agent_password'				=> 'ldapAgentPassword',
+			'ldap_base'							=> 'ldapBase',
+			'ldap_base_users'					=> 'ldapBaseUsers',
+			'ldap_base_groups'					=> 'ldapBaseGroups',
+			'ldap_userlist_filter'				=> 'ldapUserFilter',
+			'ldap_login_filter'					=> 'ldapLoginFilter',
+			'ldap_group_filter'					=> 'ldapGroupFilter',
+			'ldap_display_name'					=> 'ldapUserDisplayName',
+			'ldap_group_display_name'			=> 'ldapGroupDisplayName',
+			'ldap_tls'							=> 'ldapTLS',
+			'ldap_nocase'						=> 'ldapNoCase',
+			'ldap_quota_def'					=> 'ldapQuotaDefault',
+			'ldap_quota_attr'					=> 'ldapQuotaAttribute',
+			'ldap_email_attr'					=> 'ldapEmailAttribute',
+			'ldap_group_member_assoc_attribute'	=> 'ldapGroupMemberAssocAttr',
+			'ldap_cache_ttl'					=> 'ldapCacheTTL',
+			'home_folder_naming_rule' 			=> 'homeFolderNamingRule',
+			'ldap_turn_off_cert_check' 			=> 'turnOffCertCheck',
+			'ldap_configuration_active' 		=> 'ldapConfigurationActive',
+			'ldap_attributes_for_user_search' 	=> 'ldapAttributesForUserSearch',
+			'ldap_attributes_for_group_search'	=> 'ldapAttributesForGroupSearch',
+			'ldap_expert_username_attr' 		=> 'ldapExpertUsernameAttr',
+			'ldap_expert_uuid_attr' 			=> 'ldapExpertUUIDAttr',
+		);
+		return $array;
+	}
+
+}
\ No newline at end of file
diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php
index 468f4b004b..981d792325 100644
--- a/apps/user_ldap/lib/connection.php
+++ b/apps/user_ldap/lib/connection.php
@@ -31,48 +31,15 @@ class Connection extends LDAPUtility {
 
 	//whether connection should be kept on __destruct
 	private $dontDestruct = false;
+	private $hasPagedResultSupport = true;
 
 	//cache handler
 	protected $cache;
 
-	//settings
-	protected $config = array(
-		'ldapHost' => null,
-		'ldapPort' => null,
-		'ldapBackupHost' => null,
-		'ldapBackupPort' => null,
-		'ldapBase' => null,
-		'ldapBaseUsers' => null,
-		'ldapBaseGroups' => null,
-		'ldapAgentName' => null,
-		'ldapAgentPassword' => null,
-		'ldapTLS' => null,
-		'ldapNoCase' => null,
-		'turnOffCertCheck' => null,
-		'ldapIgnoreNamingRules' => null,
-		'ldapUserDisplayName' => null,
-		'ldapUserFilter' => null,
-		'ldapGroupFilter' => null,
-		'ldapGroupDisplayName' => null,
-		'ldapGroupMemberAssocAttr' => null,
-		'ldapLoginFilter' => null,
-		'ldapQuotaAttribute' => null,
-		'ldapQuotaDefault' => null,
-		'ldapEmailAttribute' => null,
-		'ldapCacheTTL' => null,
-		'ldapUuidUserAttribute' => 'auto',
+	//settings handler
+	protected $configuration;
 		'ldapUuidGroupAttribute' => 'auto',
-		'ldapOverrideUuidAttribute' => null,
-		'ldapOverrideMainServer' => false,
-		'ldapConfigurationActive' => false,
-		'ldapAttributesForUserSearch' => null,
-		'ldapAttributesForGroupSearch' => null,
-		'homeFolderNamingRule' => null,
-		'hasPagedResultSupport' => false,
-		'ldapExpertUsernameAttr' => null,
-		'ldapExpertUUIDUserAttr' => null,
 		'ldapExpertUUIDGroupAttr' => null,
-	);
 
 	/**
 	 * @brief Constructor
@@ -83,13 +50,14 @@ class Connection extends LDAPUtility {
 		parent::__construct($ldap);
 		$this->configPrefix = $configPrefix;
 		$this->configID = $configID;
+		$this->configuration = new Configuration($configPrefix);
 		$memcache = new \OC\Memcache\Factory();
 		if($memcache->isAvailable()) {
 			$this->cache = $memcache->create();
 		} else {
 			$this->cache = \OC_Cache::getGlobalCache();
 		}
-		$this->config['hasPagedResultSupport'] =
+		$this->hasPagedResultSupport =
 			$this->ldap->hasPagedResultSupport();
 	}
 
@@ -114,23 +82,21 @@ class Connection extends LDAPUtility {
 			$this->readConfiguration();
 		}
 
-		if(isset($this->config[$name])) {
-			return $this->config[$name];
+		if($name === 'hasPagedResultSupport') {
+			return $this->hasPagedResultSupport;
 		}
+
+		return $this->configuration->$name;
 	}
 
 	public function __set($name, $value) {
-		$changed = false;
-		//only few options are writable
-		if($name === 'ldapUuidUserAttribute' || $name === 'ldapUuidGroupAttribute') {
-			\OCP\Util::writeLog('user_ldap', 'Set config '.$name.' to  '.$value, \OCP\Util::DEBUG);
-			$this->config[$name] = $value;
+		$before = $this->configuration->$name;
+		$this->configuration->$name = $value;
+		$after = $this->configuration->$name;
+		if($before !== $after) {
 			if(!empty($this->configID)) {
-				\OCP\Config::setAppValue($this->configID, $this->configPrefix.$name, $value);
+				$this->configuration->saveConfiguration();
 			}
-			$changed = true;
-		}
-		if($changed) {
 			$this->validateConfiguration();
 		}
 	}
@@ -174,7 +140,7 @@ class Connection extends LDAPUtility {
 		if(!$this->configured) {
 			$this->readConfiguration();
 		}
-		if(!$this->config['ldapCacheTTL']) {
+		if(!$this->configuration->ldapCacheTTL) {
 			return null;
 		}
 		if(!$this->isCached($key)) {
@@ -190,7 +156,7 @@ class Connection extends LDAPUtility {
 		if(!$this->configured) {
 			$this->readConfiguration();
 		}
-		if(!$this->config['ldapCacheTTL']) {
+		if(!$this->configuration->ldapCacheTTL) {
 			return false;
 		}
 		$key = $this->getCacheKey($key);
@@ -201,110 +167,30 @@ class Connection extends LDAPUtility {
 		if(!$this->configured) {
 			$this->readConfiguration();
 		}
-		if(!$this->config['ldapCacheTTL']
-			|| !$this->config['ldapConfigurationActive']) {
+		if(!$this->configuration->ldapCacheTTL
+			|| !$this->configuration->ldapConfigurationActive) {
 			return null;
 		}
 		$key   = $this->getCacheKey($key);
 		$value = base64_encode(serialize($value));
-		$this->cache->set($key, $value, $this->config['ldapCacheTTL']);
+		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
 	}
 
 	public function clearCache() {
 		$this->cache->clear($this->getCacheKey(null));
 	}
 
-	private function getValue($varname) {
-		static $defaults;
-		if(is_null($defaults)) {
-			$defaults = $this->getDefaults();
-		}
-		return \OCP\Config::getAppValue($this->configID,
-										$this->configPrefix.$varname,
-										$defaults[$varname]);
-	}
-
-	private function setValue($varname, $value) {
-		\OCP\Config::setAppValue($this->configID,
-									$this->configPrefix.$varname,
-									$value);
-	}
-
 	/**
-	 * Special handling for reading Base Configuration
-	 *
-	 * @param $base the internal name of the config key
-	 * @param $value the value stored for the base
-	 */
-	private function readBase($base, $value) {
-		if(empty($value)) {
-			$value = '';
-		} else {
-			$value = preg_split('/\r\n|\r|\n/', $value);
-		}
-
-		$this->config[$base] = $value;
-	}
-
-	/**
-	 * Caches the general LDAP configuration.
+	 * @brief Caches the general LDAP configuration.
+	 * @param $force optional. true, if the re-read should be forced. defaults
+	 * to false.
+	 * @return null
 	 */
 	private function readConfiguration($force = false) {
 		if((!$this->configured || $force) && !is_null($this->configID)) {
-			$v = 'getValue';
-			$this->config['ldapHost']       = $this->$v('ldap_host');
-			$this->config['ldapBackupHost'] = $this->$v('ldap_backup_host');
-			$this->config['ldapPort']       = $this->$v('ldap_port');
-			$this->config['ldapBackupPort'] = $this->$v('ldap_backup_port');
-			$this->config['ldapOverrideMainServer']
-				= $this->$v('ldap_override_main_server');
-			$this->config['ldapAgentName']  = $this->$v('ldap_dn');
-			$this->config['ldapAgentPassword']
-				= base64_decode($this->$v('ldap_agent_password'));
-			$this->readBase('ldapBase',       $this->$v('ldap_base'));
-			$this->readBase('ldapBaseUsers',  $this->$v('ldap_base_users'));
-			$this->readBase('ldapBaseGroups', $this->$v('ldap_base_groups'));
-			$this->config['ldapTLS']        = $this->$v('ldap_tls');
-			$this->config['ldapNoCase']     = $this->$v('ldap_nocase');
-			$this->config['turnOffCertCheck']
-				= $this->$v('ldap_turn_off_cert_check');
-			$this->config['ldapUserDisplayName']
-				= mb_strtolower($this->$v('ldap_display_name'), 'UTF-8');
-			$this->config['ldapUserFilter']
-				= $this->$v('ldap_userlist_filter');
-			$this->config['ldapGroupFilter'] = $this->$v('ldap_group_filter');
-			$this->config['ldapLoginFilter'] = $this->$v('ldap_login_filter');
-			$this->config['ldapGroupDisplayName']
-				= mb_strtolower($this->$v('ldap_group_display_name'), 'UTF-8');
-			$this->config['ldapQuotaAttribute']
-				= $this->$v('ldap_quota_attr');
-			$this->config['ldapQuotaDefault']
-				= $this->$v('ldap_quota_def');
-			$this->config['ldapEmailAttribute']
-				= $this->$v('ldap_email_attr');
-			$this->config['ldapGroupMemberAssocAttr']
-				= $this->$v('ldap_group_member_assoc_attribute');
-			$this->config['ldapIgnoreNamingRules']
-				= \OCP\Config::getSystemValue('ldapIgnoreNamingRules', false);
-			$this->config['ldapCacheTTL']    = $this->$v('ldap_cache_ttl');
-			$this->config['ldapUuidUserAttribute']
-				= $this->$v('ldap_uuid_user_attribute');
+			$this->configuration->readConfiguration();
 			$this->config['ldapUuidGroupAttribute']
 				= $this->$v('ldap_uuid_group_attribute');
-			$this->config['ldapOverrideUuidAttribute']
-				= $this->$v('ldap_override_uuid_attribute');
-			$this->config['homeFolderNamingRule']
-				= $this->$v('home_folder_naming_rule');
-			$this->config['ldapConfigurationActive']
-				= $this->$v('ldap_configuration_active');
-			$this->config['ldapAttributesForUserSearch']
-				= preg_split('/\r\n|\r|\n/', $this->$v('ldap_attributes_for_user_search'));
-			$this->config['ldapAttributesForGroupSearch']
-				= preg_split('/\r\n|\r|\n/', $this->$v('ldap_attributes_for_group_search'));
-			$this->config['ldapExpertUsernameAttr']
-				= $this->$v('ldap_expert_username_attr');
-			$this->config['ldapExpertUUIDUserAttr']
-				= $this->$v('ldap_expert_uuid_user_attr');
 			$this->config['ldapExpertUUIDGroupAttr']
 				= $this->$v('ldap_expert_uuid_group_attr');
 
@@ -313,124 +199,32 @@ class Connection extends LDAPUtility {
 	}
 
 	/**
-	 * @return returns an array that maps internal variable names to database fields
-	 */
-	private function getConfigTranslationArray() {
-		static $array = array(
-			'ldap_host'=>'ldapHost',
-			'ldap_port'=>'ldapPort',
-			'ldap_backup_host'=>'ldapBackupHost',
-			'ldap_backup_port'=>'ldapBackupPort',
-			'ldap_override_main_server' => 'ldapOverrideMainServer',
-			'ldap_dn'=>'ldapAgentName',
-			'ldap_agent_password'=>'ldapAgentPassword',
-			'ldap_base'=>'ldapBase',
-			'ldap_base_users'=>'ldapBaseUsers',
-			'ldap_base_groups'=>'ldapBaseGroups',
-			'ldap_userlist_filter'=>'ldapUserFilter',
-			'ldap_login_filter'=>'ldapLoginFilter',
-			'ldap_group_filter'=>'ldapGroupFilter',
-			'ldap_display_name'=>'ldapUserDisplayName',
-			'ldap_group_display_name'=>'ldapGroupDisplayName',
-			'ldap_tls'=>'ldapTLS',
-			'ldap_nocase'=>'ldapNoCase',
-			'ldap_quota_def'=>'ldapQuotaDefault',
-			'ldap_quota_attr'=>'ldapQuotaAttribute',
-			'ldap_email_attr'=>'ldapEmailAttribute',
-			'ldap_group_member_assoc_attribute'=>'ldapGroupMemberAssocAttr',
-			'ldap_cache_ttl'=>'ldapCacheTTL',
-			'home_folder_naming_rule' => 'homeFolderNamingRule',
-			'ldap_turn_off_cert_check' => 'turnOffCertCheck',
-			'ldap_configuration_active' => 'ldapConfigurationActive',
-			'ldap_attributes_for_user_search' => 'ldapAttributesForUserSearch',
-			'ldap_attributes_for_group_search' => 'ldapAttributesForGroupSearch',
-			'ldap_expert_username_attr' => 'ldapExpertUsernameAttr',
-			'ldap_expert_uuid_user_attr' => 'ldapExpertUUIDUserAttr',
 			'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr',
-		);
-		return $array;
-	}
-
-	/**
 	 * @brief set LDAP configuration with values delivered by an array, not read from configuration
 	 * @param $config array that holds the config parameters in an associated array
 	 * @param &$setParameters optional; array where the set fields will be given to
 	 * @return true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
 	 */
 	public function setConfiguration($config, &$setParameters = null) {
-		if(!is_array($config)) {
-			return false;
+		if(is_null($setParameters)) {
+			$setParameters = array();
 		}
-
-		$params = $this->getConfigTranslationArray();
-
-		foreach($config as $parameter => $value) {
-			if(($parameter === 'homeFolderNamingRule'
-				|| (isset($params[$parameter])
-					&& $params[$parameter] === 'homeFolderNamingRule'))
-				&& !empty($value)) {
-				$value = 'attr:'.$value;
-			} else if (strpos($parameter, 'ldapBase') !== false
-				|| (isset($params[$parameter])
-					&& strpos($params[$parameter], 'ldapBase') !== false)) {
-				$this->readBase($params[$parameter], $value);
-				if(is_array($setParameters)) {
-					$setParameters[] = $parameter;
-				}
-				continue;
-			}
-		    if(isset($this->config[$parameter])) {
-				$this->config[$parameter] = $value;
-				if(is_array($setParameters)) {
-					$setParameters[] = $parameter;
-				}
-		    } else if(isset($params[$parameter])) {
-				$this->config[$params[$parameter]] = $value;
-				if(is_array($setParameters)) {
-					$setParameters[] = $params[$parameter];
-				}
-		    }
+		$this->configuration->setConfiguration($config, $setParameters);
+		if(count($setParameters) > 0) {
+			$this->configured = $this->validateConfiguration();
 		}
 
-		$this->configured = $this->validateConfiguration();
-
 		return $this->configured;
 	}
 
 	/**
-	 * @brief saves the current Configuration in the database
+	 * @brief saves the current Configuration in the database and empties the
+	 * cache
+	 * @return null
 	 */
 	public function saveConfiguration() {
-		$trans = array_flip($this->getConfigTranslationArray());
-		foreach($this->config as $key => $value) {
-			\OCP\Util::writeLog('user_ldap', 'LDAP: storing key '.$key.
-				' value '.print_r($value, true), \OCP\Util::DEBUG);
-			switch ($key) {
-				case 'ldapAgentPassword':
-					$value = base64_encode($value);
-					break;
-				case 'ldapBase':
-				case 'ldapBaseUsers':
-				case 'ldapBaseGroups':
-				case 'ldapAttributesForUserSearch':
-				case 'ldapAttributesForGroupSearch':
-					if(is_array($value)) {
-						$value = implode("\n", $value);
-					}
-					break;
-				case 'ldapIgnoreNamingRules':
-				case 'ldapOverrideUuidAttribute':
-				case 'ldapUuidUserAttribute':
+		$this->configuration->saveConfiguration();
 				case 'ldapUuidGroupAttribute':
-				case 'hasPagedResultSupport':
-					continue 2;
-			}
-			if(is_null($value)) {
-				$value = '';
-			}
-
-		    $this->setValue($trans[$key], $value);
-		}
 		$this->clearCache();
 	}
 
@@ -440,26 +234,181 @@ class Connection extends LDAPUtility {
 	 */
 	public function getConfiguration() {
 		$this->readConfiguration();
-		$trans = $this->getConfigTranslationArray();
-		$config = array();
-		foreach($trans as $dbKey => $classKey) {
-			if($classKey === 'homeFolderNamingRule') {
-				if(strpos($this->config[$classKey], 'attr:') === 0) {
-					$config[$dbKey] = substr($this->config[$classKey], 5);
-				} else {
-					$config[$dbKey] = '';
-				}
-				continue;
-			} else if((strpos($classKey, 'ldapBase') !== false
-						|| strpos($classKey, 'ldapAttributes') !== false)
-						&& is_array($this->config[$classKey])) {
-				$config[$dbKey] = implode("\n", $this->config[$classKey]);
-				continue;
+		$config = $this->configuration->getConfiguration();
+		$cta = $this->configuration->getConfigTranslationArray();
+		$result = array();
+		foreach($cta as $dbkey => $configkey) {
+			switch($configkey) {
+				case 'homeFolderNamingRule':
+					if(strpos($config[$configkey], 'attr:') === 0) {
+						$result[$dbkey] = substr($config[$configkey], 5);
+					} else {
+						$result[$dbkey] = '';
+					}
+					break;
+				case 'ldapBase':
+				case 'ldapBaseUsers':
+				case 'ldapBaseGroups':
+				case 'ldapAttributesForUserSearch':
+				case 'ldapAttributesForGroupSearch':
+					if(is_array($config[$configkey])) {
+						$result[$dbkey] = implode("\n", $config[$configkey]);
+						break;
+					} //else follows default
+				default:
+					$result[$dbkey] = $config[$configkey];
+			}
+		}
+		return $result;
+	}
+
+	//TODO remove if not necessary
+// 	public function getDefaults() {
+// 		return $this->configuration->getDefaults();
+// 	}
+
+	private function doSoftValidation() {
+		//if User or Group Base are not set, take over Base DN setting
+		foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
+		    $val = $this->configuration->$keyBase;
+			if(empty($val)) {
+				$obj = strpos('Users', $keyBase) !== false ? 'Users' : 'Groups';
+				\OCP\Util::writeLog('user_ldap',
+									'Base tree for '.$obj.
+									' is empty, using Base DN',
+									\OCP\Util::INFO);
+				$this->configuration->$keyBase = $this->configuration->ldapBase;
 			}
-			$config[$dbKey] = $this->config[$classKey];
 		}
 
-		return $config;
+		$groupFilter = $this->configuration->ldapGroupFilter;
+		if(empty($groupFilter)) {
+			\OCP\Util::writeLog('user_ldap',
+								'No group filter is specified, LDAP group '.
+								'feature will not be used.',
+								\OCP\Util::INFO);
+		}
+
+		$uuidOverride = $this->configuration->ldapExpertUUIDAttr;
+		if(!empty($uuidOverride)) {
+			$this->configuration->ldapUuidAttribute = $uuidOverride;
+		} else {
+			$uuidAttributes = array('auto', 'entryuuid', 'nsuniqueid',
+									'objectguid', 'guid');
+			if(!in_array($this->configuration->ldapUuidAttribute,
+						 $uuidAttributes)
+				&& (!is_null($this->configID))) {
+				$this->configuration->ldapUuidAttribute = 'auto';
+				$this->configuration->saveConfiguration();
+										'auto');
+				\OCP\Util::writeLog('user_ldap',
+									'Illegal value for the UUID Attribute, '.
+									'reset to autodetect.',
+									\OCP\Util::INFO);
+			}
+
+		}
+
+
+		$backupPort = $this->configuration->ldapBackupPort;
+		if(empty($backupPort)) {
+			$this->configuration->backupPort = $this->configuration->ldapPort;
+		}
+
+		//make sure empty search attributes are saved as simple, empty array
+		$sakeys = array('ldapAttributesForUserSearch',
+						'ldapAttributesForGroupSearch');
+		foreach($sakeys as $key) {
+			$val = $this->configuration->$key;
+			if(is_array($val) && count($val) === 1 && empty($val[0])) {
+				$this->configuration->$key = array();
+			}
+		}
+
+		if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
+			&& $this->configuration->ldapTLS) {
+			$this->configuration->ldapTLS = false;
+			\OCP\Util::writeLog('user_ldap',
+								'LDAPS (already using secure connection) and '.
+								'TLS do not work together. Switched off TLS.',
+								\OCP\Util::INFO);
+		}
+	}
+
+	private function doCriticalValidation() {
+		$configurationOK = true;
+		$errorStr = 'Configuration Error (prefix '.
+					strval($this->configPrefix).'): ';
+
+		//options that shall not be empty
+		$options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
+						 'ldapGroupDisplayName', 'ldapLoginFilter');
+		foreach($options as $key) {
+			$val = $this->configuration->$key;
+			if(empty($val)) {
+				switch($key) {
+					case 'ldapHost':
+						$subj = 'LDAP Host';
+						break;
+					case 'ldapPort':
+						$subj = 'LDAP Port';
+						break;
+					case 'ldapUserDisplayName':
+						$subj = 'LDAP User Display Name';
+						break;
+					case 'ldapGroupDisplayName':
+						$subj = 'LDAP Group Display Name';
+						break;
+					case 'ldapLoginFilter':
+						$subj = 'LDAP Login Filter';
+						break;
+					default:
+						$subj = $key;
+						break;
+				}
+				$configurationOK = false;
+				\OCP\Util::writeLog('user_ldap',
+									$errorStr.'No '.$subj.' given!',
+									\OCP\Util::WARN);
+			}
+		}
+
+		//combinations
+		$agent = $this->configuration->ldapAgentName;
+		$pwd = $this->configuration->ldapAgentPassword;
+		if((empty($agent) && !empty($pwd)) || (!empty($agent) && empty($pwd))) {
+			\OCP\Util::writeLog('user_ldap',
+								$errorStr.'either no password is given for the'.
+								'user agent or a password is given, but not an'.
+								'LDAP agent.',
+				\OCP\Util::WARN);
+			$configurationOK = false;
+		}
+
+		$base = $this->configuration->ldapBase;
+		$baseUsers = $this->configuration->ldapBaseUsers;
+		$baseGroups = $this->configuration->ldapBaseGroups;
+
+		if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
+			\OCP\Util::writeLog('user_ldap',
+								$errorStr.'Not a single Base DN given.',
+								\OCP\Util::WARN);
+			$configurationOK = false;
+		}
+
+		if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
+		   === false) {
+			\OCP\Util::writeLog('user_ldap',
+								$errorStr.'login filter does not contain %uid '.
+								'place holder.',
+								\OCP\Util::WARN);
+			$configurationOK = false;
+		}
+
+		}
+		if(!empty($this->config['ldapExpertUUIDGroupAttr'])) {
+			$this->config['ldapUuidGroupAttribute'] = $this->config['ldapExpertUUIDGroupAttr'];
+		return $configurationOK;
 	}
 
 	/**
@@ -469,162 +418,21 @@ class Connection extends LDAPUtility {
 	private function validateConfiguration() {
 		// first step: "soft" checks: settings that are not really
 		// necessary, but advisable. If left empty, give an info message
-		if(empty($this->config['ldapBaseUsers'])) {
-			\OCP\Util::writeLog('user_ldap', 'Base tree for Users is empty, using Base DN', \OCP\Util::INFO);
-			$this->config['ldapBaseUsers'] = $this->config['ldapBase'];
-		}
-		if(empty($this->config['ldapBaseGroups'])) {
-			\OCP\Util::writeLog('user_ldap', 'Base tree for Groups is empty, using Base DN', \OCP\Util::INFO);
-			$this->config['ldapBaseGroups'] = $this->config['ldapBase'];
-		}
-		if(empty($this->config['ldapGroupFilter']) && empty($this->config['ldapGroupMemberAssocAttr'])) {
-			\OCP\Util::writeLog('user_ldap',
-				'No group filter is specified, LDAP group feature will not be used.',
-				\OCP\Util::INFO);
-		}
-		$uuidAttributes = array(
-			'auto', 'entryuuid', 'nsuniqueid', 'objectguid', 'guid');
-		$uuidSettings = array(
-						'ldapUuidUserAttribute' => 'ldapExpertUUIDUserAttr',
-						'ldapUuidGroupAttribute' => 'ldapExpertUUIDGroupAttr');
-		$cta = array_flip($this->getConfigTranslationArray());
-		foreach($uuidSettings as $defaultKey => $overrideKey) {
-			if( !in_array($this->config[$defaultKey], $uuidAttributes)
-				&& is_null($this->config[$overrideKey])
-				&& !is_null($this->configID)) {
-				\OCP\Config::setAppValue($this->configID,
-										$this->configPrefix.$cta[$defaultKey],
-										'auto');
-				\OCP\Util::writeLog('user_ldap',
-					'Illegal value for'.$defaultKey.', reset to autodetect.',
-					\OCP\Util::DEBUG);
-			}
-		}
+		$this->doSoftValidation();
 
-		if(empty($this->config['ldapBackupPort'])) {
-			//force default
-			$this->config['ldapBackupPort'] = $this->config['ldapPort'];
-		}
-		foreach(array('ldapAttributesForUserSearch', 'ldapAttributesForGroupSearch') as $key) {
-			if(is_array($this->config[$key])
-				&& count($this->config[$key]) === 1
-				&& empty($this->config[$key][0])) {
-				$this->config[$key] = array();
-			}
-		}
-		if((strpos($this->config['ldapHost'], 'ldaps') === 0)
-			&& $this->config['ldapTLS']) {
-			$this->config['ldapTLS'] = false;
-			\OCP\Util::writeLog('user_ldap',
-				'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
-				\OCP\Util::INFO);
-		}
-
-		//second step: critical checks. If left empty or filled wrong, set as unconfigured and give a warning.
-		$configurationOK = true;
-		if(empty($this->config['ldapHost'])) {
-			\OCP\Util::writeLog('user_ldap', 'No LDAP host given, won`t connect.', \OCP\Util::WARN);
-			$configurationOK = false;
-		}
-		if(empty($this->config['ldapPort'])) {
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Port given, won`t connect.', \OCP\Util::WARN);
-			$configurationOK = false;
-		}
-		if((empty($this->config['ldapAgentName']) && !empty($this->config['ldapAgentPassword']))
-			|| (!empty($this->config['ldapAgentName']) && empty($this->config['ldapAgentPassword']))) {
-			\OCP\Util::writeLog('user_ldap',
-				'Either no password given for the user agent or a password is given, but no LDAP agent; won`t connect.',
-				\OCP\Util::WARN);
-			$configurationOK = false;
-		}
-		//TODO: check if ldapAgentName is in DN form
-		if(empty($this->config['ldapBase'])
-			&& (empty($this->config['ldapBaseUsers'])
-			&& empty($this->config['ldapBaseGroups']))) {
-			\OCP\Util::writeLog('user_ldap', 'No Base DN given, won`t connect.', \OCP\Util::WARN);
-			$configurationOK = false;
-		}
-		if(empty($this->config['ldapUserDisplayName'])) {
-			\OCP\Util::writeLog('user_ldap',
-				'No user display name attribute specified, won`t connect.',
-				\OCP\Util::WARN);
-			$configurationOK = false;
-		}
-		if(empty($this->config['ldapGroupDisplayName'])) {
-			\OCP\Util::writeLog('user_ldap',
-				'No group display name attribute specified, won`t connect.',
-				\OCP\Util::WARN);
-			$configurationOK = false;
-		}
-		if(empty($this->config['ldapLoginFilter'])) {
-			\OCP\Util::writeLog('user_ldap', 'No login filter specified, won`t connect.', \OCP\Util::WARN);
-			$configurationOK = false;
-		}
-		if(mb_strpos($this->config['ldapLoginFilter'], '%uid', 0, 'UTF-8') === false) {
-			\OCP\Util::writeLog('user_ldap',
-				'Login filter does not contain %uid place holder, won`t connect.',
-				\OCP\Util::WARN);
-			\OCP\Util::writeLog('user_ldap', 'Login filter was ' . $this->config['ldapLoginFilter'], \OCP\Util::DEBUG);
-			$configurationOK = false;
-		}
-
-		if(!empty($this->config['ldapExpertUUIDUserAttr'])) {
-			$this->config['ldapUuidUserAttribute'] = $this->config['ldapExpertUUIDUserAttr'];
-		}
-		if(!empty($this->config['ldapExpertUUIDGroupAttr'])) {
-			$this->config['ldapUuidGroupAttribute'] = $this->config['ldapExpertUUIDGroupAttr'];
-		}
-
-		return $configurationOK;
-	}
-
-	/**
-	 * @returns an associative array with the default values. Keys are correspond
-	 * to config-value entries in the database table
-	 */
-	static public function getDefaults() {
-		return array(
-			'ldap_host'                         => '',
-			'ldap_port'                         => '389',
-			'ldap_backup_host'                  => '',
-			'ldap_backup_port'                  => '',
-			'ldap_override_main_server'         => '',
-			'ldap_dn'                           => '',
-			'ldap_agent_password'               => '',
-			'ldap_base'                         => '',
-			'ldap_base_users'                   => '',
-			'ldap_base_groups'                  => '',
-			'ldap_userlist_filter'              => 'objectClass=person',
-			'ldap_login_filter'                 => 'uid=%uid',
-			'ldap_group_filter'                 => 'objectClass=posixGroup',
-			'ldap_display_name'                 => 'cn',
-			'ldap_group_display_name'           => 'cn',
-			'ldap_tls'                          => 1,
-			'ldap_nocase'                       => 0,
-			'ldap_quota_def'                    => '',
-			'ldap_quota_attr'                   => '',
-			'ldap_email_attr'                   => '',
-			'ldap_group_member_assoc_attribute' => 'uniqueMember',
-			'ldap_cache_ttl'                    => 600,
-			'ldap_uuid_user_attribute'          => 'auto',
+		//second step: critical checks. If left empty or filled wrong, set as
+		//unconfigured and give a warning.
+		return $this->doCriticalValidation();
 			'ldap_uuid_group_attribute'         => 'auto',
-			'ldap_override_uuid_attribute'		=> 0,
-			'home_folder_naming_rule'           => '',
-			'ldap_turn_off_cert_check'			=> 0,
-			'ldap_configuration_active'			=> 1,
-			'ldap_attributes_for_user_search'	=> '',
-			'ldap_attributes_for_group_search'	=> '',
-			'ldap_expert_username_attr'         => '',
-			'ldap_expert_uuid_user_attr'        => '',
 			'ldap_expert_uuid_group_attr'       => '',
-		);
 	}
 
+
 	/**
 	 * Connects and Binds to LDAP
 	 */
 	private function establishConnection() {
-		if(!$this->config['ldapConfigurationActive']) {
+		if(!$this->configuration->ldapConfigurationActive) {
 			return null;
 		}
 		static $phpLDAPinstalled = true;
@@ -632,29 +440,36 @@ class Connection extends LDAPUtility {
 			return false;
 		}
 		if(!$this->configured) {
-			\OCP\Util::writeLog('user_ldap', 'Configuration is invalid, cannot connect', \OCP\Util::WARN);
+			\OCP\Util::writeLog('user_ldap',
+								'Configuration is invalid, cannot connect',
+								\OCP\Util::WARN);
 			return false;
 		}
 		if(!$this->ldapConnectionRes) {
 			if(!$this->ldap->areLDAPFunctionsAvailable()) {
 				$phpLDAPinstalled = false;
 				\OCP\Util::writeLog('user_ldap',
-					'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
-					\OCP\Util::ERROR);
+									'function ldap_connect is not available. Make '.
+									'sure that the PHP ldap module is installed.',
+									\OCP\Util::ERROR);
 
 				return false;
 			}
-			if($this->config['turnOffCertCheck']) {
+			if($this->configuration->turnOffCertCheck) {
 				if(putenv('LDAPTLS_REQCERT=never')) {
 					\OCP\Util::writeLog('user_ldap',
 						'Turned off SSL certificate validation successfully.',
 						\OCP\Util::WARN);
 				} else {
-					\OCP\Util::writeLog('user_ldap', 'Could not turn off SSL certificate validation.', \OCP\Util::WARN);
+					\OCP\Util::writeLog('user_ldap',
+										'Could not turn off SSL certificate validation.',
+										\OCP\Util::WARN);
 				}
 			}
-			if(!$this->config['ldapOverrideMainServer'] && !$this->getFromCache('overrideMainServer')) {
-				$this->doConnect($this->config['ldapHost'], $this->config['ldapPort']);
+			if(!$this->configuration->ldapOverrideMainServer
+			   && !$this->getFromCache('overrideMainServer')) {
+				$this->doConnect($this->configuration->ldapHost,
+								 $this->configuration->ldapPort);
 				$bindStatus = $this->bind();
 				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
 							$this->ldap->errno($this->ldapConnectionRes) : -1;
@@ -665,9 +480,10 @@ class Connection extends LDAPUtility {
 
 			//if LDAP server is not reachable, try the Backup (Replica!) Server
 			if((!$bindStatus && ($error !== 0))
-				|| $this->config['ldapOverrideMainServer']
+				|| $this->configuration->ldapOverrideMainServer
 				|| $this->getFromCache('overrideMainServer')) {
-					$this->doConnect($this->config['ldapBackupHost'], $this->config['ldapBackupPort']);
+					$this->doConnect($this->configuration->ldapBackupHost,
+									 $this->configuration->ldapBackupPort);
 					$bindStatus = $this->bind();
 					if(!$bindStatus && $error === -1) {
 						//when bind to backup server succeeded and failed to main server,
@@ -690,7 +506,7 @@ class Connection extends LDAPUtility {
 		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
 		if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
 			if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
-				if($this->config['ldapTLS']) {
+				if($this->configuration->ldapTLS) {
 					$this->ldap->startTls($this->ldapConnectionRes);
 				}
 			}
@@ -702,7 +518,7 @@ class Connection extends LDAPUtility {
 	 */
 	public function bind() {
 		static $getConnectionResourceAttempt = false;
-		if(!$this->config['ldapConfigurationActive']) {
+		if(!$this->configuration->ldapConfigurationActive) {
 			return false;
 		}
 		if($getConnectionResourceAttempt) {
@@ -716,8 +532,8 @@ class Connection extends LDAPUtility {
 			return false;
 		}
 		$ldapLogin = @$this->ldap->bind($cr,
-										$this->config['ldapAgentName'],
-										$this->config['ldapAgentPassword']);
+										$this->configuration->ldapAgentName,
+										$this->configuration->ldapAgentPassword);
 		if(!$ldapLogin) {
 			\OCP\Util::writeLog('user_ldap',
 				'Bind failed: ' . $this->ldap->errno($cr) . ': ' . $this->ldap->error($cr),
diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php
index f20bc19118..45d874eff0 100644
--- a/apps/user_ldap/settings.php
+++ b/apps/user_ldap/settings.php
@@ -37,7 +37,8 @@ $tmpl->assign('serverConfigurationPrefixes', $prefixes);
 $tmpl->assign('serverConfigurationHosts', $hosts);
 
 // assign default values
-$defaults = \OCA\user_ldap\lib\Connection::getDefaults();
+$config = new \OCA\user_ldap\lib\Configuration('', false);
+$defaults = $config->getDefaults();
 foreach($defaults as $key => $default) {
     $tmpl->assign($key.'_default', $default);
 }

From 53db1fe5ac24f569918fa7cfb3dcd67054099836 Mon Sep 17 00:00:00 2001
From: Arthur Schiwon 
Date: Fri, 27 Sep 2013 18:30:59 +0200
Subject: [PATCH 03/82] 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'));?>
From 3fe400a3cabfe70c7fe48720d6b5283baedc3b2d Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 27 Sep 2013 18:41:25 +0200 Subject: [PATCH 04/82] Simplify WizardResult --- apps/user_ldap/lib/wizard.php | 1 - apps/user_ldap/lib/wizardresult.php | 8 -------- 2 files changed, 9 deletions(-) diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index ff81df8bf0..04802205cd 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -81,7 +81,6 @@ class Wizard extends LDAPUtility { \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; } } diff --git a/apps/user_ldap/lib/wizardresult.php b/apps/user_ldap/lib/wizardresult.php index 1d683fee97..2140f654fd 100644 --- a/apps/user_ldap/lib/wizardresult.php +++ b/apps/user_ldap/lib/wizardresult.php @@ -25,7 +25,6 @@ namespace OCA\user_ldap\lib; class WizardResult { protected $changes = array(); - protected $specifics = array(); public function addChange($key, $value) { $this->changes[$key] = $value; @@ -35,16 +34,9 @@ class WizardResult { 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 From 7c60384f20a1f5b9dea2288d8b39c5f556b4348f Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Sun, 29 Sep 2013 23:51:26 +0200 Subject: [PATCH 05/82] ignore autodetect-attribute on public save method --- apps/user_ldap/lib/configuration.php | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index e9e494c2f6..ed6f384da8 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -201,6 +201,7 @@ class Configuration { case 'ldapIgnoreNamingRules': case 'ldapOverrideUuidAttribute': case 'hasPagedResultSupport': + case 'ldapUuidAttribute': continue 2; } if(is_null($value)) { From 8290929aa6fcb1e62e79d7acf8bf310c8d6f94d7 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Sun, 29 Sep 2013 23:53:14 +0200 Subject: [PATCH 06/82] LDAP Wizard: autodetect base DN --- apps/user_ldap/ajax/wizard.php | 1 + apps/user_ldap/js/settings.js | 71 +++++++++++++++++++++----- apps/user_ldap/lib/helper.php | 21 ++++++++ apps/user_ldap/lib/ildapwrapper.php | 8 +++ apps/user_ldap/lib/ldap.php | 4 ++ apps/user_ldap/lib/wizard.php | 79 ++++++++++++++++++++++++++--- 6 files changed, 162 insertions(+), 22 deletions(-) diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php index 53c66c34f8..7df922f17a 100644 --- a/apps/user_ldap/ajax/wizard.php +++ b/apps/user_ldap/ajax/wizard.php @@ -45,6 +45,7 @@ $wizard = new \OCA\user_ldap\lib\Wizard($configuration, $ldapWrapper); switch($action) { case 'guessPortAndTLS': + case 'guessBaseDN': try { $result = $wizard->$action(); if($result !== false) { diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 97470b4da4..45b1a9239f 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -126,7 +126,7 @@ var LdapConfiguration = { var LdapWizard = { checkPortInfoShown: false, - changeIndicators: {}, + saveBlacklist: {}, ajax: function(param, fnOnSuccess, fnOnError) { $.post( @@ -144,10 +144,36 @@ var LdapWizard = { applyChanges: function (result) { for (id in result.changes) { + LdapWizard.saveBlacklist[id] = true; $('#'+id).val(result.changes[id]); } }, + checkBaseDN: function() { + host = $('#ldap_host').val(); + user = $('#ldap_dn').val(); + pass = $('#ldap_agent_password').val(); + + if(host && user && pass) { + param = 'action=guessBaseDN'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.ajax(param, + function(result) { + LdapWizard.applyChanges(result); + if($('#ldap_base').val()) { + $('#ldap_base').removeClass('hidden'); + LdapWizard.hideInfoBox(); + } + }, + function (result) { + $('#ldap_base').removeClass('hidden'); + LdapWizard.showInfoBox('Please specify a port'); + } + ); + } + }, + checkPort: function() { host = $('#ldap_host').val(); user = $('#ldap_dn').val(); @@ -162,30 +188,45 @@ var LdapWizard = { LdapWizard.applyChanges(result); if($('#ldap_port').val()) { $('#ldap_port').removeClass('hidden'); - if(LdapWizard.checkPortInfoShown) { - $('#ldapWizard1 .ldapWizardInfo').addClass('hidden'); - LdapWizard.checkPortInfoShown = false; - } + LdapWizard.hideInfoBox(); } }, function (result) { $('#ldap_port').removeClass('hidden'); - $('#ldapWizard1 .ldapWizardInfo').text(t('user_ldap', - 'Please specify a port')); - $('#ldapWizard1 .ldapWizardInfo').removeClass('hidden'); - LdapWizard.checkPortInfoShown = true; + LdapWizard.showInfoBox('Please specify the BaseDN'); } ); } }, + hideInfoBox: function() { + if(LdapWizard.checkInfoShown) { + $('#ldapWizard1 .ldapWizardInfo').addClass('hidden'); + LdapWizard.checkInfoShown = false; + } + }, + init: function() { if($('#ldap_port').val()) { $('#ldap_port').removeClass('hidden'); } }, + processChanges: function(triggerObj) { + if(triggerObj.id == 'ldap_host' + || triggerObj.id == 'ldap_port' + || triggerObj.id == 'ldap_dn' + || triggerObj.id == 'ldap_agent_password') { + LdapWizard.checkPort(); + LdapWizard.checkBaseDN(); + } + }, + save: function(inputObj) { + if(LdapWizard.saveBlacklist.hasOwnProperty(inputObj.id)) { + delete LdapWizard.saveBlacklist[inputObj.id]; + return; + } param = 'cfgkey='+inputObj.id+ '&cfgval='+$(inputObj).val()+ '&action=save'+ @@ -196,16 +237,18 @@ var LdapWizard = { param, function(result) { if(result.status == 'success') { - if(inputObj.id == 'ldap_host' - || inputObj.id == 'ldap_dn' - || inputObj.id == 'ldap_agent_password') { - LdapWizard.checkPort(); - } + LdapWizard.processChanges(inputObj); } else { // alert('Oooooooooooh :('); } } ); + }, + + showInfoBox: function(text) { + $('#ldapWizard1 .ldapWizardInfo').text(t('user_ldap', text)); + $('#ldapWizard1 .ldapWizardInfo').removeClass('hidden'); + LdapWizard.checkInfoShown = true; } }; diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php index 4c9dd07a12..09f646921e 100644 --- a/apps/user_ldap/lib/helper.php +++ b/apps/user_ldap/lib/helper.php @@ -161,4 +161,25 @@ class Helper { return true; } + + /** + * @brief extractsthe domain from a given URL + * @param $url the URL + * @return mixed, domain as string on success, false otherwise + */ + static public function getDomainFromURL($url) { + $uinfo = parse_url($url); + if(!is_array($uinfo)) { + return false; + } + + $domain = false; + if(isset($uinfo['host'])) { + $domain = $uinfo['host']; + } else if(isset($uinfo['path'])) { + $domain = $uinfo['path']; + } + + return $domain; + } } diff --git a/apps/user_ldap/lib/ildapwrapper.php b/apps/user_ldap/lib/ildapwrapper.php index 9e6bd56ef2..5e12c7c63b 100644 --- a/apps/user_ldap/lib/ildapwrapper.php +++ b/apps/user_ldap/lib/ildapwrapper.php @@ -67,6 +67,14 @@ interface ILDAPWrapper { */ public function controlPagedResultResponse($link, $result, &$cookie); + /** + * @brief Count the number of entries in a search + * @param $link LDAP link resource + * @param $result LDAP result resource + * @return mixed, number of results on success, false otherwise + */ + public function countEntries($link, $result); + /** * @brief Return the LDAP error number of the last LDAP command * @param $link LDAP link resource diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php index b63e969912..13314462b8 100644 --- a/apps/user_ldap/lib/ldap.php +++ b/apps/user_ldap/lib/ldap.php @@ -49,6 +49,10 @@ class LDAP implements ILDAPWrapper { $isCritical, $cookie); } + public function countEntries($link, $result) { + return $this->invokeLDAPMethod('count_entries', $link, $result); + } + public function errno($link) { return $this->invokeLDAPMethod('errno', $link); } diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index 04802205cd..ad71fd10f6 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -89,6 +89,10 @@ class Wizard extends LDAPUtility { return false; } + /** + * @brief tries to determine a base dn from User DN or LDAP Host + * @returns mixed WizardResult on success, false otherwise + */ public function guessBaseDN() { if(!$this->checkRequirements(array('ldapHost', 'ldapAgentName', @@ -97,10 +101,52 @@ class Wizard extends LDAPUtility { ))) { return false; } - $cr = $this->getConnection(); - if(!$cr) { + + //check whether a DN is given in the agent name (99.9% of all cases) + $base = null; + $i = stripos($this->configuration->ldapAgentName, 'dc='); + if($i !== false) { + $base = substr($this->configuration->ldapAgentName, $i); + + if($this->testBaseDN($base)) { + $this->applyFind('ldap_base', $base); + $this->applyFind('ldap_base_users', $base); + $this->applyFind('ldap_base_groups', $base); + return $this->result; + } + } + + //this did not help :( + //Let's see whether we can parse the Host URL and convert the domain to + //a base DN + $domain = Helper::getDomainFromURL($this->configuration->ldapHost); + if(!$domain) { return false; } + + $dparts = explode('.', $domain); + $base2 = implode('dc=', $dparts); + if($base !== $base2 && $this->testBaseDN($base2)) { + $this->applyFind('ldap_base', $base2); + $this->applyFind('ldap_base_users', $base2); + $this->applyFind('ldap_base_groups', $base2); + return $this->result; + } + + return false; + } + + /** + * @brief sets the found value for the configuration key in the WizardResult + * as well as in the Configuration instance + * @param $key the configuration key + * @param $value the (detected) value + * @return null + * + */ + private function applyFind($key, $value) { + $this->result->addChange($key, $value); + $this->configuration->setConfiguration(array($key => $value)); } /** @@ -116,15 +162,32 @@ class Wizard extends LDAPUtility { 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); + $this->applyFind('ldap_host', $host); + $this->applyFind('ldap_port', $port); } } + /** + * @brief Checks whether for a given BaseDN results will be returned + * @param $base the BaseDN to test + * @return bool true on success, false otherwise + */ + private function testBaseDN($base) { + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + + //base is there, let's validate it. If we search for anything, we should + //get a result set > 0 on a proper base + $rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1); + if(!$this->ldap->isResource($rr)) { + return false; + } + $entries = $this->ldap->countEntries($cr, $rr); + return ($entries !== false) && ($entries > 0); + } + /** * Connects and Binds to an LDAP Server * @param $port the port to connect with From 3b1822cf91407f243f18311b8abc273ad2eb1b11 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 4 Oct 2013 16:33:37 +0200 Subject: [PATCH 07/82] LDAP Wizard: add detection, load and save of LDAP objectClasses for filter purposes --- apps/user_ldap/ajax/wizard.php | 1 + apps/user_ldap/css/settings.css | 20 ++- apps/user_ldap/js/settings.js | 81 ++++++++-- apps/user_ldap/lib/configuration.php | 20 +-- apps/user_ldap/lib/ildapwrapper.php | 16 ++ apps/user_ldap/lib/ldap.php | 8 + apps/user_ldap/lib/wizard.php | 150 +++++++++++++++++- apps/user_ldap/lib/wizardresult.php | 11 ++ apps/user_ldap/settings.php | 12 +- .../templates/part.wizard-server.php | 4 +- .../templates/part.wizard-userfilter.php | 51 ++++++ apps/user_ldap/templates/settings.php | 2 +- 12 files changed, 343 insertions(+), 33 deletions(-) create mode 100644 apps/user_ldap/templates/part.wizard-userfilter.php diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php index 7df922f17a..807f04ca69 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 aa6c4687cf..f6c9f75633 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 45b1a9239f..88f63e25ca 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 ed6f384da8..e67e0d8d00 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 5e12c7c63b..20587cba7d 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 13314462b8..bc96319172 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 ad71fd10f6..170af44e11 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 2140f654fd..4c3b563c0c 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 8a418a6500..ebba1dbd3a 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 ae0a7e650c..c6900fd24e 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 0000000000..b58784b680 --- /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 9ca9673ada..f63a4bdef1 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -2,7 +2,7 @@
    $title) { ?> -
  • +
  • Expert
  • Advanced
  • From ee1b59734586cd4b539fe759f81ba1925e21f4f1 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 4 Oct 2013 16:44:26 +0200 Subject: [PATCH 08/82] css cleanup --- apps/user_ldap/css/settings.css | 2 +- apps/user_ldap/templates/part.wizard-server.php | 2 +- apps/user_ldap/templates/part.wizardcontrols.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/user_ldap/css/settings.css b/apps/user_ldap/css/settings.css index f6c9f75633..2f12108b09 100644 --- a/apps/user_ldap/css/settings.css +++ b/apps/user_ldap/css/settings.css @@ -19,7 +19,7 @@ height: 15px; } -.hidden, .invisible { +.invisible { visibility: hidden; } diff --git a/apps/user_ldap/templates/part.wizard-server.php b/apps/user_ldap/templates/part.wizard-server.php index c6900fd24e..92c0217750 100644 --- a/apps/user_ldap/templates/part.wizard-server.php +++ b/apps/user_ldap/templates/part.wizard-server.php @@ -60,7 +60,7 @@

    -

    -

    -

    -

    -

    - " />

    -

    - " />

    -

    - " />

    - - -

    t('Connection Settings'));?>

    -

    +

    + " />

    -

    >


    @@ -100,7 +51,7 @@
    -
    +

    t('Internal Username'));?>

    t('By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all *DAV services. With this setting, the default behavior can be overridden. To achieve a similar behavior as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users.'));?>

    From f6b60c65866ff07f28ed06dd56aa784b62f1f0c8 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 9 Oct 2013 23:03:45 +0200 Subject: [PATCH 19/82] LDAP Wizard: add forgotten templates --- .../templates/part.wizard-groupfilter.php | 42 +++++++++++++++++++ .../templates/part.wizard-loginfilter.php | 37 ++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 apps/user_ldap/templates/part.wizard-groupfilter.php create mode 100644 apps/user_ldap/templates/part.wizard-loginfilter.php diff --git a/apps/user_ldap/templates/part.wizard-groupfilter.php b/apps/user_ldap/templates/part.wizard-groupfilter.php new file mode 100644 index 0000000000..d36c171b63 --- /dev/null +++ b/apps/user_ldap/templates/part.wizard-groupfilter.php @@ -0,0 +1,42 @@ +
    +
    +

    + t('Limit the access to ownCloud to groups meeting this criteria:'));?> +

    +

    + + + +

    +

    + + + +

    +

    + +

    + +

    +

    +

    +

    + 0 t('group(s) found'));?> +

    + +
    +
    \ No newline at end of file diff --git a/apps/user_ldap/templates/part.wizard-loginfilter.php b/apps/user_ldap/templates/part.wizard-loginfilter.php new file mode 100644 index 0000000000..4cad659544 --- /dev/null +++ b/apps/user_ldap/templates/part.wizard-loginfilter.php @@ -0,0 +1,37 @@ +
    +
    +

    + t('What attribute shall be used as login name:'));?> +

    +

    + + + +

    +

    + + + +

    +

    + + + +

    +

    +

    +

    + + +
    +
    \ No newline at end of file From 3ae696f8438d4ecd0ec374ef73df7911b0d0a1db Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 9 Oct 2013 23:04:06 +0200 Subject: [PATCH 20/82] Wizard does not need data-defaults here --- apps/user_ldap/templates/part.wizard-server.php | 7 +------ .../user_ldap/templates/part.wizard-userfilter.php | 14 ++------------ 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/apps/user_ldap/templates/part.wizard-server.php b/apps/user_ldap/templates/part.wizard-server.php index 92c0217750..01dd8d0fcb 100644 --- a/apps/user_ldap/templates/part.wizard-server.php +++ b/apps/user_ldap/templates/part.wizard-server.php @@ -27,14 +27,12 @@
    @@ -43,7 +41,6 @@
    @@ -52,7 +49,6 @@
    @@ -62,8 +58,7 @@
    diff --git a/apps/user_ldap/templates/part.wizard-userfilter.php b/apps/user_ldap/templates/part.wizard-userfilter.php index 879af95b2b..85c870b5de 100644 --- a/apps/user_ldap/templates/part.wizard-userfilter.php +++ b/apps/user_ldap/templates/part.wizard-userfilter.php @@ -1,49 +1,39 @@
    -

    t('Limit the access to ownCloud to users meeting this criteria:'));?>

    -

    -

    -

    - -

    -

    0 t('user(s) found'));?>

    From c975021a67d5f714490906bb6ee5cdd47b62e686 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 9 Oct 2013 23:16:26 +0200 Subject: [PATCH 21/82] clean up --- apps/user_ldap/settings.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index 77bd26a52d..5d3306246d 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -65,10 +65,6 @@ for($i = 0; $i < count($wizTabs); $i++) { $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); $defaults = $config->getDefaults(); From 5606b60f36e0f6c34c18694a2a19f6446c6b2618 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 9 Oct 2013 23:27:20 +0200 Subject: [PATCH 22/82] LDAP: set displayname as default attribute for user display name --- apps/user_ldap/lib/configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index 14a14890bd..e1e5d2b65d 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -306,7 +306,7 @@ class Configuration { 'ldap_group_filter' => 'objectClass=posixGroup', 'ldap_groupfilter_objectclass' => '', 'ldap_groupfilter_groups' => '', - 'ldap_display_name' => 'cn', + 'ldap_display_name' => 'displayName', 'ldap_group_display_name' => 'cn', 'ldap_tls' => 1, 'ldap_nocase' => 0, From 109ddde944ae17621c4680d9c7172eb585b1626d Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 10 Oct 2013 01:21:05 +0200 Subject: [PATCH 23/82] Wizard: autodetection of group-member-assoc attribute --- apps/user_ldap/ajax/wizard.php | 1 + apps/user_ldap/js/settings.js | 16 ++++++++ apps/user_ldap/lib/wizard.php | 61 +++++++++++++++++++++++++++++ apps/user_ldap/lib/wizardresult.php | 7 +++- 4 files changed, 84 insertions(+), 1 deletion(-) diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php index ebeedaee95..c90efdf7e9 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 'determineGroupMemberAssoc': case 'determineUserObjectClasses': case 'determineGroupObjectClasses': case 'determineGroupsForUsers': diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 049df9b8b1..6b1f363bba 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -226,6 +226,7 @@ var LdapWizard = { LdapWizard.countUsers(); } else if(type == 'group') { LdapWizard.countGroups(); + LdapWizard.detectGroupMemberAssoc(); } }, function (result) { @@ -256,6 +257,20 @@ var LdapWizard = { LdapWizard._countThings('countUsers'); }, + detectGroupMemberAssoc: function() { + param = 'action=determineGroupMemberAssoc'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.ajax(param, + function(result) { + //pure background story + }, + function (result) { + // error handling + } + ); + }, + findAttributes: function() { param = 'action=determineAttributes'+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); @@ -395,6 +410,7 @@ var LdapWizard = { LdapWizard.countUsers(); } else if(triggerObj.id == 'ldap_group_filter') { LdapWizard.countGroups(); + LdapWizard.detectGroupMemberAssoc(); } if(triggerObj.id == 'ldap_loginfilter_username' diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index 86cf8d75af..2538fe4a2c 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -230,6 +230,26 @@ class Wizard extends LDAPUtility { return $this->result; } + public function determineGroupMemberAssoc() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapGroupFilter', + ))) { + return false; + } + $attribute = $this->detectGroupMemberAssoc(); + if($attribute === false) { + return false; + } + $this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute)); + //so it will be saved on destruct + $this->result->markChange(); + + return $this->result; + } + /** * @brief detects the available object classes * @returns the instance's WizardResult instance @@ -462,6 +482,47 @@ class Wizard extends LDAPUtility { } } + /** + * @brief tries to detect the group member association attribute which is + * one of 'uniqueMember', 'memberUid', 'member' + * @return mixed, string with the attribute name, false on error + */ + private function detectGroupMemberAssoc() { + $possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'unfugasdfasdfdfa'); + $filter = $this->configuration->ldapGroupFilter; + if(empty($filter)) { + return false; + } + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + $base = $this->configuration->ldapBase[0]; + $rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs); + if(!$this->ldap->isResource($rr)) { + return false; + } + $er = $this->ldap->firstEntry($cr, $rr); + while(is_resource($er)) { + $dn = $this->ldap->getDN($cr, $er); + $attrs = $this->ldap->getAttributes($cr, $er); + $result = array(); + for($i = 0; $i < count($possibleAttrs); $i++) { + if(isset($attrs[$possibleAttrs[$i]])) { + $result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count']; + } + } + if(!empty($result)) { + natsort($result); + return key($result); + } + + $er = $this->ldap->nextEntry($cr, $er); + } + + return false; + } + /** * @brief Checks whether for a given BaseDN results will be returned * @param $base the BaseDN to test diff --git a/apps/user_ldap/lib/wizardresult.php b/apps/user_ldap/lib/wizardresult.php index 4c3b563c0c..542f106cad 100644 --- a/apps/user_ldap/lib/wizardresult.php +++ b/apps/user_ldap/lib/wizardresult.php @@ -26,11 +26,16 @@ namespace OCA\user_ldap\lib; class WizardResult { protected $changes = array(); protected $options = array(); + protected $markedChange = false; public function addChange($key, $value) { $this->changes[$key] = $value; } + public function markChange() { + $this->markedChange = true; + } + public function addOptions($key, $values) { if(!is_array($values)) { $values = array($values); @@ -39,7 +44,7 @@ class WizardResult { } public function hasChanges() { - return count($this->changes) > 0; + return (count($this->changes) > 0 || $this->markedChange); } public function getResultArray() { From 7ad8319488cc35c849dba9bae15c1784acbd68d1 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 10 Oct 2013 15:24:10 +0200 Subject: [PATCH 24/82] do not instantiate LDAP Proxy if there are no active configurations --- apps/user_ldap/appinfo/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index 9d6327181a..c2cd295523 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -30,7 +30,7 @@ if(count($configPrefixes) === 1) { $ldapAccess = new OCA\user_ldap\lib\Access($connector, $ldapWrapper); $userBackend = new OCA\user_ldap\USER_LDAP($ldapAccess); $groupBackend = new OCA\user_ldap\GROUP_LDAP($ldapAccess); -} else { +} else if(count($configPrefixes) > 1) { $userBackend = new OCA\user_ldap\User_Proxy($configPrefixes, $ldapWrapper); $groupBackend = new OCA\user_ldap\Group_Proxy($configPrefixes, $ldapWrapper); } From f64ae7510708e50a416448b4eccb6b0535f2faa6 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 10 Oct 2013 19:37:12 +0200 Subject: [PATCH 25/82] LDAP Wizard: fix couple more or less nasty bugs aka polishing --- apps/user_ldap/js/settings.js | 39 ++++++++++++++++++++++++++-- apps/user_ldap/lib/configuration.php | 8 +++--- apps/user_ldap/lib/wizard.php | 22 +++++++++------- apps/user_ldap/settings.php | 1 + 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 6b1f363bba..df5de87460 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -155,14 +155,16 @@ var LdapWizard = { $('#'+id).val(result.changes[id]); } } + LdapWizard.functionalityCheck(); }, checkBaseDN: function() { host = $('#ldap_host').val(); + port = $('#ldap_port').val(); user = $('#ldap_dn').val(); pass = $('#ldap_agent_password').val(); - if(host && user && pass) { + if(host && port && user && pass) { param = 'action=guessBaseDN'+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); @@ -195,6 +197,7 @@ var LdapWizard = { function(result) { LdapWizard.applyChanges(result); if($('#ldap_port').val()) { + LdapWizard.checkBaseDN(); $('#ldap_port').removeClass('invisible'); LdapWizard.hideInfoBox(); } @@ -346,6 +349,29 @@ var LdapWizard = { ); }, + functionalityCheck: function() { + //criterias to enable the connection: + // - host, port, user filter, login filter + host = $('#ldap_host').val(); + port = $('#ldap_port').val(); + userfilter = $('#ldap_dn').val(); + loginfilter = $('#ldap_agent_password').val(); + + //FIXME: activates a manually deactivated configuration. + if(host && port && userfilter && loginfilter) { + if($('#ldap_configuration_active').is(':checked')) { + return; + } + $('#ldap_configuration_active').prop('checked', true); + LdapWizard.save($('#ldap_configuration_active')[0]); + } else { + if($('#ldap_configuration_active').is(':checked')) { + $('#ldap_configuration_active').prop('checked', false); + LdapWizard.save($('#ldap_configuration_active')[0]); + } + } + }, + hideInfoBox: function() { if(LdapWizard.checkInfoShown) { $('#ldapWizard1 .ldapWizardInfo').addClass('invisible'); @@ -362,11 +388,13 @@ var LdapWizard = { initGroupFilter: function() { LdapWizard.findObjectClasses('ldap_groupfilter_objectclass', 'Group'); LdapWizard.findAvailableGroups('ldap_groupfilter_groups', 'Groups'); + LdapWizard.composeFilter('group'); LdapWizard.countGroups(); }, initLoginFilter: function() { LdapWizard.findAttributes(); + LdapWizard.composeFilter('login'); }, initMultiSelect: function(object, id, caption) { @@ -384,6 +412,7 @@ var LdapWizard = { initUserFilter: function() { LdapWizard.findObjectClasses('ldap_userfilter_objectclass', 'User'); LdapWizard.findAvailableGroups('ldap_userfilter_groups', 'Users'); + LdapWizard.composeFilter('user'); LdapWizard.countUsers(); }, @@ -403,7 +432,10 @@ var LdapWizard = { || triggerObj.id == 'ldap_dn' || triggerObj.id == 'ldap_agent_password') { LdapWizard.checkPort(); - LdapWizard.checkBaseDN(); + if($('#ldap_port').val()) { + //if Port is already set, check BaseDN + LdapWizard.checkBaseDN(); + } } if(triggerObj.id == 'ldap_userlist_filter') { @@ -442,6 +474,9 @@ var LdapWizard = { if(originalObj == 'ldap_userfilter_objectclass' || originalObj == 'ldap_userfilter_groups') { LdapWizard.composeFilter('user'); + //when user filter is changed afterwards, login filter needs to + //be adjusted, too + LdapWizard.composeFilter('login'); } else if(originalObj == 'ldap_loginfilter_attributes') { LdapWizard.composeFilter('login'); } else if(originalObj == 'ldap_groupfilter_objectclass' diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index e1e5d2b65d..2fb566aec6 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -287,7 +287,7 @@ class Configuration { public function getDefaults() { return array( 'ldap_host' => '', - 'ldap_port' => '389', + 'ldap_port' => '', 'ldap_backup_host' => '', 'ldap_backup_port' => '', 'ldap_override_main_server' => '', @@ -296,14 +296,14 @@ class Configuration { 'ldap_base' => '', 'ldap_base_users' => '', 'ldap_base_groups' => '', - 'ldap_userlist_filter' => 'objectClass=person', + 'ldap_userlist_filter' => '', 'ldap_userfilter_objectclass' => '', 'ldap_userfilter_groups' => '', 'ldap_login_filter' => 'uid=%uid', 'ldap_loginfilter_email' => 0, 'ldap_loginfilter_username' => 1, 'ldap_loginfilter_attributes' => '', - 'ldap_group_filter' => 'objectClass=posixGroup', + 'ldap_group_filter' => '', 'ldap_groupfilter_objectclass' => '', 'ldap_groupfilter_groups' => '', 'ldap_display_name' => 'displayName', @@ -319,7 +319,7 @@ class Configuration { 'ldap_override_uuid_attribute' => 0, 'home_folder_naming_rule' => '', 'ldap_turn_off_cert_check' => 0, - 'ldap_configuration_active' => 1, + 'ldap_configuration_active' => 0, 'ldap_attributes_for_user_search' => '', 'ldap_attributes_for_group_search' => '', 'ldap_expert_username_attr' => '', diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index 2538fe4a2c..a1d635faae 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -38,6 +38,8 @@ class Wizard extends LDAPUtility { const LFILTER_USER_LIST = 3; const LFILTER_GROUP_LIST = 4; + const LDAP_NW_TIMEOUT = 4; + /** * @brief Constructor * @param $configuration an instance of Configuration @@ -222,6 +224,7 @@ class Wizard extends LDAPUtility { if($testMemberOf) { $this->configuration->hasMemberOfFilterSupport = $this->testMemberOf(); + $this->result->markChange(); if(!$this->configuration->hasMemberOfFilterSupport) { throw new \Exception('memberOf is not supported by the server'); } @@ -375,7 +378,6 @@ class Wizard extends LDAPUtility { } $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)); @@ -763,10 +765,10 @@ class Wizard extends LDAPUtility { \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); - } + $a = $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); + $c = $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT); + if($tls) { + $this->ldap->startTls($cr); } \OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG); @@ -915,7 +917,7 @@ class Wizard extends LDAPUtility { //pre-select objectclass with most result entries $maxEntryObjC = str_replace($p, '', $maxEntryObjC); $this->applyFind($dbkey, $maxEntryObjC); -// $this->result->addChange($dbkey, $maxEntryObjC); + $this->result->addChange($dbkey, $maxEntryObjC); } return $availableFeatures; @@ -962,10 +964,10 @@ class Wizard extends LDAPUtility { $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); - } + $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); + $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT); + if($this->configuration->ldapTLS === 1) { + $this->ldap->startTls($cr); } $lo = @$this->ldap->bind($cr, diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index 5d3306246d..d077eafdde 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -64,6 +64,7 @@ for($i = 0; $i < count($wizTabs); $i++) { $tmpl->assign('tabs', $wizardHtml); $tmpl->assign('toc', $toc); +$tmpl->assign('settingControls', $sControls); // assign default values $config = new \OCA\user_ldap\lib\Configuration('', false); From 63e9c56d48b257fd155de9fa6a978d2bfadffc1f Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 17 Oct 2013 13:05:14 +0200 Subject: [PATCH 26/82] LDAP Wizard: Use Ajax Spinner to indicate something is happening, especiall port detection --- apps/user_ldap/css/settings.css | 5 +++++ apps/user_ldap/js/settings.js | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/apps/user_ldap/css/settings.css b/apps/user_ldap/css/settings.css index 2f12108b09..65bff3aadb 100644 --- a/apps/user_ldap/css/settings.css +++ b/apps/user_ldap/css/settings.css @@ -5,6 +5,7 @@ .tablecell { display: table-cell !important; + white-space: nowrap; } .tablerow { @@ -75,6 +76,10 @@ color: #FF3B3B; } +.wizSpinner { + height: 15px; +} + #ldap fieldset p label { width: 20%; max-width: 200px; diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index df5de87460..0ccf7ecf42 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -128,6 +128,7 @@ var LdapWizard = { checkPortInfoShown: false, saveBlacklist: {}, userFilterGroupSelectState: 'enable', + spinner: '', ajax: function(param, fnOnSuccess, fnOnError) { $.post( @@ -168,15 +169,18 @@ var LdapWizard = { param = 'action=guessBaseDN'+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + LdapWizard.showSpinner('#ldap_base'); LdapWizard.ajax(param, function(result) { LdapWizard.applyChanges(result); + LdapWizard.hideSpinner('#ldap_base'); if($('#ldap_base').val()) { $('#ldap_base').removeClass('invisible'); LdapWizard.hideInfoBox(); } }, function (result) { + LdapWizard.hideSpinner('#ldap_base'); $('#ldap_base').removeClass('invisible'); LdapWizard.showInfoBox('Please specify a port'); } @@ -193,9 +197,11 @@ var LdapWizard = { param = 'action=guessPortAndTLS'+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + LdapWizard.showSpinner('#ldap_port'); LdapWizard.ajax(param, function(result) { LdapWizard.applyChanges(result); + LdapWizard.hideSpinner('#ldap_port'); if($('#ldap_port').val()) { LdapWizard.checkBaseDN(); $('#ldap_port').removeClass('invisible'); @@ -203,6 +209,7 @@ var LdapWizard = { } }, function (result) { + LdapWizard.hideSpinner('#ldap_port'); $('#ldap_port').removeClass('invisible'); LdapWizard.showInfoBox('Please specify the BaseDN'); } @@ -278,6 +285,7 @@ var LdapWizard = { param = 'action=determineAttributes'+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + LdapWizard.showSpinner('#ldap_loginfilter_attributes'); LdapWizard.ajax(param, function(result) { $('#ldap_loginfilter_attributes').find('option').remove(); @@ -287,6 +295,7 @@ var LdapWizard = { $('#ldap_loginfilter_attributes').append( ""); } + LdapWizard.hideSpinner('#ldap_loginfilter_attributes'); LdapWizard.applyChanges(result); $('#ldap_loginfilter_attributes').multiselect('refresh'); $('#ldap_loginfilter_attributes').multiselect('enable'); @@ -296,6 +305,7 @@ var LdapWizard = { $('#ldap_loginfilter_attributes').multiselect( {noneSelectedText : 'No attributes found'}); $('#ldap_loginfilter_attributes').multiselect('disable'); + LdapWizard.hideSpinner('#ldap_loginfilter_attributes'); } ); }, @@ -307,6 +317,7 @@ var LdapWizard = { param = 'action=determineGroupsFor'+type+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + LdapWizard.showSpinner('#'+multisel); LdapWizard.ajax(param, function(result) { $('#'+multisel).find('option').remove(); @@ -315,11 +326,13 @@ var LdapWizard = { objc = result.options[multisel][i]; $('#'+multisel).append(""); } + LdapWizard.hideSpinner('#'+multisel); LdapWizard.applyChanges(result); $('#'+multisel).multiselect('refresh'); $('#'+multisel).multiselect('enable'); }, function (result) { + LdapWizard.hideSpinner('#'+multisel); $('#'+multisel).multiselect('disable'); } ); @@ -332,6 +345,7 @@ var LdapWizard = { param = 'action=determine'+type+'ObjectClasses'+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + LdapWizard.showSpinner('#'+multisel); LdapWizard.ajax(param, function(result) { $('#'+multisel).find('option').remove(); @@ -340,10 +354,12 @@ var LdapWizard = { objc = result.options[multisel][i]; $('#'+multisel).append(""); } + LdapWizard.hideSpinner('#'+multisel); LdapWizard.applyChanges(result); $('#'+multisel).multiselect('refresh'); }, function (result) { + LdapWizard.hideSpinner('#'+multisel); //TODO: error handling } ); @@ -379,6 +395,11 @@ var LdapWizard = { } }, + hideSpinner: function(id) { + $(id+' + .wizSpinner').remove(); + $(id + " + button").css('display', 'inline'); + }, + init: function() { if($('#ldap_port').val()) { $('#ldap_port').removeClass('invisible'); @@ -510,6 +531,11 @@ var LdapWizard = { LdapWizard.checkInfoShown = true; }, + showSpinner: function(id) { + $(LdapWizard.spinner).insertAfter($(id)); + $(id + " + img + button").css('display', 'none'); + }, + toggleRawFilter: function(container, moc, mg, stateVar) { if($(container).hasClass('invisible')) { $(container).removeClass('invisible'); From 6b160d8507806f38eaeed2daea2f30621fb000b4 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 17 Oct 2013 18:33:58 +0200 Subject: [PATCH 27/82] LDAP Wizard: make Back and Continue work --- apps/user_ldap/js/settings.js | 58 +++++++++++++++++++ .../templates/part.wizardcontrols.php | 5 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 0ccf7ecf42..7ce1014a85 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -159,6 +159,22 @@ var LdapWizard = { LdapWizard.functionalityCheck(); }, + basicStatusCheck: function() { + //criterias to continue from the first tab + // - host, port, user filter, agent dn, password, base dn + host = $('#ldap_host').val(); + port = $('#ldap_port').val(); + agent = $('#ldap_dn').val(); + pwd = $('#ldap_agent_password').val(); + base = $('#ldap_base').val(); + + if(host && port && agent && pwd && base) { + $('.ldap_action_continue').removeAttr('disabled'); + } else { + $('.ldap_action_continue').attr('disabled', 'disabled'); + } + }, + checkBaseDN: function() { host = $('#ldap_host').val(); port = $('#ldap_port').val(); @@ -245,6 +261,35 @@ var LdapWizard = { ); }, + controlBack: function() { + curTabIndex = $('#ldapSettings').tabs('option', 'active'); + if(curTabIndex == 0) { + return; + } + if(curTabIndex == 1) { + $('.ldap_action_back').addClass('invisible'); + } + $('#ldapSettings').tabs('option', 'active', curTabIndex - 1); + if(curTabIndex == 3) { + $('.ldap_action_continue').removeClass('invisible'); + } + }, + + controlContinue: function() { + curTabIndex = $('#ldapSettings').tabs('option', 'active'); + if(curTabIndex == 3) { + return; + } + $('#ldapSettings').tabs('option', 'active', 1 + curTabIndex); + if(curTabIndex == 2) { + //now last tab + $('.ldap_action_continue').addClass('invisible'); + } + if(curTabIndex == 0) { + $('.ldap_action_back').removeClass('invisible'); + } + }, + _countThings: function(method) { param = 'action='+method+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); @@ -404,6 +449,7 @@ var LdapWizard = { if($('#ldap_port').val()) { $('#ldap_port').removeClass('invisible'); } + LdapWizard.basicStatusCheck(); }, initGroupFilter: function() { @@ -470,6 +516,10 @@ var LdapWizard = { || triggerObj.id == 'ldap_loginfilter_email') { LdapWizard.composeFilter('login'); } + + if($('#ldapSettings').tabs('option', 'active') == 0) { + LdapWizard.basicStatusCheck(); + } }, save: function(inputObj) { @@ -595,6 +645,14 @@ $(document).ready(function() { $('#toggleRawUserFilter').click(LdapWizard.toggleRawUserFilter); $('#toggleRawGroupFilter').click(LdapWizard.toggleRawGroupFilter); LdapConfiguration.refreshConfig(); + $('.ldap_action_continue').click(function(event) { + event.preventDefault(); + LdapWizard.controlContinue(); + }); + $('.ldap_action_back').click(function(event) { + event.preventDefault(); + LdapWizard.controlBack(); + }); $('#ldap_action_test_connection').click(function(event){ event.preventDefault(); $.post( diff --git a/apps/user_ldap/templates/part.wizardcontrols.php b/apps/user_ldap/templates/part.wizardcontrols.php index 4d05586bdf..db99145d51 100644 --- a/apps/user_ldap/templates/part.wizardcontrols.php +++ b/apps/user_ldap/templates/part.wizardcontrols.php @@ -1,8 +1,9 @@
    - - Date: Thu, 17 Oct 2013 18:55:02 +0200 Subject: [PATCH 28/82] Ldap Wizard: also disable tabs, if the first step is not complete --- apps/user_ldap/js/settings.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 7ce1014a85..efd9bade1a 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -170,8 +170,10 @@ var LdapWizard = { if(host && port && agent && pwd && base) { $('.ldap_action_continue').removeAttr('disabled'); + $('#ldapSettings').tabs('option', 'disabled', []); } else { $('.ldap_action_continue').attr('disabled', 'disabled'); + $('#ldapSettings').tabs('option', 'disabled', [1, 2, 3, 4, 5]); } }, From 6acd01d9f287def7cf4f35fbe71c069dd97eff8b Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 17 Oct 2013 19:40:59 +0200 Subject: [PATCH 29/82] resolve merge conflicts from rebase --- apps/user_ldap/lib/configuration.php | 21 +++++----- apps/user_ldap/lib/connection.php | 57 ++++++++++------------------ 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index 2fb566aec6..460cd9e1dc 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -60,8 +60,8 @@ class Configuration { 'ldapQuotaDefault' => null, 'ldapEmailAttribute' => null, 'ldapCacheTTL' => null, - 'ldapUuidAttribute' => 'auto', - 'ldapOverrideUuidAttribute' => null, + 'ldapUuidUserAttribute' => 'auto', + 'ldapUuidGroupAttribute' => 'auto', 'ldapOverrideMainServer' => false, 'ldapConfigurationActive' => false, 'ldapAttributesForUserSearch' => null, @@ -70,7 +70,8 @@ class Configuration { 'hasPagedResultSupport' => false, 'hasMemberOfFilterSupport' => false, 'ldapExpertUsernameAttr' => null, - 'ldapExpertUUIDAttr' => null, + 'ldapExpertUUIDUserAttr' => null, + 'ldapExpertUUIDGroupAttr' => null, ); public function __construct($configPrefix, $autoread = true) { @@ -214,9 +215,9 @@ class Configuration { break; //following options are not stored but detected, skip them case 'ldapIgnoreNamingRules': - case 'ldapOverrideUuidAttribute': case 'hasPagedResultSupport': - case 'ldapUuidAttribute': + case 'ldapUuidUserAttribute': + case 'ldapUuidGroupAttribute': continue 2; } if(is_null($value)) { @@ -315,15 +316,16 @@ class Configuration { 'ldap_email_attr' => '', 'ldap_group_member_assoc_attribute' => 'uniqueMember', 'ldap_cache_ttl' => 600, - 'ldap_uuid_attribute' => 'auto', - 'ldap_override_uuid_attribute' => 0, + 'ldap_uuid_user_attribute' => 'auto', + 'ldap_uuid_group_attribute' => 'auto', 'home_folder_naming_rule' => '', 'ldap_turn_off_cert_check' => 0, 'ldap_configuration_active' => 0, 'ldap_attributes_for_user_search' => '', 'ldap_attributes_for_group_search' => '', 'ldap_expert_username_attr' => '', - 'ldap_expert_uuid_attr' => '', + 'ldap_expert_uuid_user_attr' => '', + 'ldap_expert_uuid_group_attr' => '', 'has_memberof_filter_support' => 0, ); } @@ -369,7 +371,8 @@ class Configuration { 'ldap_attributes_for_user_search' => 'ldapAttributesForUserSearch', 'ldap_attributes_for_group_search' => 'ldapAttributesForGroupSearch', 'ldap_expert_username_attr' => 'ldapExpertUsernameAttr', - 'ldap_expert_uuid_attr' => 'ldapExpertUUIDAttr', + 'ldap_expert_uuid_user_attr' => 'ldapExpertUUIUserDAttr', + 'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr', 'has_memberof_filter_support' => 'hasMemberOfFilterSupport', ); return $array; diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 981d792325..8d34fb2f41 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -38,8 +38,6 @@ class Connection extends LDAPUtility { //settings handler protected $configuration; - 'ldapUuidGroupAttribute' => 'auto', - 'ldapExpertUUIDGroupAttr' => null, /** * @brief Constructor @@ -189,17 +187,11 @@ class Connection extends LDAPUtility { private function readConfiguration($force = false) { if((!$this->configured || $force) && !is_null($this->configID)) { $this->configuration->readConfiguration(); - $this->config['ldapUuidGroupAttribute'] - = $this->$v('ldap_uuid_group_attribute'); - $this->config['ldapExpertUUIDGroupAttr'] - = $this->$v('ldap_expert_uuid_group_attr'); - $this->configured = $this->validateConfiguration(); } } /** - 'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr', * @brief set LDAP configuration with values delivered by an array, not read from configuration * @param $config array that holds the config parameters in an associated array * @param &$setParameters optional; array where the set fields will be given to @@ -224,7 +216,6 @@ class Connection extends LDAPUtility { */ public function saveConfiguration() { $this->configuration->saveConfiguration(); - case 'ldapUuidGroupAttribute': $this->clearCache(); } @@ -262,11 +253,6 @@ class Connection extends LDAPUtility { return $result; } - //TODO remove if not necessary -// public function getDefaults() { -// return $this->configuration->getDefaults(); -// } - private function doSoftValidation() { //if User or Group Base are not set, take over Base DN setting foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) { @@ -289,27 +275,29 @@ class Connection extends LDAPUtility { \OCP\Util::INFO); } - $uuidOverride = $this->configuration->ldapExpertUUIDAttr; - if(!empty($uuidOverride)) { - $this->configuration->ldapUuidAttribute = $uuidOverride; - } else { - $uuidAttributes = array('auto', 'entryuuid', 'nsuniqueid', - 'objectguid', 'guid'); - if(!in_array($this->configuration->ldapUuidAttribute, - $uuidAttributes) - && (!is_null($this->configID))) { - $this->configuration->ldapUuidAttribute = 'auto'; - $this->configuration->saveConfiguration(); - 'auto'); - \OCP\Util::writeLog('user_ldap', - 'Illegal value for the UUID Attribute, '. - 'reset to autodetect.', - \OCP\Util::INFO); + foreach(array('ldapExpertUUIDUserAttr' => 'ldapUuidUserAttribute', + 'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute') + as $expertSetting => $effectiveSetting) { + $uuidOverride = $this->configuration->$expertSetting; + if(!empty($uuidOverride)) { + $this->configuration->$effectiveSetting = $uuidOverride; + } else { + $uuidAttributes = array('auto', 'entryuuid', 'nsuniqueid', + 'objectguid', 'guid'); + if(!in_array($this->configuration->$effectiveSetting, + $uuidAttributes) + && (!is_null($this->configID))) { + $this->configuration->$effectiveSetting = 'auto'; + $this->configuration->saveConfiguration(); + \OCP\Util::writeLog('user_ldap', + 'Illegal value for the '. + $effectiveSetting.', '.'reset to '. + 'autodetect.', \OCP\Util::INFO); + } + } - } - $backupPort = $this->configuration->ldapBackupPort; if(empty($backupPort)) { $this->configuration->backupPort = $this->configuration->ldapPort; @@ -405,9 +393,6 @@ class Connection extends LDAPUtility { $configurationOK = false; } - } - if(!empty($this->config['ldapExpertUUIDGroupAttr'])) { - $this->config['ldapUuidGroupAttribute'] = $this->config['ldapExpertUUIDGroupAttr']; return $configurationOK; } @@ -423,8 +408,6 @@ class Connection extends LDAPUtility { //second step: critical checks. If left empty or filled wrong, set as //unconfigured and give a warning. return $this->doCriticalValidation(); - 'ldap_uuid_group_attribute' => 'auto', - 'ldap_expert_uuid_group_attr' => '', } From d2d2c9a46d86be2b38d6e88c1943e7328ebb26d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 17 Oct 2013 20:20:13 +0200 Subject: [PATCH 30/82] fixes #5367 --- lib/private/connector/sabre/file.php | 2 +- lib/private/filechunking.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php index 3402946a13..fd12039d91 100644 --- a/lib/private/connector/sabre/file.php +++ b/lib/private/connector/sabre/file.php @@ -215,7 +215,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D if (isset($_SERVER['CONTENT_LENGTH'])) { $expected = $_SERVER['CONTENT_LENGTH']; if ($bytesWritten != $expected) { - $chunk_handler->cleanup(); + $chunk_handler->remove($info['index']); throw new Sabre_DAV_Exception_BadRequest( 'expected filesize ' . $expected . ' got ' . $bytesWritten); } diff --git a/lib/private/filechunking.php b/lib/private/filechunking.php index 0dfce696cd..aa4f73c7c0 100644 --- a/lib/private/filechunking.php +++ b/lib/private/filechunking.php @@ -85,6 +85,16 @@ class OC_FileChunking { } } + /** + * Removes one specific chunk + * @param $index + */ + public function remove($index) { + $cache = $this->getCache(); + $prefix = $this->getPrefix(); + $cache->remove($prefix.$index); + } + public function signature_split($orgfile, $input) { $info = unpack('n', fread($input, 2)); $blocksize = $info[1]; From 183dd813d34f870c11bea3a97c9a6b34bc46808b Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 17 Oct 2013 20:57:19 +0200 Subject: [PATCH 31/82] Fix Spinner issues with Base DN field --- apps/user_ldap/js/settings.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index efd9bade1a..faef477420 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -157,6 +157,10 @@ var LdapWizard = { } } LdapWizard.functionalityCheck(); + + if($('#ldapSettings').tabs('option', 'active') == 0) { + LdapWizard.basicStatusCheck(); + } }, basicStatusCheck: function() { @@ -451,6 +455,9 @@ var LdapWizard = { if($('#ldap_port').val()) { $('#ldap_port').removeClass('invisible'); } + if($('#ldap_base').val()) { + $('#ldap_base').removeClass('invisible'); + } LdapWizard.basicStatusCheck(); }, @@ -584,8 +591,10 @@ var LdapWizard = { }, showSpinner: function(id) { - $(LdapWizard.spinner).insertAfter($(id)); - $(id + " + img + button").css('display', 'none'); + if($(id + ' + .wizSpinner').length == 0) { + $(LdapWizard.spinner).insertAfter($(id)); + $(id + " + img + button").css('display', 'none'); + } }, toggleRawFilter: function(container, moc, mg, stateVar) { From 17010e8f587dc2e822660da342b3dc64475f8f41 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 17 Oct 2013 20:58:43 +0200 Subject: [PATCH 32/82] Don't set Base User and Base Group when Base DN was detected, would case problems but does not provide any benefit. also make sure that empty string is saved instead of false for multiline values --- apps/user_ldap/lib/configuration.php | 3 +++ apps/user_ldap/lib/wizard.php | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index 460cd9e1dc..c8bf1c0948 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -243,6 +243,9 @@ class Configuration { $value = ''; } else { $value = preg_split('/\r\n|\r|\n/', $value); + if($value === false) { + $value = ''; + } } $this->setValue($varname, $value); diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index a1d635faae..7b7ef45af4 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -424,11 +424,8 @@ class Wizard extends LDAPUtility { $i = stripos($this->configuration->ldapAgentName, 'dc='); if($i !== false) { $base = substr($this->configuration->ldapAgentName, $i); - if($this->testBaseDN($base)) { $this->applyFind('ldap_base', $base); - $this->applyFind('ldap_base_users', $base); - $this->applyFind('ldap_base_groups', $base); return $this->result; } } @@ -445,8 +442,6 @@ class Wizard extends LDAPUtility { $base2 = implode('dc=', $dparts); if($base !== $base2 && $this->testBaseDN($base2)) { $this->applyFind('ldap_base', $base2); - $this->applyFind('ldap_base_users', $base2); - $this->applyFind('ldap_base_groups', $base2); return $this->result; } From 5b75673e772a4c1f680043f3cc5a3ea1e81926c5 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sun, 20 Oct 2013 22:48:25 +0300 Subject: [PATCH 33/82] more breathing space in share list --- core/css/share.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/css/share.css b/core/css/share.css index 2a21dc6edf..85a805c2ae 100644 --- a/core/css/share.css +++ b/core/css/share.css @@ -22,7 +22,8 @@ } #shareWithList li { - padding-top:.1em; + padding-top: 10px; + padding-bottom: 10px; } #shareWithList li:first-child { From 846ce67d011b9f65a0f74c91d9e82d232153c8d4 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sun, 20 Oct 2013 22:51:04 +0300 Subject: [PATCH 34/82] properly align checkboxes to text --- core/css/share.css | 1 + 1 file changed, 1 insertion(+) diff --git a/core/css/share.css b/core/css/share.css index 85a805c2ae..e6c5f8848c 100644 --- a/core/css/share.css +++ b/core/css/share.css @@ -44,6 +44,7 @@ #dropdown input[type="checkbox"] { margin:0 .2em 0 .5em; + vertical-align: middle; } a.showCruds { From 1e8af6c5ef081324a57b6481970e8c910cd22c67 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sun, 20 Oct 2013 22:52:18 +0300 Subject: [PATCH 35/82] bold share username --- core/css/share.css | 1 + 1 file changed, 1 insertion(+) diff --git a/core/css/share.css b/core/css/share.css index e6c5f8848c..6e920f6bde 100644 --- a/core/css/share.css +++ b/core/css/share.css @@ -24,6 +24,7 @@ #shareWithList li { padding-top: 10px; padding-bottom: 10px; + font-weight: bold; } #shareWithList li:first-child { From fc7269e7282a4fa6f65c9ff683f19d8b5f0f5a60 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sun, 20 Oct 2013 23:03:48 +0300 Subject: [PATCH 36/82] remove hover hide/show stuff for share dropdown to make things more obvious --- core/js/share.js | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/core/js/share.js b/core/js/share.js index 6ec15f01c1..2922cdd60e 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -372,7 +372,7 @@ OC.Share={ shareChecked = 'checked="checked"'; } var html = '
  • '; - html += ''; + html += ''+t('core', 'Unshare')+''; if(shareWith.length > 14){ html += escapeHTML(shareWithDisplayName.substr(0,11) + '...'); }else{ @@ -387,14 +387,9 @@ OC.Share={ html += ''; } if (possiblePermissions & OC.PERMISSION_CREATE || possiblePermissions & OC.PERMISSION_UPDATE || possiblePermissions & OC.PERMISSION_DELETE) { - if (editChecked == '') { - html += '
  • diff --git a/core/css/share.css b/core/css/share.css index 6e920f6bde..e1edc311ee 100644 --- a/core/css/share.css +++ b/core/css/share.css @@ -102,3 +102,8 @@ a.showCruds:hover,a.unshare:hover { overflow-y:auto; overflow-x:hidden; } + +.notCreatable { + padding-top: 12px; + color: #999; +} From cbb42aae116e0ce88f0180e7720234bc0c3e7e06 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sun, 20 Oct 2013 23:46:01 +0300 Subject: [PATCH 39/82] improve wording of permission notice --- apps/files/templates/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index fd2b58e0e7..d2f100f348 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -37,7 +37,7 @@
    - t('You don’t have write permissions here'))?> + t('You don’t have permission to upload or create files here'))?>
    From e30e4ea1cc128231b702697228841c754046261c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 21 Oct 2013 22:51:56 +0200 Subject: [PATCH 40/82] php 5.3 compatibility regarding OC\DB\Connection fixed --- lib/private/db/connection.php | 2 +- lib/private/db/connectionwrapper.php | 93 ++++++++++++++++++++++++++++ lib/private/server.php | 3 +- 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 lib/private/db/connectionwrapper.php diff --git a/lib/private/db/connection.php b/lib/private/db/connection.php index 2d3193a148..2581969dbd 100644 --- a/lib/private/db/connection.php +++ b/lib/private/db/connection.php @@ -12,7 +12,7 @@ use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\Common\EventManager; -class Connection extends \Doctrine\DBAL\Connection implements \OCP\IDBConnection { +class Connection extends \Doctrine\DBAL\Connection { /** * @var string $tablePrefix */ diff --git a/lib/private/db/connectionwrapper.php b/lib/private/db/connectionwrapper.php new file mode 100644 index 0000000000..8b195ca107 --- /dev/null +++ b/lib/private/db/connectionwrapper.php @@ -0,0 +1,93 @@ +connection = $conn; + } + + /** + * Used to the owncloud database access away + * @param string $sql the sql query with ? placeholder for params + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \Doctrine\DBAL\Driver\Statement The prepared statement. + */ + public function prepare($sql, $limit = null, $offset = null) + { + return $this->connection->prepare($sql, $limit, $offset); + } + + /** + * Used to get the id of the just inserted element + * @param string $tableName the name of the table where we inserted the item + * @return int the id of the inserted element + */ + public function lastInsertId($table = null) + { + return $this->connection->lastInsertId($table); + } + + /** + * Insert a row if a matching row doesn't exists. + * @param string The table name (will replace *PREFIX*) to perform the replace on. + * @param array + * + * The input array if in the form: + * + * array ( 'id' => array ( 'value' => 6, + * 'key' => true + * ), + * 'name' => array ('value' => 'Stoyan'), + * 'family' => array ('value' => 'Stefanov'), + * 'birth_date' => array ('value' => '1975-06-20') + * ); + * @return bool + * + */ + public function insertIfNotExist($table, $input) + { + return $this->connection->insertIfNotExist($table, $input); + } + + /** + * Start a transaction + * @return bool TRUE on success or FALSE on failure + */ + public function beginTransaction() + { + return $this->connection->beginTransaction(); + } + + /** + * Commit the database changes done during a transaction that is in progress + * @return bool TRUE on success or FALSE on failure + */ + public function commit() + { + return $this->connection->commit(); + } + + /** + * Rollback the database changes done during a transaction that is in progress + * @return bool TRUE on success or FALSE on failure + */ + public function rollBack() + { + return $this->connection->rollBack(); + } + + /** + * Gets the error code and message as a string for logging + * @return string + */ + public function getError() + { + return $this->connection->getError(); + } +} diff --git a/lib/private/server.php b/lib/private/server.php index d450907534..65899f3007 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -5,6 +5,7 @@ namespace OC; use OC\AppFramework\Http\Request; use OC\AppFramework\Utility\SimpleContainer; use OC\Cache\UserCache; +use OC\DB\ConnectionWrapper; use OC\Files\Node\Root; use OC\Files\View; use OCP\IServerContainer; @@ -289,7 +290,7 @@ class Server extends SimpleContainer implements IServerContainer { * @return \OCP\IDBConnection */ function getDatabaseConnection() { - return \OC_DB::getConnection(); + return new ConnectionWrapper(\OC_DB::getConnection()); } /** From 76a195a0be5d36c090c05ce60e02466e9783fea3 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 21 Oct 2013 23:15:21 +0200 Subject: [PATCH 41/82] LDAP Wizard: l10n improvements --- apps/user_ldap/ajax/wizard.php | 3 ++- apps/user_ldap/templates/part.wizard-groupfilter.php | 4 ++-- apps/user_ldap/templates/part.wizard-loginfilter.php | 2 +- apps/user_ldap/templates/part.wizard-userfilter.php | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php index c90efdf7e9..e580c09786 100644 --- a/apps/user_ldap/ajax/wizard.php +++ b/apps/user_ldap/ajax/wizard.php @@ -82,7 +82,8 @@ switch($action) { $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]))); + \OCP\JSON::error(array('message' => $l->t($key. + ' Could not set configuration %s', $setParameters[0]))); exit; } $configuration->saveConfiguration(); diff --git a/apps/user_ldap/templates/part.wizard-groupfilter.php b/apps/user_ldap/templates/part.wizard-groupfilter.php index d36c171b63..17ce815589 100644 --- a/apps/user_ldap/templates/part.wizard-groupfilter.php +++ b/apps/user_ldap/templates/part.wizard-groupfilter.php @@ -1,7 +1,7 @@

    - t('Limit the access to ownCloud to groups meeting this criteria:'));?> + t('Limit the access to %s to groups meeting this criteria:', $theme->getName()));?>

    diff --git a/apps/user_ldap/templates/part.wizard-loginfilter.php b/apps/user_ldap/templates/part.wizard-loginfilter.php index 4cad659544..d4a36eb0cb 100644 --- a/apps/user_ldap/templates/part.wizard-loginfilter.php +++ b/apps/user_ldap/templates/part.wizard-loginfilter.php @@ -13,7 +13,7 @@

    - t('Limit the access to ownCloud to users meeting this criteria:'));?> + t('Limit the access to %s to users meeting this criteria:', $theme->getName()));?>

    From 7f1432600c12c86ca35239c1297dee392fbd08f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 22 Oct 2013 09:29:17 +0200 Subject: [PATCH 42/82] fixing php 5.3.3 error: Undefined variable: this lib/private/appframework/dependencyinjection/dicontainer.php:92 --- lib/private/appframework/dependencyinjection/dicontainer.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php index 81910df699..0054b153f9 100644 --- a/lib/private/appframework/dependencyinjection/dicontainer.php +++ b/lib/private/appframework/dependencyinjection/dicontainer.php @@ -88,8 +88,9 @@ class DIContainer extends SimpleContainer implements IAppContainer{ /** * Middleware */ - $this['SecurityMiddleware'] = $this->share(function($c){ - return new SecurityMiddleware($this, $c['Request']); + $app = $this; + $this['SecurityMiddleware'] = $this->share(function($c) use ($app){ + return new SecurityMiddleware($app, $c['Request']); }); $middleWares = $this->middleWares; From 6e0e6212188aa4a1d57339fa2ee4afd548414d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 22 Oct 2013 11:10:07 +0200 Subject: [PATCH 43/82] creating and deleting of file and folder 'Shared' in root is not allowed --- lib/private/connector/sabre/directory.php | 17 ++++++++++++++--- lib/private/connector/sabre/file.php | 4 ++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/private/connector/sabre/directory.php b/lib/private/connector/sabre/directory.php index c51f84bf67..02d1a9f4ba 100644 --- a/lib/private/connector/sabre/directory.php +++ b/lib/private/connector/sabre/directory.php @@ -50,6 +50,10 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa */ public function createFile($name, $data = null) { + if ($name === 'Shared' && empty($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + // for chunked upload also updating a existing file is a "createFile" // because we create all the chunks before reasamble them to the existing file. if (isset($_SERVER['HTTP_OC_CHUNKED'])) { @@ -82,6 +86,10 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa */ public function createDirectory($name) { + if ($name === 'Shared' && empty($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if (!\OC\Files\Filesystem::isCreatable($this->path)) { throw new \Sabre_DAV_Exception_Forbidden(); } @@ -187,12 +195,15 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa */ public function delete() { + if ($this->path === 'Shared') { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if (!\OC\Files\Filesystem::isDeletable($this->path)) { throw new \Sabre_DAV_Exception_Forbidden(); } - if ($this->path != "/Shared") { - \OC\Files\Filesystem::rmdir($this->path); - } + + \OC\Files\Filesystem::rmdir($this->path); } diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php index 3402946a13..7b8462cae5 100644 --- a/lib/private/connector/sabre/file.php +++ b/lib/private/connector/sabre/file.php @@ -143,6 +143,10 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D */ public function delete() { + if ($this->path === 'Shared') { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if (!\OC\Files\Filesystem::isDeletable($this->path)) { throw new \Sabre_DAV_Exception_Forbidden(); } From f62c4eafa549a26efcce025e3b33ed87cbce5fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 22 Oct 2013 11:22:06 +0200 Subject: [PATCH 44/82] test cases added for create/remove Shared folder and file --- tests/lib/connector/sabre/directory.php | 34 +++++++++++++++++++++++++ tests/lib/connector/sabre/file.php | 7 +++++ 2 files changed, 41 insertions(+) create mode 100644 tests/lib/connector/sabre/directory.php diff --git a/tests/lib/connector/sabre/directory.php b/tests/lib/connector/sabre/directory.php new file mode 100644 index 0000000000..c501521b60 --- /dev/null +++ b/tests/lib/connector/sabre/directory.php @@ -0,0 +1,34 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_OC_Connector_Sabre_Directory extends PHPUnit_Framework_TestCase { + + /** + * @expectedException Sabre_DAV_Exception_Forbidden + */ + public function testCreateSharedFileFails() { + $dir = new OC_Connector_Sabre_Directory(''); + $dir->createFile('Shared'); + } + + /** + * @expectedException Sabre_DAV_Exception_Forbidden + */ + public function testCreateSharedFolderFails() { + $dir = new OC_Connector_Sabre_Directory(''); + $dir->createDirectory('Shared'); + } + + /** + * @expectedException Sabre_DAV_Exception_Forbidden + */ + public function testDeleteSharedFolderFails() { + $dir = new OC_Connector_Sabre_Directory('Shared'); + $dir->delete(); + } +} diff --git a/tests/lib/connector/sabre/file.php b/tests/lib/connector/sabre/file.php index a1dade3d63..e1fed0384c 100644 --- a/tests/lib/connector/sabre/file.php +++ b/tests/lib/connector/sabre/file.php @@ -35,4 +35,11 @@ class Test_OC_Connector_Sabre_File extends PHPUnit_Framework_TestCase { $etag = $file->put('test data'); } + /** + * @expectedException Sabre_DAV_Exception_Forbidden + */ + public function testDeleteSharedFails() { + $file = new OC_Connector_Sabre_File('Shared'); + $file->delete(); + } } From 45e6d9670266884806431f35a4cedb80c0ceb229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 22 Oct 2013 18:11:03 +0200 Subject: [PATCH 45/82] prevent user from creating or renaming sth. to an existing filename - show tooltip when violating naming constraints while typing - when target filename exists on server fallback to dialog to interrupt the users flow because something unexpected went wrong - fixes #5062 - also fixes some whitespace and codestyle issues in files js - uses css selector over filterAttr in touched js files --- apps/files/ajax/newfile.php | 15 ++ apps/files/css/files.css | 8 +- apps/files/js/file-upload.js | 337 ++++++++++++++++-------------- apps/files/js/filelist.js | 388 ++++++++++++++++++----------------- apps/files/js/files.js | 198 +++++++++--------- apps/files/lib/app.php | 10 +- core/js/js.js | 2 +- 7 files changed, 523 insertions(+), 435 deletions(-) diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index 76c03c87a5..8f483aa5cb 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -56,6 +56,21 @@ function progress($notification_code, $severity, $message, $message_code, $bytes $target = $dir.'/'.$filename; +$l10n = \OC_L10n::get('files'); + +if (\OC\Files\Filesystem::file_exists($target)) { + $result = array( + 'success' => false, + 'data' => array( + 'message' => $l10n->t( + "The name %s is already used in the folder %s. Please choose a different name.", + array($newname, $dir)) + ) + ); + OCP\JSON::error($result); + exit(); +} + if($source) { if(substr($source, 0, 8)!='https://' and substr($source, 0, 7)!='http://') { OCP\JSON::error(array("data" => array( "message" => "Not a valid source" ))); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index ac2a243f2b..af8597192f 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -49,7 +49,13 @@ background-repeat:no-repeat; cursor:pointer; } #new>ul>li>p { cursor:pointer; padding-top: 7px; padding-bottom: 7px;} - +#new .error, #fileList .error { + color: #e9322d; + border-color: #e9322d; + -webkit-box-shadow: 0 0 6px #f8b9b7; + -moz-box-shadow: 0 0 6px #f8b9b7; + box-shadow: 0 0 6px #f8b9b7; +} /* FILE TABLE */ diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index fefb06a8ac..936487cad3 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -53,12 +53,12 @@ OC.Upload = { */ cancelUploads:function() { this.log('canceling uploads'); - jQuery.each(this._uploads,function(i, jqXHR){ + jQuery.each(this._uploads,function(i, jqXHR) { jqXHR.abort(); }); this._uploads = []; }, - rememberUpload:function(jqXHR){ + rememberUpload:function(jqXHR) { if (jqXHR) { this._uploads.push(jqXHR); } @@ -68,10 +68,10 @@ OC.Upload = { * returns true if any hxr has the state 'pending' * @returns {boolean} */ - isProcessing:function(){ + isProcessing:function() { var count = 0; - jQuery.each(this._uploads,function(i, data){ + jQuery.each(this._uploads,function(i, data) { if (data.state() === 'pending') { count++; } @@ -114,7 +114,7 @@ OC.Upload = { * handle skipping an upload * @param {object} data */ - onSkip:function(data){ + onSkip:function(data) { this.log('skip', null, data); this.deleteUpload(data); }, @@ -122,12 +122,12 @@ OC.Upload = { * handle replacing a file on the server with an uploaded file * @param {object} data */ - onReplace:function(data){ + onReplace:function(data) { this.log('replace', null, data); - if (data.data){ + if (data.data) { data.data.append('resolution', 'replace'); } else { - data.formData.push({name:'resolution',value:'replace'}); //hack for ie8 + data.formData.push({name:'resolution', value:'replace'}); //hack for ie8 } data.submit(); }, @@ -135,12 +135,12 @@ OC.Upload = { * handle uploading a file and letting the server decide a new name * @param {object} data */ - onAutorename:function(data){ + onAutorename:function(data) { this.log('autorename', null, data); if (data.data) { data.data.append('resolution', 'autorename'); } else { - data.formData.push({name:'resolution',value:'autorename'}); //hack for ie8 + data.formData.push({name:'resolution', value:'autorename'}); //hack for ie8 } data.submit(); }, @@ -162,7 +162,7 @@ OC.Upload = { * @param {function} callbacks.onChooseConflicts * @param {function} callbacks.onCancel */ - checkExistingFiles: function (selection, callbacks){ + checkExistingFiles: function (selection, callbacks) { // TODO check filelist before uploading and show dialog on conflicts, use callbacks callbacks.onNoConflicts(selection); } @@ -215,7 +215,7 @@ $(document).ready(function() { var selection = data.originalFiles.selection; // add uploads - if ( selection.uploads.length < selection.filesToUpload ){ + if ( selection.uploads.length < selection.filesToUpload ) { // remember upload selection.uploads.push(data); } @@ -335,7 +335,7 @@ $(document).ready(function() { delete data.jqXHR; - if(typeof result[0] === 'undefined') { + if (typeof result[0] === 'undefined') { data.textStatus = 'servererror'; data.errorThrown = t('files', 'Could not get result from server.'); var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload'); @@ -368,13 +368,13 @@ $(document).ready(function() { var fileupload = $('#file_upload_start').fileupload(file_upload_param); window.file_upload_param = fileupload; - if(supportAjaxUploadWithProgress()) { + if (supportAjaxUploadWithProgress()) { // add progress handlers fileupload.on('fileuploadadd', function(e, data) { OC.Upload.log('progress handle fileuploadadd', e, data); //show cancel button - //if(data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie? + //if (data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie? // $('#uploadprogresswrapper input.stop').show(); //} }); @@ -419,7 +419,9 @@ $(document).ready(function() { // http://stackoverflow.com/a/6700/11236 var size = 0, key; for (key in obj) { - if (obj.hasOwnProperty(key)) size++; + if (obj.hasOwnProperty(key)) { + size++; + } } return size; }; @@ -432,56 +434,61 @@ $(document).ready(function() { }); //add multiply file upload attribute to all browsers except konqueror (which crashes when it's used) - if(navigator.userAgent.search(/konqueror/i)==-1){ - $('#file_upload_start').attr('multiple','multiple'); + if (navigator.userAgent.search(/konqueror/i) === -1) { + $('#file_upload_start').attr('multiple', 'multiple'); } //if the breadcrumb is to long, start by replacing foldernames with '...' except for the current folder var crumb=$('div.crumb').first(); - while($('div.controls').height()>40 && crumb.next('div.crumb').length>0){ + while($('div.controls').height() > 40 && crumb.next('div.crumb').length > 0) { crumb.children('a').text('...'); - crumb=crumb.next('div.crumb'); + crumb = crumb.next('div.crumb'); } //if that isn't enough, start removing items from the breacrumb except for the current folder and it's parent - var crumb=$('div.crumb').first(); - var next=crumb.next('div.crumb'); - while($('div.controls').height()>40 && next.next('div.crumb').length>0){ + var crumb = $('div.crumb').first(); + var next = crumb.next('div.crumb'); + while($('div.controls').height()>40 && next.next('div.crumb').length > 0) { crumb.remove(); - crumb=next; - next=crumb.next('div.crumb'); + crumb = next; + next = crumb.next('div.crumb'); } //still not enough, start shorting down the current folder name var crumb=$('div.crumb>a').last(); - while($('div.controls').height()>40 && crumb.text().length>6){ - var text=crumb.text() - text=text.substr(0,text.length-6)+'...'; + while($('div.controls').height() > 40 && crumb.text().length > 6) { + var text=crumb.text(); + text = text.substr(0,text.length-6)+'...'; crumb.text(text); } - $(document).click(function(){ + $(document).click(function() { $('#new>ul').hide(); $('#new').removeClass('active'); - $('#new li').each(function(i,element){ - if($(element).children('p').length==0){ + if ($('#new .error').length > 0) { + $('#new .error').tipsy('hide'); + } + $('#new li').each(function(i,element) { + if ($(element).children('p').length === 0) { $(element).children('form').remove(); $(element).append('

    '+$(element).data('text')+'

    '); } }); }); - $('#new').click(function(event){ + $('#new').click(function(event) { event.stopPropagation(); }); - $('#new>a').click(function(){ + $('#new>a').click(function() { $('#new>ul').toggle(); $('#new').toggleClass('active'); }); - $('#new li').click(function(){ - if($(this).children('p').length==0){ + $('#new li').click(function() { + if ($(this).children('p').length === 0) { return; } + + $('#new .error').tipsy('hide'); - $('#new li').each(function(i,element){ - if($(element).children('p').length==0){ + $('#new li').each(function(i,element) { + if ($(element).children('p').length === 0) { $(element).children('form').remove(); $(element).append('

    '+$(element).data('text')+'

    '); } @@ -491,132 +498,164 @@ $(document).ready(function() { var text=$(this).children('p').text(); $(this).data('text',text); $(this).children('p').remove(); + + // add input field var form=$('
    '); var input=$(''); form.append(input); $(this).append(form); + + var checkInput = function () { + var filename = input.val(); + if (type === 'web' && filename.length === 0) { + throw t('files', 'URL cannot be empty.'); + } else if (type !== 'web' && !Files.isFileNameValid(filename)) { + // Files.isFileNameValid(filename) throws an exception itself + } else if ($('#dir').val() === '/' && filename === 'Shared') { + throw t('files','Invalid name. Usage of \'Shared\' is reserved by ownCloud'); + } else if (FileList.inList(filename)) { + throw t('files', '{new_name} already exists', {new_name: filename}); + } else { + return true; + } + }; + + // verify filename on typing + input.keyup(function(event) { + try { + checkInput(); + input.tipsy('hide'); + input.removeClass('error'); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); + } + }); + input.focus(); - form.submit(function(event){ + form.submit(function(event) { event.stopPropagation(); event.preventDefault(); - var newname=input.val(); - if(type == 'web' && newname.length == 0) { - OC.Notification.show(t('files', 'URL cannot be empty.')); - return false; - } else if (type != 'web' && !Files.isFileNameValid(newname)) { - return false; - } else if( type == 'folder' && $('#dir').val() == '/' && newname == 'Shared') { - OC.Notification.show(t('files','Invalid folder name. Usage of \'Shared\' is reserved by ownCloud')); - return false; - } - if (FileList.lastAction) { - FileList.lastAction(); - } - var name = getUniqueName(newname); - if (newname != name) { - FileList.checkName(name, newname, true); - var hidden = true; - } else { - var hidden = false; - } - switch(type){ - case 'file': - $.post( - OC.filePath('files','ajax','newfile.php'), - {dir:$('#dir').val(),filename:name}, - function(result){ - if (result.status == 'success') { - var date=new Date(); - // TODO: ideally addFile should be able to receive - // all attributes and set them automatically, - // and also auto-load the preview - var tr = FileList.addFile(name,0,date,false,hidden); - tr.attr('data-size',result.data.size); - tr.attr('data-mime',result.data.mime); - tr.attr('data-id', result.data.id); - tr.find('.filesize').text(humanFileSize(result.data.size)); - var path = getPathForPreview(name); - lazyLoadPreview(path, result.data.mime, function(previewpath){ - tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); - }); - FileActions.display(tr.find('td.filename'), true); - } else { - OC.dialogs.alert(result.data.message, t('core', 'Error')); + try { + checkInput(); + var newname = input.val(); + if (FileList.lastAction) { + FileList.lastAction(); + } + var name = getUniqueName(newname); + if (newname !== name) { + FileList.checkName(name, newname, true); + var hidden = true; + } else { + var hidden = false; + } + switch(type) { + case 'file': + $.post( + OC.filePath('files', 'ajax', 'newfile.php'), + {dir:$('#dir').val(), filename:name}, + function(result) { + if (result.status === 'success') { + var date = new Date(); + // TODO: ideally addFile should be able to receive + // all attributes and set them automatically, + // and also auto-load the preview + var tr = FileList.addFile(name, 0, date, false, hidden); + tr.attr('data-size', result.data.size); + tr.attr('data-mime', result.data.mime); + tr.attr('data-id', result.data.id); + tr.find('.filesize').text(humanFileSize(result.data.size)); + var path = getPathForPreview(name); + lazyLoadPreview(path, result.data.mime, function(previewpath) { + tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); + }); + FileActions.display(tr.find('td.filename'), true); + } else { + OC.dialogs.alert(result.data.message, t('core', 'Could not create file')); + } } - } - ); - break; - case 'folder': - $.post( - OC.filePath('files','ajax','newfolder.php'), - {dir:$('#dir').val(),foldername:name}, - function(result){ - if (result.status == 'success') { - var date=new Date(); - FileList.addDir(name,0,date,hidden); - var tr=$('tr').filterAttr('data-file',name); - tr.attr('data-id', result.data.id); - } else { - OC.dialogs.alert(result.data.message, t('core', 'Error')); + ); + break; + case 'folder': + $.post( + OC.filePath('files','ajax','newfolder.php'), + {dir:$('#dir').val(), foldername:name}, + function(result) { + if (result.status === 'success') { + var date=new Date(); + FileList.addDir(name, 0, date, hidden); + var tr=$('tr[data-file="'+name+'"]'); + tr.attr('data-id', result.data.id); + } else { + OC.dialogs.alert(result.data.message, t('core', 'Could not create folder')); + } } + ); + break; + case 'web': + if (name.substr(0,8) !== 'https://' && name.substr(0,7) !== 'http://') { + name = 'http://' + name; } - ); - break; - case 'web': - if(name.substr(0,8)!='https://' && name.substr(0,7)!='http://'){ - name='http://'+name; - } - var localName=name; - if(localName.substr(localName.length-1,1)=='/'){//strip / - localName=localName.substr(0,localName.length-1) - } - if(localName.indexOf('/')){//use last part of url - localName=localName.split('/').pop(); - } else { //or the domain - localName=(localName.match(/:\/\/(.[^\/]+)/)[1]).replace('www.',''); - } - localName = getUniqueName(localName); - //IE < 10 does not fire the necessary events for the progress bar. - if($('html.lte9').length === 0) { - $('#uploadprogressbar').progressbar({value:0}); - $('#uploadprogressbar').fadeIn(); - } - - var eventSource=new OC.EventSource(OC.filePath('files','ajax','newfile.php'),{dir:$('#dir').val(),source:name,filename:localName}); - eventSource.listen('progress',function(progress){ + var localName=name; + if (localName.substr(localName.length-1,1)==='/') {//strip / + localName=localName.substr(0,localName.length-1); + } + if (localName.indexOf('/')) {//use last part of url + localName=localName.split('/').pop(); + } else { //or the domain + localName=(localName.match(/:\/\/(.[^\/]+)/)[1]).replace('www.',''); + } + localName = getUniqueName(localName); //IE < 10 does not fire the necessary events for the progress bar. - if($('html.lte9').length === 0) { - $('#uploadprogressbar').progressbar('value',progress); + if ($('html.lte9').length === 0) { + $('#uploadprogressbar').progressbar({value:0}); + $('#uploadprogressbar').fadeIn(); } - }); - eventSource.listen('success',function(data){ - var mime=data.mime; - var size=data.size; - var id=data.id; - $('#uploadprogressbar').fadeOut(); - var date=new Date(); - FileList.addFile(localName,size,date,false,hidden); - var tr=$('tr').filterAttr('data-file',localName); - tr.data('mime',mime).data('id',id); - tr.attr('data-id', id); - var path = $('#dir').val()+'/'+localName; - lazyLoadPreview(path, mime, function(previewpath){ - tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); + + var eventSource=new OC.EventSource(OC.filePath('files','ajax','newfile.php'),{dir:$('#dir').val(),source:name,filename:localName}); + eventSource.listen('progress',function(progress) { + //IE < 10 does not fire the necessary events for the progress bar. + if ($('html.lte9').length === 0) { + $('#uploadprogressbar').progressbar('value',progress); + } }); - }); - eventSource.listen('error',function(error){ - $('#uploadprogressbar').fadeOut(); - alert(error); - }); - break; + eventSource.listen('success',function(data) { + var mime = data.mime; + var size = data.size; + var id = data.id; + $('#uploadprogressbar').fadeOut(); + var date = new Date(); + FileList.addFile(localName, size, date, false, hidden); + var tr = $('tr[data-file="'+localName+'"]'); + tr.data('mime', mime).data('id', id); + tr.attr('data-id', id); + var path = $('#dir').val()+'/'+localName; + lazyLoadPreview(path, mime, function(previewpath) { + tr.find('td.filename').attr('style', 'background-image:url('+previewpath+')'); + }); + FileActions.display(tr.find('td.filename'), true); + }); + eventSource.listen('error',function(error) { + $('#uploadprogressbar').fadeOut(); + alert(error); + }); + break; + } + var li=form.parent(); + form.remove(); + /* workaround for IE 9&10 click event trap, 2 lines: */ + $('input').first().focus(); + $('#content').focus(); + li.append('

    '+li.data('text')+'

    '); + $('#new>a').click(); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); } - var li=form.parent(); - form.remove(); - /* workaround for IE 9&10 click event trap, 2 lines: */ - $('input').first().focus(); - $('#content').focus(); - li.append('

    '+li.data('text')+'

    '); - $('#new>a').click(); }); }); window.file_upload_param = file_upload_param; diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 84ff109325..694ddb0c80 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -1,7 +1,7 @@ var FileList={ useUndo:true, - postProcessList: function(){ - $('#fileList tr').each(function(){ + postProcessList: function() { + $('#fileList tr').each(function() { //little hack to set unescape filenames in attribute $(this).attr('data-file',decodeURIComponent($(this).attr('data-file'))); }); @@ -11,20 +11,20 @@ var FileList={ permissions = $('#permissions').val(), isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; $fileList.empty().html(fileListHtml); - $('#emptycontent').toggleClass('hidden', !isCreatable || $fileList.find('tr').length > 0); + $('#emptycontent').toggleClass('hidden', !isCreatable || $fileList.find('tr').exists()); $fileList.find('tr').each(function () { FileActions.display($(this).children('td.filename')); }); $fileList.trigger(jQuery.Event("fileActionsReady")); FileList.postProcessList(); // "Files" might not be loaded in extending apps - if (window.Files){ + if (window.Files) { Files.setupDragAndDrop(); } FileList.updateFileSummary(); $fileList.trigger(jQuery.Event("updated")); }, - createRow:function(type, name, iconurl, linktarget, size, lastModified, permissions){ + createRow:function(type, name, iconurl, linktarget, size, lastModified, permissions) { var td, simpleSize, basename, extension; //containing tr var tr = $('').attr({ @@ -45,7 +45,7 @@ var FileList={ "href": linktarget }); //split extension from filename for non dirs - if (type != 'dir' && name.indexOf('.')!=-1) { + if (type !== 'dir' && name.indexOf('.') !== -1) { basename=name.substr(0,name.lastIndexOf('.')); extension=name.substr(name.lastIndexOf('.')); } else { @@ -54,11 +54,11 @@ var FileList={ } var name_span=$('').addClass('nametext').text(basename); link_elem.append(name_span); - if(extension){ + if (extension) { name_span.append($('').addClass('extension').text(extension)); } //dirs can show the number of uploaded files - if (type == 'dir') { + if (type === 'dir') { link_elem.append($('').attr({ 'class': 'uploadtext', 'currentUploads': 0 @@ -68,9 +68,9 @@ var FileList={ tr.append(td); //size column - if(size!=t('files', 'Pending')){ + if (size !== t('files', 'Pending')) { simpleSize = humanFileSize(size); - }else{ + } else { simpleSize=t('files', 'Pending'); } var sizeColor = Math.round(160-Math.pow((size/(1024*1024)),2)); @@ -92,7 +92,7 @@ var FileList={ tr.append(td); return tr; }, - addFile:function(name,size,lastModified,loading,hidden,param){ + addFile:function(name, size, lastModified, loading, hidden, param) { var imgurl; if (!param) { @@ -122,9 +122,9 @@ var FileList={ ); FileList.insertElement(name, 'file', tr); - if(loading){ - tr.data('loading',true); - }else{ + if (loading) { + tr.data('loading', true); + } else { tr.find('td.filename').draggable(dragOptions); } if (hidden) { @@ -132,7 +132,7 @@ var FileList={ } return tr; }, - addDir:function(name,size,lastModified,hidden){ + addDir:function(name, size, lastModified, hidden) { var tr = this.createRow( 'dir', @@ -144,7 +144,7 @@ var FileList={ $('#permissions').val() ); - FileList.insertElement(name,'dir',tr); + FileList.insertElement(name, 'dir', tr); var td = tr.find('td.filename'); td.draggable(dragOptions); td.droppable(folderDropOptions); @@ -158,25 +158,26 @@ var FileList={ * @brief Changes the current directory and reload the file list. * @param targetDir target directory (non URL encoded) * @param changeUrl false if the URL must not be changed (defaults to true) + * @param {boolean} force set to true to force changing directory */ - changeDirectory: function(targetDir, changeUrl, force){ + changeDirectory: function(targetDir, changeUrl, force) { var $dir = $('#dir'), url, currentDir = $dir.val() || '/'; targetDir = targetDir || '/'; - if (!force && currentDir === targetDir){ + if (!force && currentDir === targetDir) { return; } FileList.setCurrentDir(targetDir, changeUrl); FileList.reload(); }, - linkTo: function(dir){ + linkTo: function(dir) { return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); }, - setCurrentDir: function(targetDir, changeUrl){ + setCurrentDir: function(targetDir, changeUrl) { $('#dir').val(targetDir); - if (changeUrl !== false){ - if (window.history.pushState && changeUrl !== false){ + if (changeUrl !== false) { + if (window.history.pushState && changeUrl !== false) { url = FileList.linkTo(targetDir); window.history.pushState({dir: targetDir}, '', url); } @@ -189,9 +190,9 @@ var FileList={ /** * @brief Reloads the file list using ajax call */ - reload: function(){ + reload: function() { FileList.showMask(); - if (FileList._reloadCall){ + if (FileList._reloadCall) { FileList._reloadCall.abort(); } FileList._reloadCall = $.ajax({ @@ -200,7 +201,7 @@ var FileList={ dir : $('#dir').val(), breadcrumb: true }, - error: function(result){ + error: function(result) { FileList.reloadCallback(result); }, success: function(result) { @@ -208,7 +209,7 @@ var FileList={ } }); }, - reloadCallback: function(result){ + reloadCallback: function(result) { var $controls = $('#controls'); delete FileList._reloadCall; @@ -219,17 +220,17 @@ var FileList={ return; } - if (result.status === 404){ + if (result.status === 404) { // go back home FileList.changeDirectory('/'); return; } - if (result.data.permissions){ + if (result.data.permissions) { FileList.setDirectoryPermissions(result.data.permissions); } - if(typeof(result.data.breadcrumb) != 'undefined'){ + if (typeof(result.data.breadcrumb) !== 'undefined') { $controls.find('.crumb').remove(); $controls.prepend(result.data.breadcrumb); @@ -238,81 +239,83 @@ var FileList={ Files.resizeBreadcrumbs(width, true); // in case svg is not supported by the browser we need to execute the fallback mechanism - if(!SVGSupport()) { + if (!SVGSupport()) { replaceSVG(); } } FileList.update(result.data.files); }, - setDirectoryPermissions: function(permissions){ + setDirectoryPermissions: function(permissions) { var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; $('#permissions').val(permissions); $('.creatable').toggleClass('hidden', !isCreatable); $('.notCreatable').toggleClass('hidden', isCreatable); }, - remove:function(name){ - $('tr').filterAttr('data-file',name).find('td.filename').draggable('destroy'); - $('tr').filterAttr('data-file',name).remove(); + remove:function(name) { + $('tr[data-file="'+name+'"]').find('td.filename').draggable('destroy'); + $('tr[data-file="'+name+'"]').remove(); FileList.updateFileSummary(); - if($('tr[data-file]').length==0){ + if ( ! $('tr[data-file]').exists() ) { $('#emptycontent').removeClass('hidden'); } }, - insertElement:function(name,type,element){ + insertElement:function(name, type, element) { //find the correct spot to insert the file or folder var pos, fileElements=$('tr[data-file][data-type="'+type+'"]:visible'); - if(name.localeCompare($(fileElements[0]).attr('data-file'))<0){ - pos=-1; - }else if(name.localeCompare($(fileElements[fileElements.length-1]).attr('data-file'))>0){ - pos=fileElements.length-1; - }else{ - for(pos=0;pos0 && name.localeCompare($(fileElements[pos+1]).attr('data-file'))<0){ + if (name.localeCompare($(fileElements[0]).attr('data-file')) < 0) { + pos = -1; + } else if (name.localeCompare($(fileElements[fileElements.length-1]).attr('data-file')) > 0) { + pos = fileElements.length - 1; + } else { + for(pos = 0; pos 0 + && name.localeCompare($(fileElements[pos+1]).attr('data-file')) < 0) + { break; } } } - if(fileElements.length){ - if(pos==-1){ + if (fileElements.exists()) { + if (pos === -1) { $(fileElements[0]).before(element); - }else{ + } else { $(fileElements[pos]).after(element); } - }else if(type=='dir' && $('tr[data-file]').length>0){ + } else if (type === 'dir' && $('tr[data-file]').exists()) { $('tr[data-file]').first().before(element); - } else if(type=='file' && $('tr[data-file]').length>0) { + } else if (type === 'file' && $('tr[data-file]').exists()) { $('tr[data-file]').last().before(element); - }else{ + } else { $('#fileList').append(element); } $('#emptycontent').addClass('hidden'); FileList.updateFileSummary(); }, - loadingDone:function(name, id){ - var mime, tr=$('tr').filterAttr('data-file',name); - tr.data('loading',false); - mime=tr.data('mime'); - tr.attr('data-mime',mime); - if (id != null) { + loadingDone:function(name, id) { + var mime, tr = $('tr[data-file="'+name+'"]'); + tr.data('loading', false); + mime = tr.data('mime'); + tr.attr('data-mime', mime); + if (id) { tr.attr('data-id', id); } var path = getPathForPreview(name); - lazyLoadPreview(path, mime, function(previewpath){ + lazyLoadPreview(path, mime, function(previewpath) { tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); }); tr.find('td.filename').draggable(dragOptions); }, - isLoading:function(name){ - return $('tr').filterAttr('data-file',name).data('loading'); + isLoading:function(name) { + return $('tr[data-file="'+name+'"]').data('loading'); }, - rename:function(name){ + rename:function(oldname) { var tr, td, input, form; - tr=$('tr').filterAttr('data-file',name); + tr = $('tr[data-file="'+oldname+'"]'); tr.data('renaming',true); - td=tr.children('td.filename'); - input=$('').val(name); - form=$('
    '); + td = tr.children('td.filename'); + input = $('').val(oldname); + form = $('
    '); form.append(input); td.children('a.name').hide(); td.append(form); @@ -322,18 +325,29 @@ var FileList={ if (len === -1) { len = input.val().length; } - input.selectRange(0,len); + input.selectRange(0, len); - form.submit(function(event){ + var checkInput = function () { + var filename = input.val(); + if (filename !== oldname) { + if (!Files.isFileNameValid(filename)) { + // Files.isFileNameValid(filename) throws an exception itself + } else if($('#dir').val() === '/' && filename === 'Shared') { + throw t('files','Invalid name. Usage of \'Shared\' is reserved by ownCloud'); + } else if (FileList.inList(filename)) { + throw t('files', '{new_name} already exists', {new_name: filename}); + } + } + return true; + }; + + form.submit(function(event) { event.stopPropagation(); event.preventDefault(); - var newname=input.val(); - if (!Files.isFileNameValid(newname)) { - return false; - } else if (newname != name) { - if (FileList.checkName(name, newname, false)) { - newname = name; - } else { + try { + var newname = input.val(); + if (newname !== oldname) { + checkInput(); // save background image, because it's replaced by a spinner while async request var oldBackgroundImage = td.css('background-image'); // mark as loading @@ -343,16 +357,16 @@ var FileList={ data: { dir : $('#dir').val(), newname: newname, - file: name + file: oldname }, success: function(result) { if (!result || result.status === 'error') { - OC.Notification.show(result.data.message); - newname = name; + OC.dialogs.alert(result.data.message, t('core', 'Could not rename file')); // revert changes + newname = oldname; tr.attr('data-file', newname); var path = td.children('a.name').attr('href'); - td.children('a.name').attr('href', path.replace(encodeURIComponent(name), encodeURIComponent(newname))); + td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newname))); if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { var basename=newname.substr(0,newname.lastIndexOf('.')); } else { @@ -360,7 +374,7 @@ var FileList={ } td.find('a.name span.nametext').text(basename); if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { - if (td.find('a.name span.extension').length === 0 ) { + if ( ! td.find('a.name span.extension').exists() ) { td.find('a.name span.nametext').append(''); } td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.'))); @@ -368,70 +382,76 @@ var FileList={ tr.find('.fileactions').effect('highlight', {}, 5000); tr.effect('highlight', {}, 5000); } + // reinsert row + tr.detach(); + FileList.insertElement( tr.attr('data-file'), tr.attr('data-type'),tr ); // remove loading mark and recover old image td.css('background-image', oldBackgroundImage); } }); } - } - tr.data('renaming',false); - tr.attr('data-file', newname); - var path = td.children('a.name').attr('href'); - td.children('a.name').attr('href', path.replace(encodeURIComponent(name), encodeURIComponent(newname))); - if (newname.indexOf('.') > 0 && tr.data('type') != 'dir') { - var basename=newname.substr(0,newname.lastIndexOf('.')); - } else { - var basename=newname; - } - td.find('a.name span.nametext').text(basename); - if (newname.indexOf('.') > 0 && tr.data('type') != 'dir') { - if (td.find('a.name span.extension').length == 0 ) { - td.find('a.name span.nametext').append(''); + input.tipsy('hide'); + tr.data('renaming',false); + tr.attr('data-file', newname); + var path = td.children('a.name').attr('href'); + // FIXME this will fail if the path contains the filename. + td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newname))); + var basename = newname; + if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { + basename = newname.substr(0, newname.lastIndexOf('.')); + } + td.find('a.name span.nametext').text(basename); + if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { + if ( ! td.find('a.name span.extension').exists() ) { + td.find('a.name span.nametext').append(''); + } + td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.'))); } - td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.'))); + form.remove(); + td.children('a.name').show(); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); } - form.remove(); - td.children('a.name').show(); return false; }); - input.keyup(function(event){ - if (event.keyCode == 27) { + input.keyup(function(event) { + // verify filename on typing + try { + checkInput(); + input.tipsy('hide'); + input.removeClass('error'); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); + } + if (event.keyCode === 27) { + input.tipsy('hide'); tr.data('renaming',false); form.remove(); td.children('a.name').show(); } }); - input.click(function(event){ + input.click(function(event) { event.stopPropagation(); event.preventDefault(); }); - input.blur(function(){ + input.blur(function() { form.trigger('submit'); }); }, - checkName:function(oldName, newName, isNewFile) { - if (isNewFile || $('tr').filterAttr('data-file', newName).length > 0) { - var html; - if(isNewFile){ - html = t('files', '{new_name} already exists', {new_name: escapeHTML(newName)})+''+t('files', 'replace')+''+t('files', 'suggest name')+' '+t('files', 'cancel')+''; - }else{ - html = t('files', '{new_name} already exists', {new_name: escapeHTML(newName)})+''+t('files', 'replace')+''+t('files', 'cancel')+''; - } - html = $('' + html + ''); - html.attr('data-oldName', oldName); - html.attr('data-newName', newName); - html.attr('data-isNewFile', isNewFile); - OC.Notification.showHtml(html); - return true; - } else { - return false; - } + inList:function(filename) { + return $('#fileList tr[data-file="'+filename+'"]').length; }, replace:function(oldName, newName, isNewFile) { // Finish any existing actions - $('tr').filterAttr('data-file', oldName).hide(); - $('tr').filterAttr('data-file', newName).hide(); - var tr = $('tr').filterAttr('data-file', oldName).clone(); + $('tr[data-file="'+oldName+'"]').hide(); + $('tr[data-file="'+newName+'"]').hide(); + var tr = $('tr[data-file="'+oldName+'"]').clone(); tr.attr('data-replace', 'true'); tr.attr('data-file', newName); var td = tr.children('td.filename'); @@ -460,14 +480,14 @@ var FileList={ FileList.finishReplace(); }; if (!isNewFile) { - OC.Notification.showHtml(t('files', 'replaced {new_name} with {old_name}', {new_name: newName}, {old_name: oldName})+''+t('files', 'undo')+''); + OC.Notification.showHtml(t('files', 'replaced {new_name} with {old_name}', {new_name: newName}, {old_name: oldName})+''+t('files', 'undo')+''); } }, finishReplace:function() { if (!FileList.replaceCanceled && FileList.replaceOldName && FileList.replaceNewName) { $.ajax({url: OC.filePath('files', 'ajax', 'rename.php'), async: false, data: { dir: $('#dir').val(), newname: FileList.replaceNewName, file: FileList.replaceOldName }, success: function(result) { - if (result && result.status == 'success') { - $('tr').filterAttr('data-replace', 'true').removeAttr('data-replace'); + if (result && result.status === 'success') { + $('tr[data-replace="true"').removeAttr('data-replace'); } else { OC.dialogs.alert(result.data.message, 'Error moving file'); } @@ -478,12 +498,12 @@ var FileList={ }}); } }, - do_delete:function(files){ - if(files.substr){ + do_delete:function(files) { + if (files.substr) { files=[files]; } for (var i=0; i 0 ) { + if ( $('#fileList tr').exists() ) { var totalDirs = 0; var totalFiles = 0; var totalSize = 0; @@ -536,7 +556,7 @@ var FileList={ var infoVars = { dirs: ''+directoryInfo+'', files: ''+fileInfo+'' - } + }; var info = t('files', '{dirs} and {files}', infoVars); @@ -618,10 +638,10 @@ var FileList={ } } }, - showMask: function(){ + showMask: function() { // in case one was shown before var $mask = $('#content .mask'); - if ($mask.length){ + if ($mask.exists()) { return; } @@ -632,31 +652,31 @@ var FileList={ $('#content').append($mask); // block UI, but only make visible in case loading takes longer - FileList._maskTimeout = window.setTimeout(function(){ + FileList._maskTimeout = window.setTimeout(function() { // reset opacity $mask.removeClass('transparent'); }, 250); }, - hideMask: function(){ + hideMask: function() { var $mask = $('#content .mask').remove(); - if (FileList._maskTimeout){ + if (FileList._maskTimeout) { window.clearTimeout(FileList._maskTimeout); } }, scrollTo:function(file) { //scroll to and highlight preselected file - var scrolltorow = $('tr[data-file="'+file+'"]'); - if (scrolltorow.length > 0) { - scrolltorow.addClass('searchresult'); - $(window).scrollTop(scrolltorow.position().top); + var $scrolltorow = $('tr[data-file="'+file+'"]'); + if ($scrolltorow.exists()) { + $scrolltorow.addClass('searchresult'); + $(window).scrollTop($scrolltorow.position().top); //remove highlight when hovered over - scrolltorow.one('hover', function(){ - scrolltorow.removeClass('searchresult'); + $scrolltorow.one('hover', function() { + $scrolltorow.removeClass('searchresult'); }); } }, - filter:function(query){ - $('#fileList tr:not(.summary)').each(function(i,e){ + filter:function(query) { + $('#fileList tr:not(.summary)').each(function(i,e) { if ($(e).data('file').toLowerCase().indexOf(query.toLowerCase()) !== -1) { $(e).addClass("searchresult"); } else { @@ -665,18 +685,18 @@ var FileList={ }); //do not use scrollto to prevent removing searchresult css class var first = $('#fileList tr.searchresult').first(); - if (first.length !== 0) { + if (first.exists()) { $(window).scrollTop(first.position().top); } }, - unfilter:function(){ - $('#fileList tr.searchresult').each(function(i,e){ + unfilter:function() { + $('#fileList tr.searchresult').each(function(i,e) { $(e).removeClass("searchresult"); }); } }; -$(document).ready(function(){ +$(document).ready(function() { var isPublic = !!$('#isPublic').val(); // handle upload events @@ -686,16 +706,16 @@ $(document).ready(function(){ OC.Upload.log('filelist handle fileuploaddrop', e, data); var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); - if(dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder + if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder // remember as context data.context = dropTarget; var dir = dropTarget.data('file'); // if from file list, need to prepend parent dir - if (dir){ + if (dir) { var parentDir = $('#dir').val() || '/'; - if (parentDir[parentDir.length - 1] != '/'){ + if (parentDir[parentDir.length - 1] !== '/') { parentDir += '/'; } dir = parentDir + dir; @@ -719,12 +739,12 @@ $(document).ready(function(){ OC.Upload.log('filelist handle fileuploadadd', e, data); //finish delete if we are uploading a deleted file - if(FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1){ + if (FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1) { FileList.finishDelete(null, true); //delete file before continuing } // add ui visualization to existing folder - if(data.context && data.context.data('type') === 'dir') { + if (data.context && data.context.data('type') === 'dir') { // add to existing folder // update upload counter ui @@ -734,7 +754,7 @@ $(document).ready(function(){ uploadtext.attr('currentUploads', currentUploads); var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); - if(currentUploads === 1) { + if (currentUploads === 1) { var img = OC.imagePath('core', 'loading.gif'); data.context.find('td.filename').attr('style','background-image:url('+img+')'); uploadtext.text(translatedText); @@ -761,7 +781,7 @@ $(document).ready(function(){ } var result=$.parseJSON(response); - if(typeof result[0] !== 'undefined' && result[0].status === 'success') { + if (typeof result[0] !== 'undefined' && result[0].status === 'success') { var file = result[0]; if (data.context && data.context.data('type') === 'dir') { @@ -772,7 +792,7 @@ $(document).ready(function(){ currentUploads -= 1; uploadtext.attr('currentUploads', currentUploads); var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); - if(currentUploads === 0) { + if (currentUploads === 0) { var img = OC.imagePath('core', 'filetypes/folder.png'); data.context.find('td.filename').attr('style','background-image:url('+img+')'); uploadtext.text(translatedText); @@ -789,18 +809,18 @@ $(document).ready(function(){ } else { // only append new file if dragged onto current dir's crumb (last) - if (data.context && data.context.hasClass('crumb') && !data.context.hasClass('last')){ + if (data.context && data.context.hasClass('crumb') && !data.context.hasClass('last')) { return; } // add as stand-alone row to filelist var size=t('files', 'Pending'); - if (data.files[0].size>=0){ + if (data.files[0].size>=0) { size=data.files[0].size; } var date=new Date(); var param = {}; - if ($('#publicUploadRequestToken').length) { + if ($('#publicUploadRequestToken').exists()) { param.download_url = document.location.href + '&download&path=/' + $('#dir').val() + '/' + file.name; } //should the file exist in the list remove it @@ -813,14 +833,14 @@ $(document).ready(function(){ data.context.attr('data-mime',file.mime).attr('data-id',file.id); var permissions = data.context.data('permissions'); - if(permissions !== file.permissions) { + if (permissions !== file.permissions) { data.context.attr('data-permissions', file.permissions); data.context.data('permissions', file.permissions); } FileActions.display(data.context.find('td.filename'), true); var path = getPathForPreview(file.name); - lazyLoadPreview(path, file.mime, function(previewpath){ + lazyLoadPreview(path, file.mime, function(previewpath) { data.context.find('td.filename').attr('style','background-image:url('+previewpath+')'); }); } @@ -854,10 +874,10 @@ $(document).ready(function(){ }); $('#notification').hide(); - $('#notification').on('click', '.undo', function(){ + $('#notification').on('click', '.undo', function() { if (FileList.deleteFiles) { - $.each(FileList.deleteFiles,function(index,file){ - $('tr').filterAttr('data-file',file).show(); + $.each(FileList.deleteFiles,function(index,file) { + $('tr[data-file="'+file+'"]').show(); }); FileList.deleteCanceled=true; FileList.deleteFiles=null; @@ -867,10 +887,10 @@ $(document).ready(function(){ FileList.deleteCanceled = false; FileList.deleteFiles = [FileList.replaceOldName]; } else { - $('tr').filterAttr('data-file', FileList.replaceOldName).show(); + $('tr[data-file="'+FileList.replaceOldName+'"]').show(); } - $('tr').filterAttr('data-replace', 'true').remove(); - $('tr').filterAttr('data-file', FileList.replaceNewName).show(); + $('tr[data-replace="true"').remove(); + $('tr[data-file="'+FileList.replaceNewName+'"]').show(); FileList.replaceCanceled = true; FileList.replaceOldName = null; FileList.replaceNewName = null; @@ -885,7 +905,7 @@ $(document).ready(function(){ }); }); $('#notification:first-child').on('click', '.suggest', function() { - $('tr').filterAttr('data-file', $('#notification > span').attr('data-oldName')).show(); + $('tr[data-file="'+$('#notification > span').attr('data-oldName')+'"]').show(); OC.Notification.hide(); }); $('#notification:first-child').on('click', '.cancel', function() { @@ -895,67 +915,67 @@ $(document).ready(function(){ } }); FileList.useUndo=(window.onbeforeunload)?true:false; - $(window).bind('beforeunload', function (){ + $(window).bind('beforeunload', function () { if (FileList.lastAction) { FileList.lastAction(); } }); - $(window).unload(function (){ + $(window).unload(function () { $(window).trigger('beforeunload'); }); - function decodeQuery(query){ + function decodeQuery(query) { return query.replace(/\+/g, ' '); } - function parseHashQuery(){ + function parseHashQuery() { var hash = window.location.hash, pos = hash.indexOf('?'), query; - if (pos >= 0){ + if (pos >= 0) { return hash.substr(pos + 1); } return ''; } - function parseCurrentDirFromUrl(){ + function parseCurrentDirFromUrl() { var query = parseHashQuery(), params, dir = '/'; // try and parse from URL hash first - if (query){ + if (query) { params = OC.parseQueryString(decodeQuery(query)); } // else read from query attributes - if (!params){ + if (!params) { params = OC.parseQueryString(decodeQuery(location.search)); } return (params && params.dir) || '/'; } // disable ajax/history API for public app (TODO: until it gets ported) - if (!isPublic){ + if (!isPublic) { // fallback to hashchange when no history support - if (!window.history.pushState){ - $(window).on('hashchange', function(){ + if (!window.history.pushState) { + $(window).on('hashchange', function() { FileList.changeDirectory(parseCurrentDirFromUrl(), false); }); } - window.onpopstate = function(e){ + window.onpopstate = function(e) { var targetDir; - if (e.state && e.state.dir){ + if (e.state && e.state.dir) { targetDir = e.state.dir; } else{ // read from URL targetDir = parseCurrentDirFromUrl(); } - if (targetDir){ + if (targetDir) { FileList.changeDirectory(targetDir, false); } - } + }; - if (parseInt($('#ajaxLoad').val(), 10) === 1){ + if (parseInt($('#ajaxLoad').val(), 10) === 1) { // need to initially switch the dir to the one from the hash (IE8) FileList.changeDirectory(parseCurrentDirFromUrl(), false, true); } diff --git a/apps/files/js/files.js b/apps/files/js/files.js index c3a8d81b50..3567904f2f 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -1,18 +1,18 @@ Files={ updateMaxUploadFilesize:function(response) { - if(response == undefined) { + if (response === undefined) { return; } - if(response.data !== undefined && response.data.uploadMaxFilesize !== undefined) { + if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) { $('#max_upload').val(response.data.uploadMaxFilesize); $('#upload.button').attr('original-title', response.data.maxHumanFilesize); $('#usedSpacePercent').val(response.data.usedSpacePercent); Files.displayStorageWarnings(); } - if(response[0] == undefined) { + if (response[0] === undefined) { return; } - if(response[0].uploadMaxFilesize !== undefined) { + if (response[0].uploadMaxFilesize !== undefined) { $('#max_upload').val(response[0].uploadMaxFilesize); $('#upload.button').attr('original-title', response[0].maxHumanFilesize); $('#usedSpacePercent').val(response[0].usedSpacePercent); @@ -22,23 +22,18 @@ Files={ }, isFileNameValid:function (name) { if (name === '.') { - OC.Notification.show(t('files', '\'.\' is an invalid file name.')); - return false; - } - if (name.length == 0) { - OC.Notification.show(t('files', 'File name cannot be empty.')); - return false; + throw t('files', '\'.\' is an invalid file name.'); + } else if (name.length === 0) { + throw t('files', 'File name cannot be empty.'); } // check for invalid characters var invalid_characters = ['\\', '/', '<', '>', ':', '"', '|', '?', '*']; for (var i = 0; i < invalid_characters.length; i++) { - if (name.indexOf(invalid_characters[i]) != -1) { - OC.Notification.show(t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed.")); - return false; + if (name.indexOf(invalid_characters[i]) !== -1) { + throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed."); } } - OC.Notification.hide(); return true; }, displayStorageWarnings: function() { @@ -78,18 +73,18 @@ Files={ } }, - setupDragAndDrop: function(){ + setupDragAndDrop: function() { var $fileList = $('#fileList'); //drag/drop of files - $fileList.find('tr td.filename').each(function(i,e){ + $fileList.find('tr td.filename').each(function(i,e) { if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) { $(e).draggable(dragOptions); } }); - $fileList.find('tr[data-type="dir"] td.filename').each(function(i,e){ - if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE){ + $fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) { + if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) { $(e).droppable(folderDropOptions); } }); @@ -127,9 +122,9 @@ Files={ }, resizeBreadcrumbs: function (width, firstRun) { - if (width != Files.lastWidth) { + if (width !== Files.lastWidth) { if ((width < Files.lastWidth || firstRun) && width < Files.breadcrumbsWidth) { - if (Files.hiddenBreadcrumbs == 0) { + if (Files.hiddenBreadcrumbs === 0) { Files.breadcrumbsWidth -= $(Files.breadcrumbs[1]).get(0).offsetWidth; $(Files.breadcrumbs[1]).find('a').hide(); $(Files.breadcrumbs[1]).append('...'); @@ -141,12 +136,12 @@ Files={ Files.breadcrumbsWidth -= $(Files.breadcrumbs[i]).get(0).offsetWidth; $(Files.breadcrumbs[i]).hide(); Files.hiddenBreadcrumbs = i; - i++ + i++; } } else if (width > Files.lastWidth && Files.hiddenBreadcrumbs > 0) { var i = Files.hiddenBreadcrumbs; while (width > Files.breadcrumbsWidth && i > 0) { - if (Files.hiddenBreadcrumbs == 1) { + if (Files.hiddenBreadcrumbs === 1) { Files.breadcrumbsWidth -= $(Files.breadcrumbs[1]).get(0).offsetWidth; $(Files.breadcrumbs[1]).find('span').remove(); $(Files.breadcrumbs[1]).find('a').show(); @@ -170,7 +165,7 @@ Files={ }; $(document).ready(function() { // FIXME: workaround for trashbin app - if (window.trashBinApp){ + if (window.trashBinApp) { return; } Files.displayEncryptionWarning(); @@ -215,7 +210,7 @@ $(document).ready(function() { var rows = $(this).parent().parent().parent().children('tr'); for (var i = start; i < end; i++) { $(rows).each(function(index) { - if (index == i) { + if (index === i) { var checkbox = $(this).children().children('input:checkbox'); $(checkbox).attr('checked', 'checked'); $(checkbox).parent().parent().addClass('selected'); @@ -232,23 +227,23 @@ $(document).ready(function() { } else { $(checkbox).attr('checked', 'checked'); $(checkbox).parent().parent().toggleClass('selected'); - var selectedCount=$('td.filename input:checkbox:checked').length; - if (selectedCount == $('td.filename input:checkbox').length) { + var selectedCount = $('td.filename input:checkbox:checked').length; + if (selectedCount === $('td.filename input:checkbox').length) { $('#select_all').attr('checked', 'checked'); } } procesSelection(); } else { var filename=$(this).parent().parent().attr('data-file'); - var tr=$('tr').filterAttr('data-file',filename); + var tr=$('tr[data-file="'+filename+'"]'); var renaming=tr.data('renaming'); - if(!renaming && !FileList.isLoading(filename)){ + if (!renaming && !FileList.isLoading(filename)) { FileActions.currentFile = $(this).parent(); var mime=FileActions.getCurrentMimeType(); var type=FileActions.getCurrentType(); var permissions = FileActions.getCurrentPermissions(); var action=FileActions.getDefault(mime,type, permissions); - if(action){ + if (action) { event.preventDefault(); action(filename); } @@ -259,11 +254,11 @@ $(document).ready(function() { // Sets the select_all checkbox behaviour : $('#select_all').click(function() { - if($(this).attr('checked')){ + if ($(this).attr('checked')) { // Check all $('td.filename input:checkbox').attr('checked', true); $('td.filename input:checkbox').parent().parent().addClass('selected'); - }else{ + } else { // Uncheck all $('td.filename input:checkbox').attr('checked', false); $('td.filename input:checkbox').parent().parent().removeClass('selected'); @@ -280,7 +275,7 @@ $(document).ready(function() { var rows = $(this).parent().parent().parent().children('tr'); for (var i = start; i < end; i++) { $(rows).each(function(index) { - if (index == i) { + if (index === i) { var checkbox = $(this).children().children('input:checkbox'); $(checkbox).attr('checked', 'checked'); $(checkbox).parent().parent().addClass('selected'); @@ -290,10 +285,10 @@ $(document).ready(function() { } var selectedCount=$('td.filename input:checkbox:checked').length; $(this).parent().parent().toggleClass('selected'); - if(!$(this).attr('checked')){ + if (!$(this).attr('checked')) { $('#select_all').attr('checked',false); - }else{ - if(selectedCount==$('td.filename input:checkbox').length){ + } else { + if (selectedCount===$('td.filename input:checkbox').length) { $('#select_all').attr('checked',true); } } @@ -306,10 +301,11 @@ $(document).ready(function() { var dir=$('#dir').val()||'/'; OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.')); // use special download URL if provided, e.g. for public shared files - if ( (downloadURL = document.getElementById("downloadURL")) ) { - window.location=downloadURL.value+"&download&files="+encodeURIComponent(fileslist); + var downloadURL = document.getElementById("downloadURL"); + if ( downloadURL ) { + window.location = downloadURL.value+"&download&files=" + encodeURIComponent(fileslist); } else { - window.location=OC.filePath('files', 'ajax', 'download.php') + '?'+ $.param({ dir: dir, files: fileslist }); + window.location = OC.filePath('files', 'ajax', 'download.php') + '?'+ $.param({ dir: dir, files: fileslist }); } return false; }); @@ -376,12 +372,12 @@ $(document).ready(function() { } }); -function scanFiles(force, dir, users){ +function scanFiles(force, dir, users) { if (!OC.currentUser) { return; } - if(!dir){ + if (!dir) { dir = ''; } force = !!force; //cast to bool @@ -399,17 +395,17 @@ function scanFiles(force, dir, users){ scannerEventSource = new OC.EventSource(OC.filePath('files','ajax','scan.php'),{force: force,dir: dir}); } scanFiles.cancel = scannerEventSource.close.bind(scannerEventSource); - scannerEventSource.listen('count',function(count){ - console.log(count + ' files scanned') + scannerEventSource.listen('count',function(count) { + console.log(count + ' files scanned'); }); - scannerEventSource.listen('folder',function(path){ - console.log('now scanning ' + path) + scannerEventSource.listen('folder',function(path) { + console.log('now scanning ' + path); }); - scannerEventSource.listen('done',function(count){ + scannerEventSource.listen('done',function(count) { scanFiles.scanning=false; console.log('done after ' + count + ' files'); }); - scannerEventSource.listen('user',function(user){ + scannerEventSource.listen('user',function(user) { console.log('scanning files for ' + user); }); } @@ -418,14 +414,14 @@ scanFiles.scanning=false; function boolOperationFinished(data, callback) { result = jQuery.parseJSON(data.responseText); Files.updateMaxUploadFilesize(result); - if(result.status == 'success'){ + if (result.status === 'success') { callback.call(); } else { alert(result.data.message); } } -var createDragShadow = function(event){ +var createDragShadow = function(event) { //select dragged file var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked'); if (!isDragSelected) { @@ -435,7 +431,7 @@ var createDragShadow = function(event){ var selectedFiles = getSelectedFilesTrash(); - if (!isDragSelected && selectedFiles.length == 1) { + if (!isDragSelected && selectedFiles.length === 1) { //revert the selection $(event.target).parents('tr').find('td input:first').prop('checked',false); } @@ -452,7 +448,7 @@ var createDragShadow = function(event){ var dir=$('#dir').val(); - $(selectedFiles).each(function(i,elem){ + $(selectedFiles).each(function(i,elem) { var newtr = $('').attr('data-dir', dir).attr('data-filename', elem.name); newtr.append($('').addClass('filename').text(elem.name)); newtr.append($('').addClass('size').text(humanFileSize(elem.size))); @@ -461,14 +457,14 @@ var createDragShadow = function(event){ newtr.find('td.filename').attr('style','background-image:url('+OC.imagePath('core', 'filetypes/folder.png')+')'); } else { var path = getPathForPreview(elem.name); - lazyLoadPreview(path, elem.mime, function(previewpath){ + lazyLoadPreview(path, elem.mime, function(previewpath) { newtr.find('td.filename').attr('style','background-image:url('+previewpath+')'); }); } }); return dragshadow; -} +}; //options for file drag/drop var dragOptions={ @@ -478,7 +474,7 @@ var dragOptions={ stop: function(event, ui) { $('#fileList tr td.filename').addClass('ui-draggable'); } -} +}; // sane browsers support using the distance option if ( $('html.ie').length === 0) { dragOptions['distance'] = 20; @@ -491,20 +487,20 @@ var folderDropOptions={ return false; } - var target=$.trim($(this).find('.nametext').text()); + var target = $.trim($(this).find('.nametext').text()); var files = ui.helper.find('tr'); - $(files).each(function(i,row){ + $(files).each(function(i,row) { var dir = $(row).data('dir'); var file = $(row).data('filename'); $.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: dir+'/'+target }, function(result) { if (result) { if (result.status === 'success') { //recalculate folder size - var oldSize = $('#fileList tr').filterAttr('data-file',target).data('size'); - var newSize = oldSize + $('#fileList tr').filterAttr('data-file',file).data('size'); - $('#fileList tr').filterAttr('data-file',target).data('size', newSize); - $('#fileList tr').filterAttr('data-file',target).find('td.filesize').text(humanFileSize(newSize)); + var oldSize = $('#fileList tr[data-file="'+target+'"]').data('size'); + var newSize = oldSize + $('#fileList tr[data-file="'+file+'"]').data('size'); + $('#fileList tr[data-file="'+target+'"]').data('size', newSize); + $('#fileList tr[data-file="'+target+'"]').find('td.filesize').text(humanFileSize(newSize)); FileList.remove(file); procesSelection(); @@ -521,24 +517,24 @@ var folderDropOptions={ }); }, tolerance: 'pointer' -} +}; var crumbDropOptions={ drop: function( event, ui ) { var target=$(this).data('dir'); - var dir=$('#dir').val(); - while(dir.substr(0,1)=='/'){//remove extra leading /'s + var dir = $('#dir').val(); + while(dir.substr(0,1) === '/') {//remove extra leading /'s dir=dir.substr(1); } - dir='/'+dir; - if(dir.substr(-1,1)!='/'){ - dir=dir+'/'; + dir = '/' + dir; + if (dir.substr(-1,1) !== '/') { + dir = dir + '/'; } - if(target==dir || target+'/'==dir){ + if (target === dir || target+'/' === dir) { return; } var files = ui.helper.find('tr'); - $(files).each(function(i,row){ + $(files).each(function(i,row) { var dir = $(row).data('dir'); var file = $(row).data('filename'); $.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: target }, function(result) { @@ -559,13 +555,17 @@ var crumbDropOptions={ }); }, tolerance: 'pointer' -} +}; -function procesSelection(){ - var selected=getSelectedFilesTrash(); - var selectedFiles=selected.filter(function(el){return el.type=='file'}); - var selectedFolders=selected.filter(function(el){return el.type=='dir'}); - if(selectedFiles.length==0 && selectedFolders.length==0) { +function procesSelection() { + var selected = getSelectedFilesTrash(); + var selectedFiles = selected.filter(function(el) { + return el.type==='file'; + }); + var selectedFolders = selected.filter(function(el) { + return el.type==='dir'; + }); + if (selectedFiles.length === 0 && selectedFolders.length === 0) { $('#headerName>span.name').text(t('files','Name')); $('#headerSize').text(t('files','Size')); $('#modified').text(t('files','Modified')); @@ -574,22 +574,22 @@ function procesSelection(){ } else { $('.selectedActions').show(); - var totalSize=0; - for(var i=0;i0){ + var selection = ''; + if (selectedFolders.length > 0) { selection += n('files', '%n folder', '%n folders', selectedFolders.length); - if(selectedFiles.length>0){ - selection+=' & '; + if (selectedFiles.length > 0) { + selection += ' & '; } } - if(selectedFiles.length>0){ + if (selectedFiles.length>0) { selection += n('files', '%n file', '%n files', selectedFiles.length); } $('#headerName>span.name').text(selection); @@ -600,37 +600,37 @@ function procesSelection(){ /** * @brief get a list of selected files - * @param string property (option) the property of the file requested - * @return array + * @param {string} property (option) the property of the file requested + * @return {array} * * possible values for property: name, mime, size and type * if property is set, an array with that property for each file is returnd * if it's ommited an array of objects with all properties is returned */ -function getSelectedFilesTrash(property){ +function getSelectedFilesTrash(property) { var elements=$('td.filename input:checkbox:checked').parent().parent(); var files=[]; - elements.each(function(i,element){ + elements.each(function(i,element) { var file={ name:$(element).attr('data-file'), mime:$(element).data('mime'), type:$(element).data('type'), size:$(element).data('size') }; - if(property){ + if (property) { files.push(file[property]); - }else{ + } else { files.push(file); } }); return files; } -function getMimeIcon(mime, ready){ - if(getMimeIcon.cache[mime]){ +function getMimeIcon(mime, ready) { + if (getMimeIcon.cache[mime]) { ready(getMimeIcon.cache[mime]); - }else{ - $.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path){ + } else { + $.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) { getMimeIcon.cache[mime]=path; ready(getMimeIcon.cache[mime]); }); @@ -655,7 +655,7 @@ function lazyLoadPreview(path, mime, ready, width, height) { if ( ! height ) { height = $('#filestable').data('preview-y'); } - if( $('#publicUploadButtonMock').length ) { + if ( $('#publicUploadButtonMock').length ) { var previewURL = OC.Router.generate('core_ajax_public_preview', {file: path, x:width, y:height, t:$('#dirToken').val()}); } else { var previewURL = OC.Router.generate('core_ajax_preview', {file: path, x:width, y:height}); @@ -669,8 +669,8 @@ function lazyLoadPreview(path, mime, ready, width, height) { }); } -function getUniqueName(name){ - if($('tr').filterAttr('data-file',name).length>0){ +function getUniqueName(name) { + if ($('tr[data-file="'+name+'"]').exists()) { var parts=name.split('.'); var extension = ""; if (parts.length > 1) { @@ -679,9 +679,9 @@ function getUniqueName(name){ var base=parts.join('.'); numMatch=base.match(/\((\d+)\)/); var num=2; - if(numMatch && numMatch.length>0){ + if (numMatch && numMatch.length>0) { num=parseInt(numMatch[numMatch.length-1])+1; - base=base.split('(') + base=base.split('('); base.pop(); base=$.trim(base.join('(')); } @@ -695,19 +695,19 @@ function getUniqueName(name){ } function checkTrashStatus() { - $.post(OC.filePath('files_trashbin', 'ajax', 'isEmpty.php'), function(result){ + $.post(OC.filePath('files_trashbin', 'ajax', 'isEmpty.php'), function(result) { if (result.data.isEmpty === false) { $("input[type=button][id=trash]").removeAttr("disabled"); } }); } -function onClickBreadcrumb(e){ +function onClickBreadcrumb(e) { var $el = $(e.target).closest('.crumb'), $targetDir = $el.data('dir'); isPublic = !!$('#isPublic').val(); - if ($targetDir !== undefined && !isPublic){ + if ($targetDir !== undefined && !isPublic) { e.preventDefault(); FileList.changeDirectory(decodeURIComponent($targetDir)); } diff --git a/apps/files/lib/app.php b/apps/files/lib/app.php index 579e8676cf..ccf629ae3c 100644 --- a/apps/files/lib/app.php +++ b/apps/files/lib/app.php @@ -52,7 +52,15 @@ class App { $result['data'] = array( 'message' => $this->l10n->t("Invalid folder name. Usage of 'Shared' is reserved by ownCloud") ); - } elseif( + // rename to existing file is denied + } else if ($this->view->file_exists($dir . '/' . $newname)) { + + $result['data'] = array( + 'message' => $this->l10n->t( + "The name %s is already used in the folder %s. Please choose a different name.", + array($newname, $dir)) + ); + } else if ( // rename to "." is denied $newname !== '.' and // rename of "/Shared" is denied diff --git a/core/js/js.js b/core/js/js.js index c17e3fa295..f5991cfc9d 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -933,7 +933,7 @@ jQuery.fn.selectRange = function(start, end) { */ jQuery.fn.exists = function(){ return this.length > 0; -} +}; /** * Calls the server periodically every 15 mins to ensure that session doesnt From 83956b1703dbb12de264306664b629132cd21e0b Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 22 Oct 2013 19:16:34 +0200 Subject: [PATCH 46/82] Expand exception stack trace in log in debug mode --- index.php | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/index.php b/index.php index 40063fa6e0..1b9b4a58f7 100755 --- a/index.php +++ b/index.php @@ -23,6 +23,34 @@ $RUNTIME_NOAPPS = true; //no apps, yet +function logException($ex) { + $message = $ex->getMessage(); + if ($ex->getCode()) { + $message .= ' [' . $message . ']'; + } + \OCP\Util::writeLog('index', $message, \OCP\Util::FATAL); + if (defined('DEBUG') and DEBUG) { + // also log stack trace + $stack = explode('#', $ex->getTraceAsString()); + // first element is empty + array_shift($stack); + foreach ($stack as $s) { + \OCP\Util::writeLog('index', $s, \OCP\Util::FATAL); + } + + // include cause + $l = OC_L10N::get('lib'); + while (method_exists($ex, 'getPrevious') && $ex = $ex->getPrevious()) { + $message .= ' - '.$l->t('Caused by:').' '; + $message .= $ex->getMessage(); + if ($ex->getCode()) { + $message .= '['.$ex->getCode().'] '; + } + \OCP\Util::writeLog('index', $message, \OCP\Util::FATAL); + } + } +} + try { require_once 'lib/base.php'; @@ -30,8 +58,9 @@ try { OC::handleRequest(); } catch (Exception $ex) { + logException($ex); + //show the user a detailed error page - \OCP\Util::writeLog('index', $ex->getMessage(), \OCP\Util::FATAL); OC_Response::setStatus(OC_Response::STATUS_INTERNAL_SERVER_ERROR); OC_Template::printExceptionErrorPage($ex); } From 0283589a19518d6db266cdb84ba35e5d4bf8a979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 22 Oct 2013 22:38:05 +0200 Subject: [PATCH 47/82] adding PHPDoc comments --- apps/files/lib/app.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/files/lib/app.php b/apps/files/lib/app.php index ccf629ae3c..810a9fdd8d 100644 --- a/apps/files/lib/app.php +++ b/apps/files/lib/app.php @@ -25,7 +25,14 @@ namespace OCA\Files; class App { + /** + * @var \OC_L10N + */ private $l10n; + + /** + * @var \OC\Files\View + */ private $view; public function __construct($view, $l10n) { From bf46e0c3171ed8546a64b5907e02f3ee1fe0a5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 22 Oct 2013 22:38:48 +0200 Subject: [PATCH 48/82] fixing undefined variable $newname --- apps/files/ajax/newfile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index 8f483aa5cb..c3d1037bc3 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -64,7 +64,7 @@ if (\OC\Files\Filesystem::file_exists($target)) { 'data' => array( 'message' => $l10n->t( "The name %s is already used in the folder %s. Please choose a different name.", - array($newname, $dir)) + array($target, $dir)) ) ); OCP\JSON::error($result); From ca3771f8fd761a51ea7bbe123dcb16c5da7f4045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 22 Oct 2013 22:39:28 +0200 Subject: [PATCH 49/82] fixing js syntax error --- apps/files/js/file-upload.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 936487cad3..17673dacfd 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -21,13 +21,13 @@ function supportAjaxUploadWithProgress() { var fi = document.createElement('INPUT'); fi.type = 'file'; return 'files' in fi; - }; + } // Are progress events supported? function supportAjaxUploadProgressEvents() { var xhr = new XMLHttpRequest(); return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload)); - }; + } // Is FormData supported? function supportFormData() { From 42c58220def38d5f762dc0c422012b0210577463 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 23 Oct 2013 10:47:26 +0200 Subject: [PATCH 50/82] Moved logException to OCP\Util class --- index.php | 30 +----------------------------- lib/public/util.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/index.php b/index.php index 1b9b4a58f7..ab7333cf0e 100755 --- a/index.php +++ b/index.php @@ -23,34 +23,6 @@ $RUNTIME_NOAPPS = true; //no apps, yet -function logException($ex) { - $message = $ex->getMessage(); - if ($ex->getCode()) { - $message .= ' [' . $message . ']'; - } - \OCP\Util::writeLog('index', $message, \OCP\Util::FATAL); - if (defined('DEBUG') and DEBUG) { - // also log stack trace - $stack = explode('#', $ex->getTraceAsString()); - // first element is empty - array_shift($stack); - foreach ($stack as $s) { - \OCP\Util::writeLog('index', $s, \OCP\Util::FATAL); - } - - // include cause - $l = OC_L10N::get('lib'); - while (method_exists($ex, 'getPrevious') && $ex = $ex->getPrevious()) { - $message .= ' - '.$l->t('Caused by:').' '; - $message .= $ex->getMessage(); - if ($ex->getCode()) { - $message .= '['.$ex->getCode().'] '; - } - \OCP\Util::writeLog('index', $message, \OCP\Util::FATAL); - } - } -} - try { require_once 'lib/base.php'; @@ -58,7 +30,7 @@ try { OC::handleRequest(); } catch (Exception $ex) { - logException($ex); + \OCP\Util::logException($ex); //show the user a detailed error page OC_Response::setStatus(OC_Response::STATUS_INTERNAL_SERVER_ERROR); diff --git a/lib/public/util.php b/lib/public/util.php index b33f07b55e..00ef863c24 100644 --- a/lib/public/util.php +++ b/lib/public/util.php @@ -77,6 +77,39 @@ class Util { \OC_LOG::write( $app, $message, $level ); } + /** + * @brief write exception into the log. Include the stack trace + * if DEBUG mode is enabled + * @param Exception $ex exception to log + */ + public static function logException( \Exception $ex ) { + $message = $ex->getMessage(); + if ($ex->getCode()) { + $message .= ' [' . $ex->getCode() . ']'; + } + \OCP\Util::writeLog('index', 'Exception: ' . $message, \OCP\Util::FATAL); + if (defined('DEBUG') and DEBUG) { + // also log stack trace + $stack = explode('#', $ex->getTraceAsString()); + // first element is empty + array_shift($stack); + foreach ($stack as $s) { + \OCP\Util::writeLog('index', 'Exception: ' . $s, \OCP\Util::FATAL); + } + + // include cause + $l = \OC_L10N::get('lib'); + while (method_exists($ex, 'getPrevious') && $ex = $ex->getPrevious()) { + $message .= ' - '.$l->t('Caused by:').' '; + $message .= $ex->getMessage(); + if ($ex->getCode()) { + $message .= '[' . $ex->getCode() . '] '; + } + \OCP\Util::writeLog('index', 'Exception: ' . $message, \OCP\Util::FATAL); + } + } + } + /** * @brief get l10n object * @param string $app From 20716a153a9453d4da008a05949d848b53ea1220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 23 Oct 2013 10:15:50 +0200 Subject: [PATCH 51/82] fix message dialog titles in IE8 --- core/templates/message.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/templates/message.html b/core/templates/message.html index 59048100f3..cd642231a9 100644 --- a/core/templates/message.html +++ b/core/templates/message.html @@ -1,3 +1,3 @@ -
    +

    {message}

    From bc0faa1c4e67f0058c1d9ce89a3727f09e321c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 23 Oct 2013 10:17:50 +0200 Subject: [PATCH 52/82] use correct filename in error result json --- apps/files/ajax/newfile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index c3d1037bc3..aab856dd9a 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -64,7 +64,7 @@ if (\OC\Files\Filesystem::file_exists($target)) { 'data' => array( 'message' => $l10n->t( "The name %s is already used in the folder %s. Please choose a different name.", - array($target, $dir)) + array($filename, $dir)) ) ); OCP\JSON::error($result); From af7ec3169b98f52107c74b91d6422d2375c7a89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 23 Oct 2013 10:49:43 +0200 Subject: [PATCH 53/82] cleanup precondition checking when creating new files / folders - use i18n - use trim when checking for empty file / folder name - use more verbose error descriptions --- apps/files/ajax/newfile.php | 31 +++++++++++++++++--------- apps/files/ajax/newfolder.php | 42 ++++++++++++++++++++++++++--------- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index aab856dd9a..f64a1bcc27 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -20,15 +20,6 @@ if($source) { OC_JSON::callCheck(); } -if($filename == '') { - OCP\JSON::error(array("data" => array( "message" => "Empty Filename" ))); - exit(); -} -if(strpos($filename, '/')!==false) { - OCP\JSON::error(array("data" => array( "message" => "Invalid Filename" ))); - exit(); -} - function progress($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { static $filesize = 0; static $lastsize = 0; @@ -54,10 +45,28 @@ function progress($notification_code, $severity, $message, $message_code, $bytes } } -$target = $dir.'/'.$filename; - $l10n = \OC_L10n::get('files'); +$result = array( + 'success' => false, + 'data' => NULL + ); + +if(trim($filename) === '') { + $result['data'] = array('message' => $l10n->t('Filename cannot not be empty.')); + OCP\JSON::error($result); + exit(); +} + +if(strpos($filename, '/') !== false) { + $result['data'] = array('message' => $l10n->t('Filename must not contain /. Please choose a different name.')); + OCP\JSON::error($result); + exit(); +} + +//TODO why is stripslashes used on foldername in newfolder.php but not here? +$target = $dir.'/'.$filename; + if (\OC\Files\Filesystem::file_exists($target)) { $result = array( 'success' => false, diff --git a/apps/files/ajax/newfolder.php b/apps/files/ajax/newfolder.php index e26e1238bc..531759dc04 100644 --- a/apps/files/ajax/newfolder.php +++ b/apps/files/ajax/newfolder.php @@ -10,25 +10,47 @@ OCP\JSON::callCheck(); $dir = isset( $_POST['dir'] ) ? stripslashes($_POST['dir']) : ''; $foldername = isset( $_POST['foldername'] ) ? stripslashes($_POST['foldername']) : ''; -if(trim($foldername) == '') { - OCP\JSON::error(array("data" => array( "message" => "Empty Foldername" ))); - exit(); -} -if(strpos($foldername, '/')!==false) { - OCP\JSON::error(array("data" => array( "message" => "Invalid Foldername" ))); +$l10n = \OC_L10n::get('files'); + +$result = array( + 'success' => false, + 'data' => NULL + ); + +if(trim($foldername) === '') { + $result['data'] = array('message' => $l10n->t('Foldername cannot not be empty.')); + OCP\JSON::error($result); exit(); } -if(\OC\Files\Filesystem::mkdir($dir . '/' . stripslashes($foldername))) { - if ( $dir != '/') { +if(strpos($foldername, '/') !== false) { + $result['data'] = array('message' => $l10n->t('Foldername must not contain /. Please choose a different name.')); + OCP\JSON::error($result); + exit(); +} + +//TODO why is stripslashes used on foldername here but not in newfile.php? +$target = $dir . '/' . stripslashes($foldername); + +if (\OC\Files\Filesystem::file_exists($target)) { + $result['data'] = array('message' => $l10n->t( + 'The name %s is already used in the folder %s. Please choose a different name.', + array($foldername, $dir)) + ); + OCP\JSON::error($result); + exit(); +} + +if(\OC\Files\Filesystem::mkdir($target)) { + if ( $dir !== '/') { $path = $dir.'/'.$foldername; } else { $path = '/'.$foldername; } $meta = \OC\Files\Filesystem::getFileInfo($path); $id = $meta['fileid']; - OCP\JSON::success(array("data" => array('id'=>$id))); + OCP\JSON::success(array('data' => array('id' => $id))); exit(); } -OCP\JSON::error(array("data" => array( "message" => "Error when creating the folder" ))); +OCP\JSON::error(array('data' => array( 'message' => 'Error when creating the folder' ))); From c478a792fb13bdb410d5e9467efc06cae9163858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 23 Oct 2013 10:53:52 +0200 Subject: [PATCH 54/82] add missing ; --- core/js/octemplate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/js/octemplate.js b/core/js/octemplate.js index 46ffa97657..aab705059d 100644 --- a/core/js/octemplate.js +++ b/core/js/octemplate.js @@ -82,7 +82,7 @@ } ); } catch(e) { - console.error(e, 'data:', data) + console.error(e, 'data:', data); } }, options: { From a9735c8a6ffeb84ab5ad4c7669174ca84af67c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 23 Oct 2013 11:01:05 +0200 Subject: [PATCH 55/82] cleanup array value assignment --- apps/files/ajax/newfile.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index f64a1bcc27..a58948bf68 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -68,14 +68,10 @@ if(strpos($filename, '/') !== false) { $target = $dir.'/'.$filename; if (\OC\Files\Filesystem::file_exists($target)) { - $result = array( - 'success' => false, - 'data' => array( - 'message' => $l10n->t( - "The name %s is already used in the folder %s. Please choose a different name.", - array($filename, $dir)) - ) - ); + $result['data'] = array('message' => $l10n->t( + "The name %s is already used in the folder %s. Please choose a different name.", + array($filename, $dir)) + ); OCP\JSON::error($result); exit(); } From 8ed73e5cedac5e22a8e50d1fdf04feb400b676fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 23 Oct 2013 11:08:30 +0200 Subject: [PATCH 56/82] use a more descriptive error message when the user tries to create '/Shared' --- apps/files/js/file-upload.js | 4 ++-- apps/files/js/filelist.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 17673dacfd..5bf4f5c098 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -508,11 +508,11 @@ $(document).ready(function() { var checkInput = function () { var filename = input.val(); if (type === 'web' && filename.length === 0) { - throw t('files', 'URL cannot be empty.'); + throw t('files', 'URL cannot be empty'); } else if (type !== 'web' && !Files.isFileNameValid(filename)) { // Files.isFileNameValid(filename) throws an exception itself } else if ($('#dir').val() === '/' && filename === 'Shared') { - throw t('files','Invalid name. Usage of \'Shared\' is reserved by ownCloud'); + throw t('files', 'In the home folder \'Shared\' is a reserved filename'); } else if (FileList.inList(filename)) { throw t('files', '{new_name} already exists', {new_name: filename}); } else { diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 78eaa9761a..980260928e 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -359,7 +359,7 @@ var FileList={ if (!Files.isFileNameValid(filename)) { // Files.isFileNameValid(filename) throws an exception itself } else if($('#dir').val() === '/' && filename === 'Shared') { - throw t('files','Invalid name. Usage of \'Shared\' is reserved by ownCloud'); + throw t('files','In the home folder \'Shared\' is a reserved filename'); } else if (FileList.inList(filename)) { throw t('files', '{new_name} already exists', {new_name: filename}); } From 30c0f5dee602e8b09963f2be5234eeff04193eb5 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 23 Oct 2013 12:20:13 +0200 Subject: [PATCH 57/82] LDAP Wizard: proper strings and translations for user and group count text --- apps/user_ldap/lib/wizard.php | 12 +++++++++--- apps/user_ldap/templates/part.wizard-groupfilter.php | 2 +- apps/user_ldap/templates/part.wizard-userfilter.php | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index 7b7ef45af4..9b84c3d5a4 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -73,8 +73,10 @@ class Wizard extends LDAPUtility { $base = $this->configuration->ldapBase[0]; $filter = $this->configuration->ldapGroupFilter; \OCP\Util::writeLog('user_ldap', 'Wiz: g filter '. print_r($filter, true), \OCP\Util::DEBUG); + $l = \OC_L10N::get('user_ldap'); if(empty($filter)) { - $this->result->addChange('ldap_group_count', 0); + $output = $l->n('%s group found', '%s groups found', 0, array(0)); + $this->result->addChange('ldap_group_count', $output); return $this->result; } $cr = $this->getConnection(); @@ -87,7 +89,8 @@ class Wizard extends LDAPUtility { } $entries = $this->ldap->countEntries($cr, $rr); $entries = ($entries !== false) ? $entries : 0; - $this->result->addChange('ldap_group_count', $entries); + $output = $l->n('%s group found', '%s groups found', $entries, $entries); + $this->result->addChange('ldap_group_count', $output); return $this->result; } @@ -116,11 +119,14 @@ class Wizard extends LDAPUtility { } $entries = $this->ldap->countEntries($cr, $rr); $entries = ($entries !== false) ? $entries : 0; - $this->result->addChange('ldap_user_count', $entries); + $l = \OC_L10N::get('user_ldap'); + $output = $l->n('%s user found', '%s users found', $entries, $entries); + $this->result->addChange('ldap_user_count', $output); return $this->result; } + public function determineAttributes() { if(!$this->checkRequirements(array('ldapHost', 'ldapPort', diff --git a/apps/user_ldap/templates/part.wizard-groupfilter.php b/apps/user_ldap/templates/part.wizard-groupfilter.php index 17ce815589..0cc4dfa572 100644 --- a/apps/user_ldap/templates/part.wizard-groupfilter.php +++ b/apps/user_ldap/templates/part.wizard-groupfilter.php @@ -35,7 +35,7 @@

    - 0 t('group(s) found'));?> + 0 t('groups found'));?>

    diff --git a/apps/user_ldap/templates/part.wizard-userfilter.php b/apps/user_ldap/templates/part.wizard-userfilter.php index 146e6bb739..c1d522ce2a 100644 --- a/apps/user_ldap/templates/part.wizard-userfilter.php +++ b/apps/user_ldap/templates/part.wizard-userfilter.php @@ -35,7 +35,7 @@

    - 0 t('user(s) found'));?> + 0 t('users found'));?>

    From 3c710696e8ae72ff2f264d2df8b20cd364fe5003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 23 Oct 2013 12:27:54 +0200 Subject: [PATCH 58/82] add missing file header --- lib/private/db/connectionwrapper.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/private/db/connectionwrapper.php b/lib/private/db/connectionwrapper.php index 8b195ca107..93d4fb57f7 100644 --- a/lib/private/db/connectionwrapper.php +++ b/lib/private/db/connectionwrapper.php @@ -1,4 +1,10 @@ + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ namespace OC\DB; From a542c57a7b6fc2c85887d11c16575ecc3d64fa3d Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 21 Oct 2013 14:48:08 +0200 Subject: [PATCH 59/82] Catch duplicate insertion errors while scanning files When two scanning processed run at the same time, for example when scan.php and list.php were called at the same time on an external storage mountpoint, duplicate insertion errors can occurs. These errors are now logged and ignored. Since both scans are running in parallel transactions, they don't see each other's changes directly in the DB which can cause duplicate insertion errors for mime types as well, but those mime types can't be selected yet. The solution to this is to force-reload the mimetypes list after the transaction is finished. Fixes #5199 --- lib/private/files/cache/cache.php | 16 +++++++++---- lib/private/files/cache/scanner.php | 36 +++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php index 364a50d377..fc2d965d7f 100644 --- a/lib/private/files/cache/cache.php +++ b/lib/private/files/cache/cache.php @@ -69,9 +69,15 @@ class Cache { } if (!isset(self::$mimetypeIds[$mime])) { - $result = \OC_DB::executeAudited('INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)', array($mime)); - self::$mimetypeIds[$mime] = \OC_DB::insertid('*PREFIX*mimetypes'); - self::$mimetypes[self::$mimetypeIds[$mime]] = $mime; + try{ + $result = \OC_DB::executeAudited('INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)', array($mime)); + self::$mimetypeIds[$mime] = \OC_DB::insertid('*PREFIX*mimetypes'); + self::$mimetypes[self::$mimetypeIds[$mime]] = $mime; + } + catch (\Doctrine\DBAL\DBALException $e){ + \OC_Log::write('core', 'Exception during mimetype insertion: ' . $e->getmessage(), \OC_Log::DEBUG); + return -1; + } } return self::$mimetypeIds[$mime]; @@ -84,8 +90,8 @@ class Cache { return isset(self::$mimetypes[$id]) ? self::$mimetypes[$id] : null; } - - protected function loadMimetypes(){ + + public function loadMimetypes(){ $result = \OC_DB::executeAudited('SELECT `id`, `mimetype` FROM `*PREFIX*mimetypes`', array()); if ($result) { while ($row = $result->fetchRow()) { diff --git a/lib/private/files/cache/scanner.php b/lib/private/files/cache/scanner.php index 96f84609cf..f63abf2d4f 100644 --- a/lib/private/files/cache/scanner.php +++ b/lib/private/files/cache/scanner.php @@ -190,24 +190,34 @@ class Scanner extends BasicEmitter { } $newChildren = array(); if ($this->storage->is_dir($path) && ($dh = $this->storage->opendir($path))) { + $exceptionOccurred = false; \OC_DB::beginTransaction(); if (is_resource($dh)) { while (($file = readdir($dh)) !== false) { $child = ($path) ? $path . '/' . $file : $file; if (!Filesystem::isIgnoredDir($file)) { $newChildren[] = $file; - $data = $this->scanFile($child, $reuse, true); - if ($data) { - if ($data['size'] === -1) { - if ($recursive === self::SCAN_RECURSIVE) { - $childQueue[] = $child; - } else { - $size = -1; + try { + $data = $this->scanFile($child, $reuse, true); + if ($data) { + if ($data['size'] === -1) { + if ($recursive === self::SCAN_RECURSIVE) { + $childQueue[] = $child; + } else { + $size = -1; + } + } else if ($size !== -1) { + $size += $data['size']; } - } else if ($size !== -1) { - $size += $data['size']; } } + catch (\Doctrine\DBAL\DBALException $ex){ + // might happen if inserting duplicate while a scanning + // process is running in parallel + // log and ignore + \OC_Log::write('core', 'Exception while scanning file "' . $child . '": ' . $ex->getMessage(), \OC_Log::DEBUG); + $exceptionOccurred = true; + } } } } @@ -217,6 +227,14 @@ class Scanner extends BasicEmitter { $this->cache->remove($child); } \OC_DB::commit(); + if ($exceptionOccurred){ + // It might happen that the parallel scan process has already + // inserted mimetypes but those weren't available yet inside the transaction + // To make sure to have the updated mime types in such cases, + // we reload them here + $this->cache->loadMimetypes(); + } + foreach ($childQueue as $child) { $childSize = $this->scanChildren($child, self::SCAN_RECURSIVE, $reuse); if ($childSize === -1) { From c4dee281e693088e445a075d5f59f54e34a1010b Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 23 Oct 2013 13:58:42 +0200 Subject: [PATCH 60/82] Added $app parameter to logException --- index.php | 2 +- lib/public/util.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.php b/index.php index ab7333cf0e..0a2f15f9f5 100755 --- a/index.php +++ b/index.php @@ -30,7 +30,7 @@ try { OC::handleRequest(); } catch (Exception $ex) { - \OCP\Util::logException($ex); + \OCP\Util::logException('index', $ex); //show the user a detailed error page OC_Response::setStatus(OC_Response::STATUS_INTERNAL_SERVER_ERROR); diff --git a/lib/public/util.php b/lib/public/util.php index 00ef863c24..ed0622b8d1 100644 --- a/lib/public/util.php +++ b/lib/public/util.php @@ -82,19 +82,19 @@ class Util { * if DEBUG mode is enabled * @param Exception $ex exception to log */ - public static function logException( \Exception $ex ) { + public static function logException( $app, \Exception $ex ) { $message = $ex->getMessage(); if ($ex->getCode()) { $message .= ' [' . $ex->getCode() . ']'; } - \OCP\Util::writeLog('index', 'Exception: ' . $message, \OCP\Util::FATAL); + \OCP\Util::writeLog($app, 'Exception: ' . $message, \OCP\Util::FATAL); if (defined('DEBUG') and DEBUG) { // also log stack trace $stack = explode('#', $ex->getTraceAsString()); // first element is empty array_shift($stack); foreach ($stack as $s) { - \OCP\Util::writeLog('index', 'Exception: ' . $s, \OCP\Util::FATAL); + \OCP\Util::writeLog($app, 'Exception: ' . $s, \OCP\Util::FATAL); } // include cause @@ -105,7 +105,7 @@ class Util { if ($ex->getCode()) { $message .= '[' . $ex->getCode() . '] '; } - \OCP\Util::writeLog('index', 'Exception: ' . $message, \OCP\Util::FATAL); + \OCP\Util::writeLog($app, 'Exception: ' . $message, \OCP\Util::FATAL); } } } From 76f831979b7d15b60c2324ce7a2904a10725af7a Mon Sep 17 00:00:00 2001 From: Johannes Twittmann Date: Wed, 23 Oct 2013 14:21:28 +0200 Subject: [PATCH 61/82] Update CONTRIBUTING.md Gave more info on how to search issues. --- CONTRIBUTING.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a79fcc08d6..2360a082a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,10 @@ If you have questions about how to install or use ownCloud, please direct these ### Guidelines * Please search the existing issues first, it's likely that your issue was already reported or even fixed. -* This repository is *only* for issues within the ownCloud core code. This also includes the apps: files, encryption, external storage, sharing, deleted files, versions, LDAP, and WebDAV Auth + - Go to one of the repositories, click "issues" and type any word in the top search/command bar. + - You can also filter by appending e. g. "state:open" to the search string. + - More info on [search syntax within github](https://help.github.com/articles/searching-issues) +* This repository ([core](https://github.com/owncloud/core/issues)) is *only* for issues within the ownCloud core code. This also includes the apps: files, encryption, external storage, sharing, deleted files, versions, LDAP, and WebDAV Auth * The issues in other components should be reported in their respective repositories: - [Android client](https://github.com/owncloud/android/issues) - [iOS client](https://github.com/owncloud/ios-issues/issues) From 5aeafab9ab62b9af415229cac56ed564c10eee74 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 23 Oct 2013 15:20:31 +0200 Subject: [PATCH 62/82] Fixed wrong id when reading checkbox value for force SSL setting Fixes #5488 --- settings/js/admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/js/admin.js b/settings/js/admin.js index f2d6f37a51..e957bd68f1 100644 --- a/settings/js/admin.js +++ b/settings/js/admin.js @@ -32,6 +32,6 @@ $(document).ready(function(){ }); $('#security').change(function(){ - $.post(OC.filePath('settings','ajax','setsecurity.php'), { enforceHTTPS: $('#enforceHTTPSEnabled').val() },function(){} ); + $.post(OC.filePath('settings','ajax','setsecurity.php'), { enforceHTTPS: $('#forcessl').val() },function(){} ); }); }); From b60ae11db8b49aa2a06116625de07fa03349a8e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 23 Oct 2013 16:03:57 +0200 Subject: [PATCH 63/82] introducing new dav property {xmlns:oc="http://owncloud.org/ns}id --- apps/files/appinfo/remote.php | 1 + lib/private/connector/sabre/filesplugin.php | 68 +++++++++++++++++++++ lib/private/connector/sabre/node.php | 18 +++++- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 lib/private/connector/sabre/filesplugin.php diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index 75c80cd49f..9f29079620 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -48,6 +48,7 @@ $defaults = new OC_Defaults(); $server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, $defaults->getName())); $server->addPlugin(new Sabre_DAV_Locks_Plugin($lockBackend)); $server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload +$server->addPlugin(new OC_Connector_Sabre_FilesPlugin()); $server->addPlugin(new OC_Connector_Sabre_AbortedUploadDetectionPlugin()); $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin()); $server->addPlugin(new OC_Connector_Sabre_MaintenancePlugin()); diff --git a/lib/private/connector/sabre/filesplugin.php b/lib/private/connector/sabre/filesplugin.php new file mode 100644 index 0000000000..c9d43a038d --- /dev/null +++ b/lib/private/connector/sabre/filesplugin.php @@ -0,0 +1,68 @@ + + * + * @license AGPL3 + */ + +class OC_Connector_Sabre_FilesPlugin extends Sabre_DAV_ServerPlugin +{ + + // namespace + const NS_OWNCLOUD = 'xmlns:oc="http://owncloud.org/ns'; + + /** + * Reference to main server object + * + * @var Sabre_DAV_Server + */ + private $server; + + /** + * This initializes the plugin. + * + * This function is called by Sabre_DAV_Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param Sabre_DAV_Server $server + * @return void + */ + public function initialize(Sabre_DAV_Server $server) { + + $server->xmlNamespaces[self::NS_OWNCLOUD] = 'oc'; + $server->protectedProperties[] = '{' . self::NS_OWNCLOUD . '}id'; + + $this->server = $server; + $this->server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); + } + + /** + * Adds all ownCloud-specific properties + * + * @param string $path + * @param Sabre_DAV_INode $node + * @param array $requestedProperties + * @param array $returnedProperties + * @return void + */ + public function beforeGetProperties($path, Sabre_DAV_INode $node, array &$requestedProperties, array &$returnedProperties) { + + if ($node instanceof OC_Connector_Sabre_Node) { + + $fileid_propertyname = '{' . self::NS_OWNCLOUD . '}id'; + unset($requestedProperties[array_search($fileid_propertyname, $requestedProperties)]); + + /** @var $node OC_Connector_Sabre_Node */ + $returnedProperties[200][$fileid_propertyname] = $node->getFileId(); + + } + + } + +} diff --git a/lib/private/connector/sabre/node.php b/lib/private/connector/sabre/node.php index 3c2ad60f1d..5c0fd2dcad 100644 --- a/lib/private/connector/sabre/node.php +++ b/lib/private/connector/sabre/node.php @@ -45,6 +45,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr * @var string */ protected $path; + /** * node fileinfo cache * @var array @@ -211,6 +212,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr * properties should be returned */ public function getProperties($properties) { + if (is_null($this->property_cache)) { $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; $result = OC_DB::executeAudited( $sql, array( OC_User::getUser(), $this->path ) ); @@ -236,8 +238,11 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr $props = array(); foreach($properties as $property) { - if (isset($this->property_cache[$property])) $props[$property] = $this->property_cache[$property]; + if (isset($this->property_cache[$property])) { + $props[$property] = $this->property_cache[$property]; + } } + return $props; } @@ -260,4 +265,15 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr } return $this->fileView; } + + /** + * @return mixed + */ + public function getFileId() + { + $this->getFileinfoCache(); + $instanceId = OC_Util::getInstanceId(); + $id = sprintf('%08d', $this->fileinfo_cache['fileid']); + return $instanceId . $id; + } } From 96562802524e638e8042502bea3ce69671ea9b16 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 23 Oct 2013 16:20:53 +0200 Subject: [PATCH 64/82] Added left padding to notCreatable message --- core/css/share.css | 1 + 1 file changed, 1 insertion(+) diff --git a/core/css/share.css b/core/css/share.css index e1edc311ee..1ad7bf8d88 100644 --- a/core/css/share.css +++ b/core/css/share.css @@ -104,6 +104,7 @@ a.showCruds:hover,a.unshare:hover { } .notCreatable { + padding-left: 12px; padding-top: 12px; color: #999; } From af58360434cdd6cb808d80d20fdbd293e1c57f0c Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Sat, 5 Oct 2013 18:00:46 +0200 Subject: [PATCH 65/82] files_encryption: Fix getFileSize() For certain file sizes, we rounded to the wrong chunk number and the returned bogus results. This should fix https://github.com/owncloud/mirall/issues/1009 Conflicts: apps/files_encryption/tests/util.php --- apps/files_encryption/lib/util.php | 11 ++++++++-- apps/files_encryption/tests/util.php | 30 +++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index b9592a32cb..0d34af043a 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -508,11 +508,18 @@ class Util { ) { // get the size from filesystem - $fullPath = $this->view->getLocalFile($path); $size = $this->view->filesize($path); + // fast path, else the calculation for $lastChunkNr is bogus + if ($size === 0) { + \OC_FileProxy::$enabled = $proxyStatus; + return 0; + } + // calculate last chunk nr - $lastChunkNr = floor($size / 8192); + // next highest is end of chunks, one subtracted is last one + // we have to read the last chunk, we can't just calculate it (because of padding etc) + $lastChunkNr = ceil($size/ 8192) - 1; $lastChunkSize = $size - ($lastChunkNr * 8192); // open stream diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index eddc4c6b3f..1b93bc36c8 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -241,6 +241,34 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { $this->view->unlink($this->userId . '/files/' . $filename); } + /** +< * @brief Test that data that is read by the crypto stream wrapper + */ + function testGetFileSize() { + \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1); + + $filename = 'tmp-' . time(); + $externalFilename = '/' . $this->userId . '/files/' . $filename; + + // Test for 0 byte files + $problematicFileSizeData = ""; + $cryptedFile = $this->view->file_put_contents($externalFilename, $problematicFileSizeData); + $this->assertTrue(is_int($cryptedFile)); + $this->assertEquals($this->util->getFileSize($externalFilename), 0); + $decrypt = $this->view->file_get_contents($externalFilename); + $this->assertEquals($problematicFileSizeData, $decrypt); + $this->view->unlink($this->userId . '/files/' . $filename); + + // Test a file with 18377 bytes as in https://github.com/owncloud/mirall/issues/1009 + $problematicFileSizeData = str_pad("", 18377, "abc"); + $cryptedFile = $this->view->file_put_contents($externalFilename, $problematicFileSizeData); + $this->assertTrue(is_int($cryptedFile)); + $this->assertEquals($this->util->getFileSize($externalFilename), 18377); + $decrypt = $this->view->file_get_contents($externalFilename); + $this->assertEquals($problematicFileSizeData, $decrypt); + $this->view->unlink($this->userId . '/files/' . $filename); + } + /** * @medium */ @@ -333,7 +361,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { /** * helper function to set migration status to the right value * to be able to test the migration path - * + * * @param $status needed migration status for test * @param $user for which user the status should be set * @return boolean From dd972f65a8271ddcc4d253e87f9dbaeaba95941f Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 18 Oct 2013 12:51:16 +0200 Subject: [PATCH 66/82] fix infinite loop while updating etags, fix for #4365 --- apps/files_sharing/lib/updater.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index 08aaa62e25..8945da41e3 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -32,17 +32,19 @@ class Shared_Updater { $uid = \OCP\User::getUser(); $uidOwner = \OC\Files\Filesystem::getOwner($target); $info = \OC\Files\Filesystem::getFileInfo($target); + $currentOwner = $uidOwner; // Correct Shared folders of other users shared with - $users = \OCP\Share::getUsersItemShared('file', $info['fileid'], $uidOwner, true); + $users = \OCP\Share::getUsersItemShared('file', $info['fileid'], $currentOwner, true); if (!empty($users)) { while (!empty($users)) { $reshareUsers = array(); foreach ($users as $user) { - if ( $user !== $uidOwner ) { + if ( $user !== $currentOwner ) { $etag = \OC\Files\Filesystem::getETag(''); \OCP\Config::setUserValue($user, 'files_sharing', 'etag', $etag); // Look for reshares $reshareUsers = array_merge($reshareUsers, \OCP\Share::getUsersItemShared('file', $info['fileid'], $user, true)); + $currentOwner = $user; } } $users = $reshareUsers; From 6ae761d94607432b4c2a76276f7a84673f312c62 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 18 Oct 2013 17:35:56 +0200 Subject: [PATCH 67/82] keep a list of users we already checked to avoid loops --- apps/files_sharing/lib/updater.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index 8945da41e3..2b4c799cf1 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -32,19 +32,19 @@ class Shared_Updater { $uid = \OCP\User::getUser(); $uidOwner = \OC\Files\Filesystem::getOwner($target); $info = \OC\Files\Filesystem::getFileInfo($target); - $currentOwner = $uidOwner; + $checkedUser = array($uidOwner); // Correct Shared folders of other users shared with $users = \OCP\Share::getUsersItemShared('file', $info['fileid'], $currentOwner, true); if (!empty($users)) { while (!empty($users)) { $reshareUsers = array(); foreach ($users as $user) { - if ( $user !== $currentOwner ) { + if ( in_array($user, $checkedUser) ) { $etag = \OC\Files\Filesystem::getETag(''); \OCP\Config::setUserValue($user, 'files_sharing', 'etag', $etag); // Look for reshares $reshareUsers = array_merge($reshareUsers, \OCP\Share::getUsersItemShared('file', $info['fileid'], $user, true)); - $currentOwner = $user; + $checkedUser[] = $user; } } $users = $reshareUsers; From 60e219da43e9a15ba180c511527b64159a9974ae Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 18 Oct 2013 17:47:01 +0200 Subject: [PATCH 68/82] update etags for user we haven't checked yet --- apps/files_sharing/lib/updater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index 2b4c799cf1..891e87ec40 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -39,7 +39,7 @@ class Shared_Updater { while (!empty($users)) { $reshareUsers = array(); foreach ($users as $user) { - if ( in_array($user, $checkedUser) ) { + if ( !in_array($user, $checkedUser) ) { $etag = \OC\Files\Filesystem::getETag(''); \OCP\Config::setUserValue($user, 'files_sharing', 'etag', $etag); // Look for reshares From 2221aa9ca42bcfdf9b00e46924947238daf75047 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 18 Oct 2013 17:48:35 +0200 Subject: [PATCH 69/82] run first getUsersItemShared() with the owner --- apps/files_sharing/lib/updater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index 891e87ec40..3381f75f16 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -34,7 +34,7 @@ class Shared_Updater { $info = \OC\Files\Filesystem::getFileInfo($target); $checkedUser = array($uidOwner); // Correct Shared folders of other users shared with - $users = \OCP\Share::getUsersItemShared('file', $info['fileid'], $currentOwner, true); + $users = \OCP\Share::getUsersItemShared('file', $info['fileid'], $uidOwner, true); if (!empty($users)) { while (!empty($users)) { $reshareUsers = array(); From b02a4857126337688e8ad40d5f74929d005037ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 23 Oct 2013 16:40:29 +0200 Subject: [PATCH 70/82] handle case where fileid is not set --- lib/private/connector/sabre/filesplugin.php | 5 ++++- lib/private/connector/sabre/node.php | 11 ++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/private/connector/sabre/filesplugin.php b/lib/private/connector/sabre/filesplugin.php index c9d43a038d..46ca14fadc 100644 --- a/lib/private/connector/sabre/filesplugin.php +++ b/lib/private/connector/sabre/filesplugin.php @@ -59,7 +59,10 @@ class OC_Connector_Sabre_FilesPlugin extends Sabre_DAV_ServerPlugin unset($requestedProperties[array_search($fileid_propertyname, $requestedProperties)]); /** @var $node OC_Connector_Sabre_Node */ - $returnedProperties[200][$fileid_propertyname] = $node->getFileId(); + $fileId = $node->getFileId(); + if (!is_null($fileId)) { + $returnedProperties[200][$fileid_propertyname] = $fileId; + } } diff --git a/lib/private/connector/sabre/node.php b/lib/private/connector/sabre/node.php index 5c0fd2dcad..76fbc25110 100644 --- a/lib/private/connector/sabre/node.php +++ b/lib/private/connector/sabre/node.php @@ -272,8 +272,13 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr public function getFileId() { $this->getFileinfoCache(); - $instanceId = OC_Util::getInstanceId(); - $id = sprintf('%08d', $this->fileinfo_cache['fileid']); - return $instanceId . $id; + + if (isset($this->fileinfo_cache['fileid'])) { + $instanceId = OC_Util::getInstanceId(); + $id = sprintf('%08d', $this->fileinfo_cache['fileid']); + return $instanceId . $id; + } + + return null; } } From 0bf034da968d63b90e6fce4796d4a9a1b04eed23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 23 Oct 2013 16:44:56 +0200 Subject: [PATCH 71/82] prefer logo png from theme over svg from core The logo images are specified in the php templates with eg `image_path('', 'logo-wide.svg')`. If that file exists the correct path will be served to the client in the html template. Then the `SVGSupport()` is checked in https://github.com/owncloud/core/blob/master/core/js/js.js#L701 which will replace `svg` with `png` in img tags and css background definitions. fixes #5074 partially solves #5421, but not when an svg has been specified in css and is then 'fixed' by js to point to a png which does not exist in the theme but is then might be resolved to an image from core .... well theoretical problem ... might not even be a problem @karlitschek @jancborchardt please review --- lib/private/urlgenerator.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/private/urlgenerator.php b/lib/private/urlgenerator.php index 5c1d9d825b..1ec10fe568 100644 --- a/lib/private/urlgenerator.php +++ b/lib/private/urlgenerator.php @@ -81,17 +81,35 @@ class URLGenerator implements IURLGenerator { // Read the selected theme from the config file $theme = \OC_Util::getTheme(); + //if a theme has a png but not an svg always use the png + $basename = substr(basename($image),0,-4); + // Check if the app is in the app folder if (file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$image")) { return \OC::$WEBROOT . "/themes/$theme/apps/$app/img/$image"; + } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.png")) { + return \OC::$WEBROOT . "/themes/$theme/apps/$app/img/$basename.png"; } elseif (file_exists(\OC_App::getAppPath($app) . "/img/$image")) { return \OC_App::getAppWebPath($app) . "/img/$image"; + } elseif (!file_exists(\OC_App::getAppPath($app) . "/img/$basename.svg") + && file_exists(\OC_App::getAppPath($app) . "/img/$basename.png")) { + return \OC_App::getAppPath($app) . "/img/$basename.png"; } elseif (!empty($app) and file_exists(\OC::$SERVERROOT . "/themes/$theme/$app/img/$image")) { return \OC::$WEBROOT . "/themes/$theme/$app/img/$image"; + } elseif (!empty($app) and (!file_exists(\OC::$SERVERROOT . "/themes/$theme/$app/img/$basename.svg") + && file_exists(\OC::$WEBROOT . "/themes/$theme/$app/img/$basename.png"))) { + return \OC::$WEBROOT . "/themes/$theme/$app/img/$basename.png"; } elseif (!empty($app) and file_exists(\OC::$SERVERROOT . "/$app/img/$image")) { return \OC::$WEBROOT . "/$app/img/$image"; + } elseif (!empty($app) and (!file_exists(\OC::$SERVERROOT . "/$app/img/$basename.svg") + && file_exists(\OC::$WEBROOT . "/$app/img/$basename.png"))) { + return \OC::$WEBROOT . "/$app/img/$basename.png"; } elseif (file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$image")) { return \OC::$WEBROOT . "/themes/$theme/core/img/$image"; + } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.png")) { + return \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; } elseif (file_exists(\OC::$SERVERROOT . "/core/img/$image")) { return \OC::$WEBROOT . "/core/img/$image"; } else { From 9aa164f3813dca15269e6a081fc9965890acc3d8 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 23 Oct 2013 16:47:25 +0200 Subject: [PATCH 72/82] Renamed current placeholder jquery plugin to imageplaceholder This is to prevent conflicts with jquery libs that handle text placeholders in IE8 --- core/js/jquery.avatar.js | 6 +++--- core/js/placeholder.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/js/jquery.avatar.js b/core/js/jquery.avatar.js index 0006810172..dbab032b97 100644 --- a/core/js/jquery.avatar.js +++ b/core/js/jquery.avatar.js @@ -60,7 +60,7 @@ if (typeof(this.data('user')) !== 'undefined') { user = this.data('user'); } else { - this.placeholder('x'); + this.imageplaceholder('x'); return; } } @@ -76,9 +76,9 @@ if (typeof(result) === 'object') { if (!hidedefault) { if (result.data && result.data.displayname) { - $div.placeholder(user, result.data.displayname); + $div.imageplaceholder(user, result.data.displayname); } else { - $div.placeholder(user); + $div.imageplaceholder(user); } } else { $div.hide(); diff --git a/core/js/placeholder.js b/core/js/placeholder.js index ee2a8ce84c..47cff780d2 100644 --- a/core/js/placeholder.js +++ b/core/js/placeholder.js @@ -30,7 +30,7 @@ * * And call this from Javascript: * - * $('#albumart').placeholder('The Album Title'); + * $('#albumart').imageplaceholder('The Album Title'); * * Which will result in: * @@ -38,7 +38,7 @@ * * You may also call it like this, to have a different background, than the seed: * - * $('#albumart').placeholder('The Album Title', 'Album Title'); + * $('#albumart').imageplaceholder('The Album Title', 'Album Title'); * * Resulting in: * @@ -47,7 +47,7 @@ */ (function ($) { - $.fn.placeholder = function(seed, text) { + $.fn.imageplaceholder = function(seed, text) { // set optional argument "text" to value of "seed" if undefined text = text || seed; From a2e9c0f81a28cf1f38e1c123aec7a4f82fbf65d3 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Wed, 23 Oct 2013 17:57:06 +0300 Subject: [PATCH 73/82] better wording for share input field and share link --- core/js/share.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/js/share.js b/core/js/share.js index 82fa65ead5..d064a399e5 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -200,7 +200,7 @@ OC.Share={ } }); - html += ''; + html += ''; html += '
      '; html += '
    '; var linksAllowed = false; @@ -217,7 +217,7 @@ OC.Share={ }); if (link && linksAllowed) { html += '