From d5c111a984278a097025d267f3461c01983ca0c2 Mon Sep 17 00:00:00 2001
From: Arthur Schiwon <blizzz@owncloud.com>
Date: Thu, 23 Aug 2012 18:29:43 +0200
Subject: [PATCH] LDAP: identify (map) users with their directory UUID. Fixes
 the issue, that usernames for owncloud will change, when the DN changes
 (which happens rarely, but it happens).

---
 apps/user_ldap/appinfo/database.xml | 16 ++++++
 apps/user_ldap/appinfo/update.php   | 42 ++++++++-------
 apps/user_ldap/appinfo/version      |  2 +-
 apps/user_ldap/lib/access.php       | 81 +++++++++++++++++++++++++++--
 apps/user_ldap/lib/connection.php   | 25 +++++++++
 5 files changed, 140 insertions(+), 26 deletions(-)

diff --git a/apps/user_ldap/appinfo/database.xml b/apps/user_ldap/appinfo/database.xml
index 3bbd2b09a4..a785bbf422 100644
--- a/apps/user_ldap/appinfo/database.xml
+++ b/apps/user_ldap/appinfo/database.xml
@@ -28,6 +28,14 @@
 	<default></default>
    </field>
 
+   <field>
+    <name>directory_uuid</name>
+    <type>text</type>
+    <notnull>true</notnull>
+    <length>255</length>
+	<default></default>
+   </field>
+
    <index>
     <name>ldap_dn_users</name>
     <unique>true</unique>
@@ -71,6 +79,14 @@
     <default></default>
    </field>
 
+   <field>
+    <name>directory_uuid</name>
+    <type>text</type>
+    <notnull>true</notnull>
+    <length>255</length>
+	<default></default>
+   </field>
+
    <index>
     <name>ldap_dn_groups</name>
     <unique>true</unique>
diff --git a/apps/user_ldap/appinfo/update.php b/apps/user_ldap/appinfo/update.php
index badceb378d..9cf1814cf6 100644
--- a/apps/user_ldap/appinfo/update.php
+++ b/apps/user_ldap/appinfo/update.php
@@ -2,6 +2,11 @@
 
 //from version 0.1 to 0.2
 
+//ATTENTION
+//Upgrade from ownCloud 3 (LDAP backend 0.1) to ownCloud 4.5 (LDAP backend 0.3) is not supported!!
+//You must do upgrade to ownCloud 4.0 first!
+//The upgrade stuff in the section from 0.1 to 0.2 is just to minimize the bad efffects.
+
 //settings
 $pw = OCP\Config::getAppValue('user_ldap', 'ldap_password');
 if(!is_null($pw)) {
@@ -12,33 +17,25 @@ if(!is_null($pw)) {
 
 //detect if we can switch on naming guidelines. We won't do it on conflicts.
 //it's a bit spaghetti, but hey.
-$state = OCP\Config::getSystemValue('ldapIgnoreNamingRules', 'doCheck');
-if($state == 'doCheck'){
-	$sqlCleanMap = 'DELETE FROM *PREFIX*ldap_user_mapping';
-
-	OCP\Config::setSystemValue('ldapIgnoreNamingRules', true);
-	$LDAP_USER = new OC_USER_LDAP();
-	$users_old = $LDAP_USER->getUsers();
-	$query = OCP\DB::prepare($sqlCleanMap);
-	$query->execute();
+$state = OCP\Config::getSystemValue('ldapIgnoreNamingRules', 'unset');
+if($state == 'unset'){
 	OCP\Config::setSystemValue('ldapIgnoreNamingRules', false);
-	OC_LDAP::init(true);
-	$users_new = $LDAP_USER->getUsers();
-	$query = OCP\DB::prepare($sqlCleanMap);
-	$query->execute();
-	if($users_old !== $users_new) {
-		//we don't need to check Groups, because they were not supported in 3'
-		OCP\Config::setSystemValue('ldapIgnoreNamingRules', true);
-	}
 }
 
+// ### SUPPORTED upgrade path starts here ###
 
-//from version 0.2 to 0.2.1
+//from version 0.2 to 0.3 (0.2.0.x dev version)
 $objects = array('user', 'group');
 
+$connector = new \OCA\user_ldap\lib\Connection('user_ldap');
+$userBE = new \OCA\user_ldap\USER_LDAP();
+$userBE->setConnector($connector);
+$groupBE = new \OCA\user_ldap\GROUP_LDAP();
+$groupBE->setConnector($connector);
+
 foreach($objects as $object) {
 	$fetchDNSql = 'SELECT ldap_dn from *PREFIX*ldap_'.$object.'_mapping';
-	$updateSql = 'UPDATE *PREFIX*ldap_'.$object.'_mapping SET ldap_DN = ? WHERE ldap_dn = ?';
+	$updateSql = 'UPDATE *PREFIX*ldap_'.$object.'_mapping SET ldap_DN = ?, directory_uuid = ? WHERE ldap_dn = ?';
 
 	$query = OCP\DB::prepare($fetchDNSql);
 	$res = $query->execute();
@@ -46,6 +43,11 @@ foreach($objects as $object) {
 	$updateQuery = OCP\DB::prepare($updateSql);
 	foreach($DNs as $dn) {
 		$newDN = mb_strtolower($dn['ldap_dn'], 'UTF-8');
-		$updateQuery->execute(array($newDN, $dn['ldap_dn']));
+		if($object == 'user') {
+			$uuid = $userBE->getUUID($newDN);
+		} else {
+			$uuid = $groupBE->getUUID($newDN);
+		}
+		$updateQuery->execute(array($newDN, $uuid, $dn['ldap_dn']));
 	}
 }
diff --git a/apps/user_ldap/appinfo/version b/apps/user_ldap/appinfo/version
index 1683a26615..e689e4949e 100644
--- a/apps/user_ldap/appinfo/version
+++ b/apps/user_ldap/appinfo/version
@@ -1 +1 @@
-0.2.0.8
\ No newline at end of file
+0.2.0.26
\ No newline at end of file
diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php
index 4e7215525d..be9aa21c3d 100644
--- a/apps/user_ldap/lib/access.php
+++ b/apps/user_ldap/lib/access.php
@@ -50,11 +50,12 @@ abstract class Access {
 		$cr = $this->connection->getConnectionResource();
 		if(!is_resource($cr)) {
 			//LDAP not available
+			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
 			return false;
 		}
 		$rr = @ldap_read($cr, $dn, 'objectClass=*', array($attr));
 		if(!is_resource($rr)) {
-			\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, \OCP\Util::DEBUG);
+			\OCP\Util::writeLog('user_ldap', 'readAttribute '.$attr.' failed for DN '.$dn, \OCP\Util::DEBUG);
 			//in case an error occurs , e.g. object does not exist
 			return false;
 		}
@@ -70,6 +71,7 @@ abstract class Access {
 			}
 			return $values;
 		}
+		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
 		return false;
 	}
 
@@ -226,11 +228,32 @@ abstract class Access {
 			WHERE ldap_dn = ?
 		');
 
+		//let's try to retrieve the ownCloud name from the mappings table
 		$component = $query->execute(array($dn))->fetchOne();
 		if($component) {
 			return $component;
 		}
 
+		//second try: get the UUID and check if it is known. Then, update the DN and return the name.
+		$uuid = $this->getUUID($dn);
+		if($uuid) {
+			$query = \OCP\DB::prepare('
+				SELECT owncloud_name
+				FROM '.$table.'
+				WHERE directory_uuid = ?
+			');
+			$component = $query->execute(array($uuid))->fetchOne();
+			if($component) {
+				$query = \OCP\DB::prepare('
+					UPDATE '.$table.'
+					SET ldap_dn = ?
+					WHERE directory_uuid = ?
+				');
+				$query->execute(array($dn, $uuid));
+				return $component;
+			}
+		}
+
 		if(is_null($ldapname)) {
 			$ldapname = $this->readAttribute($dn, $nameAttribute);
 			if(!isset($ldapname[0]) && empty($ldapname[0])) {
@@ -389,17 +412,18 @@ abstract class Access {
 		}
 
 		$insert = \OCP\DB::prepare('
-			INSERT INTO '.$table.' (ldap_dn, owncloud_name)
-				SELECT ?,?
+			INSERT INTO '.$table.' (ldap_dn, owncloud_name, directory_uuid)
+				SELECT ?,?,?
 				'.$sqlAdjustment.'
 				WHERE NOT EXISTS (
 					SELECT 1
 					FROM '.$table.'
 					WHERE ldap_dn = ?
-						OR owncloud_name = ? )
+						OR owncloud_name = ?)
 		');
 
-		$res = $insert->execute(array($dn, $ocname, $dn, $ocname));
+		//feed the DB
+		$res = $insert->execute(array($dn, $ocname, $this->getUUID($dn), $dn, $ocname));
 
 		if(\OCP\DB::isError($res)) {
 			return false;
@@ -602,4 +626,51 @@ abstract class Access {
 		}
 		return $testConnection->bind();
 	}
+
+	/**
+	 * @brief auto-detects the directory's UUID attribute
+	 * @param $dn a known DN used to check against
+	 * @param $force the detection should be run, even if it is not set to auto
+	 * @returns true on success, false otherwise
+	 */
+	private function detectUuidAttribute($dn, $force = false) {
+		if(($this->connection->ldapUuidAttribute != 'auto') && !$force) {
+			return true;
+		}
+
+		//for now, supported (known) attributes are entryUUID, nsuniqueid, objectGUID
+		$testAttributes = array('entryuuid', 'nsuniqueid', 'objectguid');
+
+		foreach($testAttributes as $attribute) {
+			\OCP\Util::writeLog('user_ldap', 'Testing '.$attribute.' as UUID attr', \OCP\Util::DEBUG);
+
+		    $value = $this->readAttribute($dn, $attribute);
+		    if(is_array($value) && isset($value[0]) && !empty($value[0])) {
+				\OCP\Util::writeLog('user_ldap', 'Setting '.$attribute.' as UUID attr', \OCP\Util::DEBUG);
+				$this->connection->ldapUuidAttribute = $attribute;
+				return true;
+		    }
+		    \OCP\Util::writeLog('user_ldap', 'The looked for uuid attr is not '.$attribute.', result was '.print_r($value,true), \OCP\Util::DEBUG);
+		}
+
+		return false;
+	}
+
+	public function getUUID($dn) {
+		if($this->detectUuidAttribute($dn)) {
+			$uuid = $this->readAttribute($dn, $this->connection->ldapUuidAttribute);
+			if(!is_array($uuid) && $this->connection->ldapOverrideUuidAttribute) {
+				$this->detectUuidAttribute($dn, true);
+				$uuid = $this->readAttribute($dn, $this->connection->ldapUuidAttribute);
+			}
+			if(is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
+				$uuid = $uuid[0];
+			} else {
+				$uuid = false;
+			}
+		} else {
+			$uuid = false;
+		}
+		return $uuid;
+	}
 }
\ No newline at end of file
diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php
index 9bb012e910..dc160a1642 100644
--- a/apps/user_ldap/lib/connection.php
+++ b/apps/user_ldap/lib/connection.php
@@ -53,6 +53,8 @@ class Connection {
 		'ldapQuotaDefault' => null,
 		'ldapEmailAttribute' => null,
 		'ldapCacheTTL' => null,
+		'ldapUuidAttribute' => null,
+		'ldapOverrideUuidAttribute' => null,
 	);
 
 	public function __construct($configID = 'user_ldap') {
@@ -74,6 +76,22 @@ class Connection {
 		}
 	}
 
+	public function __set($name, $value) {
+		$changed = false;
+		//omly few options are writable
+		if($name == 'ldapUuidAttribute') {
+			\OCP\Util::writeLog('user_ldap', 'Set config ldapUuidAttribute to  '.$value, \OCP\Util::DEBUG);
+			$this->config[$name] = $value;
+			if(!empty($this->configID)) {
+				\OCP\Config::getAppValue($this->configID, 'ldap_uuid_attribute', $value);
+			}
+			$changed = true;
+		}
+		if($changed) {
+			$this->validateConfiguration();
+		}
+	}
+
 	/**
 	 * @brief initializes the LDAP backend
 	 * @param $force read the config settings no matter what
@@ -180,6 +198,8 @@ class Connection {
 			$this->config['ldapGroupMemberAssocAttr'] = \OCP\Config::getAppValue($this->configID, 'ldap_group_member_assoc_attribute', 'uniqueMember');
 			$this->config['ldapIgnoreNamingRules'] = \OCP\Config::getSystemValue('ldapIgnoreNamingRules', false);
 			$this->config['ldapCacheTTL']          = \OCP\Config::getAppValue($this->configID, 'ldap_cache_ttl', 10*60);
+			$this->config['ldapUuidAttribute']     = \OCP\Config::getAppValue($this->configID, 'ldap_uuid_attribute', 'auto');
+			$this->config['ldapOverrideUuidAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_override_uuid_attribute', 0);
 
 			$this->configured = $this->validateConfiguration();
 		}
@@ -236,6 +256,11 @@ class Connection {
 		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);
 		}
+		if(!in_array($this->config['ldapUuidAttribute'], array('auto','entryuuid', 'nsuniqueid', 'objectguid'))) {
+			\OCP\Config::setAppValue($this->configID, 'ldap_uuid_attribute', 'auto');
+			\OCP\Util::writeLog('user_ldap', 'Illegal value for the UUID Attribute, reset to autodetect.', \OCP\Util::INFO);
+		}
+
 
 		//second step: critical checks. If left empty or filled wrong, set as unconfigured and give a warning.
 		$configurationOK = true;
-- 
GitLab