diff --git a/apps/user_ldap/appinfo/database.xml b/apps/user_ldap/appinfo/database.xml new file mode 100644 index 0000000000..74c56fcf74 --- /dev/null +++ b/apps/user_ldap/appinfo/database.xml @@ -0,0 +1,95 @@ + + + + *dbname* + true + false + utf8 + + + + *dbprefix*ldap_user_mapping + + + + + ldap_dn + text + true + 255 + + + + + owncloud_name + text + true + 255 + + + + + ldap_dn + true + + ldap_dn + + + + + owncloud_name + true + + owncloud_name + ascending + + + + + +
+ + + + *dbprefix*ldap_group_mapping + + + + + ldap_dn + text + true + 255 + + + + + owncloud_name + text + true + 255 + + + + + ldap_dn + true + + ldap_dn + + + + + owncloud_name + true + + owncloud_name + ascending + + + + + +
+ +
\ No newline at end of file diff --git a/apps/user_ldap/appinfo/version b/apps/user_ldap/appinfo/version index ceab6e11ec..a0d78bd347 100644 --- a/apps/user_ldap/appinfo/version +++ b/apps/user_ldap/appinfo/version @@ -1 +1 @@ -0.1 \ No newline at end of file +0.1.90 \ No newline at end of file diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php index df82162a67..7773968e20 100644 --- a/apps/user_ldap/group_ldap.php +++ b/apps/user_ldap/group_ldap.php @@ -24,11 +24,9 @@ class OC_GROUP_LDAP extends OC_Group_Backend { // //group specific settings protected $ldapGroupFilter; - protected $ldapGroupDisplayName; public function __construct() { $this->ldapGroupFilter = OC_Appconfig::getValue('user_ldap', 'ldap_group_filter', '(objectClass=posixGroup)'); - $this->ldapGroupDisplayName = OC_Appconfig::getValue('user_ldap', 'ldap_group_display_name', 'cn'); } /** @@ -40,18 +38,17 @@ class OC_GROUP_LDAP extends OC_Group_Backend { * Checks whether the user is member of a group or not. */ public function inGroup($uid, $gid) { - $filter = OC_LDAP::combineFilterWithAnd(array( - $this->ldapGroupFilter, - LDAP_GROUP_MEMBER_ASSOC_ATTR.'='.$uid, - $this->ldapGroupDisplayName.'='.$gid - )); - $groups = $this->retrieveList($filter, $this->ldapGroupDisplayName); - - if(count($groups) > 0) { - return true; - } else { + $dn_user = OC_LDAP::username2dn($uid); + $dn_group = OC_LDAP::groupname2dn($gid); +// if($dn_group == 'c') {echo('#sdfsdgfds');die($gid);} + // just in case + if(!$dn_group || !$dn_user) { return false; } +// var_dump($dn_group); + $members = OC_LDAP::readAttribute($dn_group, LDAP_GROUP_MEMBER_ASSOC_ATTR); + + return in_array($dn_user, $members); } /** @@ -63,12 +60,19 @@ class OC_GROUP_LDAP extends OC_Group_Backend { * if the user exists at all. */ public function getUserGroups($uid) { + $userDN = OC_LDAP::username2dn($uid); + if(!$userDN) { + return array(); + } + $filter = OC_LDAP::combineFilterWithAnd(array( $this->ldapGroupFilter, - LDAP_GROUP_MEMBER_ASSOC_ATTR.'='.$uid + LDAP_GROUP_MEMBER_ASSOC_ATTR.'='.$userDN )); + $groups = $this->retrieveList($filter, array(OC_LDAP::conf('ldapGroupDisplayName'),'dn')); + $userGroups = OC_LDAP::ownCloudGroupNames($groups); - return $this->retrieveList($filter, $this->ldapGroupDisplayName); + return array_unique($userGroups, SORT_LOCALE_STRING); } /** @@ -76,22 +80,16 @@ class OC_GROUP_LDAP extends OC_Group_Backend { * @returns array with user ids */ public function usersInGroup($gid) { - $filter = OC_LDAP::combineFilterWithAnd(array( - $this->ldapGroupFilter, - $this->ldapGroupDisplayName.'='.$gid - )); - - $userDNs = $this->retrieveList($filter, LDAP_GROUP_MEMBER_ASSOC_ATTR, false); - $users = array(); - $attr = OC_LDAP::conf('ldapUserDisplayName'); - foreach($userDNs as $dn) { - $uid = OC_LDAP::readAttribute($dn, $attr); - if($uid) { -// if(($uid = OC_LDAP::readAttribute($dn, $attr)) != false){ - $users[] = $uid; - } + $groupDN = OC_LDAP::groupname2dn($gid); + if(!$groupDN) { + return array(); } - return $users; + $members = OC_LDAP::readAttribute($groupDN, LDAP_GROUP_MEMBER_ASSOC_ATTR); + $result = array(); + foreach($members as $member) { + $result[] = OC_LDAP::dn2username($member); + } + return array_unique($result, SORT_LOCALE_STRING); } /** @@ -101,7 +99,9 @@ class OC_GROUP_LDAP extends OC_Group_Backend { * Returns a list with all groups */ public function getGroups() { - return $this->retrieveList($this->ldapGroupFilter, $this->ldapGroupDisplayName); + $ldap_groups = $this->retrieveList($this->ldapGroupFilter, array(OC_LDAP::conf('ldapGroupDisplayName'), 'dn')); + $groups = OC_LDAP::ownCloudGroupNames($ldap_groups); + return $groups; } /** @@ -120,13 +120,18 @@ class OC_GROUP_LDAP extends OC_Group_Backend { $list = OC_LDAP::searchUsers($filter, $attr); } - if(is_array($list)) { - return array_unique($list, SORT_LOCALE_STRING); + if(count($attr) > 1){ + return $list; + } else { + return array_unique($list, SORT_LOCALE_STRING); + } } //error cause actually, maybe throw an exception in future. return array(); } + + } \ No newline at end of file diff --git a/apps/user_ldap/lib_ldap.php b/apps/user_ldap/lib_ldap.php index bd5f290779..4efcf0c5a0 100644 --- a/apps/user_ldap/lib_ldap.php +++ b/apps/user_ldap/lib_ldap.php @@ -21,7 +21,8 @@ * */ -define('LDAP_GROUP_MEMBER_ASSOC_ATTR','uniquemember'); +define('LDAP_GROUP_MEMBER_ASSOC_ATTR','uniqueMember'); +define('LDAP_GROUP_DISPLAY_NAME_ATTR','cn'); //needed to unbind, because we use OC_LDAP only statically class OC_LDAP_DESTRUCTOR { @@ -45,7 +46,9 @@ class OC_LDAP { static protected $ldapTLS; static protected $ldapNoCase; // user and group settings, that are needed in both backends - static public $ldapUserDisplayName; + static protected $ldapUserDisplayName; + static protected $ldapUserFilter; + static protected $ldapGroupDisplayName; static public function init() { self::readConfiguration(); @@ -56,33 +59,343 @@ class OC_LDAP { @ldap_unbind(self::$ldapConnectionRes); } + /** + * @brief returns a read-only configuration value + * @param $key the name of the configuration value + * @returns the value on success, otherwise null + * + * returns a read-only configuration values + * + * we cannot work with getters, because it is a static class + */ static public function conf($key) { + if(!self::$configured) { + self::init(); + } + $availableProperties = array( 'ldapUserDisplayName', + 'ldapGroupDisplayName', ); if(in_array($key, $availableProperties)) { return self::$$key; } + + return null; + } + + /** + * gives back the database table for the query + */ + static private function getMapTable($isUser) { + if($isUser) { + return '*PREFIX*ldap_user_mapping'; + } else { + return '*PREFIX*ldap_group_mapping'; + } + } + + /** + * @brief returns the LDAP DN for the given internal ownCloud name of the group + * @param $name the ownCloud name in question + * @returns string with the LDAP DN on success, otherwise false + * + * returns the LDAP DN for the given internal ownCloud name of the group + */ + static public function groupname2dn($name) { + return self::ocname2dn($name, false); + } + + /** + * @brief returns the LDAP DN for the given internal ownCloud name of the user + * @param $name the ownCloud name in question + * @returns string with the LDAP DN on success, otherwise false + * + * returns the LDAP DN for the given internal ownCloud name of the user + */ + static public function username2dn($name) { + $dn = self::ocname2dn($name, true); + if($dn) { + return $dn; + } else { + //fallback: user is not mapped + $filter = self::combineFilterWithAnd(array( + self::$ldapUserFilter, + self::$ldapUserDisplayName . '=' . $name, + )); + $result = self::searchUsers($filter, 'dn'); + if(isset($result[0]['dn'])) { + self::mapUser($result[0], $name); + return $result[0]; + } + } + + return false; + } + + static private function ocname2dn($name, $isUser) { + $table = self::getMapTable($isUser); + + $query = OC_DB::prepare(' + SELECT ldap_dn + FROM '.$table.' + WHERE owncloud_name = ? + '); + + $record = $query->execute(array($name))->fetchOne(); + return $record; + if($name=='Coyotes') { + echo("adsfasdf "); + var_dump($record); + die(); + } + if(isset($record['ldap_dn'])) { + return $record['ldap_dn']; + } + + return false; + } + + /** + * @brief returns the internal ownCloud name for the given LDAP DN of the group + * @param $dn the dn of the group object + * @param $ldapname optional, the display name of the object + * @returns string with with the name to use in ownCloud + * + * returns the internal ownCloud name for the given LDAP DN of the group + */ + static public function dn2groupname($dn, $ldapname = null) { + return self::dn2ocname($dn, $ldapname, false); + } + + /** + * @brief returns the internal ownCloud name for the given LDAP DN of the user + * @param $dn the dn of the user object + * @param $ldapname optional, the display name of the object + * @returns string with with the name to use in ownCloud + * + * returns the internal ownCloud name for the given LDAP DN of the user + */ + static public function dn2username($dn, $ldapname = null) { + return self::dn2ocname($dn, $ldapname, true); + } + + static public function dn2ocname($dn, $ldapname = null, $isUser = true) { + $table = self::getMapTable($isUser); + if($isUser) { + $nameAttribute = self::conf('ldapUserDisplayName'); + } else { + $nameAttribute = self::conf('ldapGroupDisplayName'); + } + + $query = OC_DB::prepare(' + SELECT owncloud_name + FROM '.$table.' + WHERE ldap_dn = ? + '); + + $component = $query->execute(array($dn))->fetchOne(); + if($component) { + return $component; + } + + if(is_null($ldapname)) { + $ldapname = self::readAttribute($dn, $nameAttribute); + $ldapname = $ldapname[0]; + } + + //a new user/group! Then let's try to add it. We're shooting into the blue with the user/group name, assuming that in most cases there will not be a conflict. Otherwise an error will occur and we will continue with our second shot. + if(self::mapComponent($dn, $ldapname, $isUser)) { + return $ldapname; + } + + //doh! There is a conflict. We need to distinguish between users/groups. Adding indexes is an idea, but not much of a help for the user. The DN is ugly, but for now the only reasonable way. But we transform it to a readable format and remove the first part to only give the path where this object is located. + $oc_name = self::alternateOwnCloudName($ldapname, $dn); + if(self::mapComponent($dn, $oc_name, $isUser)) { + return $oc_name; + } + + //and this of course should never been thrown :) + throw new Exception('LDAP backend: unexpected collision of DN and ownCloud Name.'); + } + + /** + * @brief gives back the user names as they are used ownClod internally + * @param $ldapGroups an array with the ldap Users result in style of array ( array ('dn' => foo, 'uid' => bar), ... ) + * @returns an array with the user names to use in ownCloud + * + * gives back the user names as they are used ownClod internally + */ + static public function ownCloudUserNames($ldapUsers) { + return self::ldap2ownCloudNames($ldapUsers, true); + } + + /** + * @brief gives back the group names as they are used ownClod internally + * @param $ldapGroups an array with the ldap Groups result in style of array ( array ('dn' => foo, 'cn' => bar), ... ) + * @returns an array with the group names to use in ownCloud + * + * gives back the group names as they are used ownClod internally + */ + static public function ownCloudGroupNames($ldapGroups) { + return self::ldap2ownCloudNames($ldapGroups, false); + } + + static private function ldap2ownCloudNames($ldapObjects, $isUsers) { + if($isUsers) { + $knownObjects = self::mappedUsers(); + $nameAttribute = self::conf('ldapUserDisplayName'); + } else { + $knownObjects = self::mappedGroups(); + $nameAttribute = self::conf('ldapGroupDisplayName'); + } + $ownCloudNames = array(); + + foreach($ldapObjects as $ldapObject) { + $key = self::recursiveArraySearch($knownObjects, $ldapObject['dn']); + + //everything is fine when we know the group + if($key) { + $ownCloudNames[] = $knownObjects[$key]['owncloud_name']; + continue; + } + + //a new group! Then let's try to add it. We're shooting into the blue with the group name, assuming that in most cases there will not be a conflict + if(self::mapComponent($ldapObject['dn'], $ldapObject[$nameAttribute], $isUsers)) { + $ownCloudNames[] = $ldapObject[$nameAttribute]; + continue; + } + + //doh! There is a conflict. We need to distinguish between groups. Adding indexes is an idea, but not much of a help for the user. The DN is ugly, but for now the only reasonable way. But we transform it to a readable format and remove the first part to only give the path where this entry is located. + $oc_name = self::alternateOwnCloudName($ldapObject[$nameAttribute], $ldapObject['dn']); + if(self::mapComponent($ldapObject['dn'], $oc_name, $isUsers)) { + $ownCloudNames[] = $oc_name; + continue; + } + + //and this of course should never been thrown :) + throw new Exception('LDAP backend: unexpected collision of DN and ownCloud Name.'); + } + return $ownCloudNames; + } + + /** + * @brief creates a hopefully unique name for owncloud based on the display name and the dn of the LDAP object + * @param $name the display name of the object + * @param $dn the dn of the object + * @returns string with with the name to use in ownCloud + * + * creates a hopefully unique name for owncloud based on the display name and the dn of the LDAP object + */ + static private function alternateOwnCloudName($name, $dn) { + $ufn = ldap_dn2ufn($dn); + return $name . ' (' . trim(substr_replace($ufn, '', 0, strpos($ufn, ','))) . ')'; + } + + /** + * @brief retrieves all known groups from the mappings table + * @returns array with the results + * + * retrieves all known groups from the mappings table + */ + static private function mappedGroups() { + return self::mappedComponents(false); + } + + /** + * @brief retrieves all known users from the mappings table + * @returns array with the results + * + * retrieves all known users from the mappings table + */ + static private function mappedUsers() { + return self::mappedComponents(true); + } + + static private function mappedComponents($isUsers) { + $table = self::getMapTable($isUsers); + + $query = OC_DB::prepare(' + SELECT ldap_dn, owncloud_name + FROM '. $table + ); + + return $query->execute()->fetchAll(); + } + + /** + * @brief inserts a new group into the mappings table + * @param $dn the record in question + * @param $ocname the name to use in ownCloud + * @returns true on success, false otherwise + * + * inserts a new group into the mappings table + */ + static private function mapGroup($dn, $ocname) { + return self::mapComponent($dn, $ocname, false); + } + + /** + * @brief inserts a new user into the mappings table + * @param $dn the record in question + * @param $ocname the name to use in ownCloud + * @returns true on success, false otherwise + * + * inserts a new user into the mappings table + */ + static private function mapUser($dn, $ocname) { + return self::mapComponent($dn, $ocname, true); + } + + /** + * @brief inserts a new user or group into the mappings table + * @param $dn the record in question + * @param $ocname the name to use in ownCloud + * @param $isUser is it a user or a group? + * @returns true on success, false otherwise + * + * inserts a new user or group into the mappings table + */ + static private function mapComponent($dn, $ocname, $isUser = true) { + $table = self::getMapTable($isUser); + + $insert = OC_DB::prepare(' + INSERT IGNORE INTO '.$table.' + (ldap_dn, owncloud_name) + VALUES (?,?) + '); + + $res = $insert->execute(array($dn, $ocname)); + + return !OC_DB::isError($res); } /** * @brief reads a given attribute for an LDAP record identified by a DN * @param $dn the record in question * @param $attr the attribute that shall be retrieved - * @returns the value on success, false otherwise + * @returns the values in an array on success, false otherwise * * Reads an attribute from an LDAP entry */ static public function readAttribute($dn, $attr) { - $attr = strtolower($attr); $cr = self::getConnectionResource(); - +// echo("
");var_dump($dn);
 		$rr = ldap_read($cr, $dn, 'objectClass=*', array($attr));
+		if(!$rr) {
+			echo('
###RA ');var_dump($dn);var_dump(debug_backtrace());die();
+		}
 		$er = ldap_first_entry($cr, $rr);
 		$result = ldap_get_attributes($cr, $er);
-		if($result['count'] > 0){
-			return $result[$attr][0];
+
+// 		if($dn == 'cn=Coyotes,cn=groups,dc=blizzz-oc,dc=bzoc') die((var_dump($result)));
+		if($result[$attr]['count'] > 0){
+			$values = array();
+			for($i=0;$i<$result[$attr]['count'];$i++) {
+				$values[] = $result[$attr][$i];
+			}
+			return $values;
 		}
 		return false;
 	}
@@ -121,15 +434,38 @@ class OC_LDAP {
 	 * Executes an LDAP search
 	 */
 	static private function search($filter, $base, $attr = null) {
-		$sr = ldap_search(self::getConnectionResource(), $base, $filter, array($attr));
+		if(!is_null($attr) && !is_array($attr)) {
+			$attr = array(strtolower($attr));
+		}
+		$sr = ldap_search(self::getConnectionResource(), $base, $filter, $attr);
 		$findings = ldap_get_entries(self::getConnectionResource(), $sr );
 
 		if(!is_null($attr)) {
 			$selection = array();
+			$multiarray = false;
+			if(count($attr) > 1) {
+				$multiarray = true;
+				$i = 0;
+			}
 			foreach($findings as $item) {
-				if(isset($item[strtolower($attr)])) {
-					$selection[] = $item[strtolower($attr)][0];
+				if($multiarray) {
+					foreach($attr as $key) {
+						if(isset($item[$key])) {
+							if($key != 'dn'){
+								$selection[$i][$key] = $item[$key][0];
+							} else {
+								$selection[$i][$key] = $item[$key];
+							}
+						}
+
+					}
+					$i++;
+				} else {
+					if(isset($item[$attr[0]])) {
+						$selection[] = $item[$attr[0]];
+					}
 				}
+
 			}
 			return $selection;
 		}
@@ -194,16 +530,18 @@ class OC_LDAP {
 	 */
 	static private function readConfiguration() {
 		if(!self::$configured) {
-			self::$ldapHost            = OC_Appconfig::getValue('user_ldap', 'ldap_host', '');
-			self::$ldapPort            = OC_Appconfig::getValue('user_ldap', 'ldap_port', OC_USER_BACKEND_LDAP_DEFAULT_PORT);
-			self::$ldapAgentName       = OC_Appconfig::getValue('user_ldap', 'ldap_dn','');
-			self::$ldapAgentPassword   = OC_Appconfig::getValue('user_ldap', 'ldap_password','');
-			self::$ldapBase            = OC_Appconfig::getValue('user_ldap', 'ldap_base', '');
-			self::$ldapBaseUsers       = OC_Appconfig::getValue('user_ldap', 'ldap_base_users',self::$ldapBase);
-			self::$ldapBaseGroups      = OC_Appconfig::getValue('user_ldap', 'ldap_base_groups', self::$ldapBase);
-			self::$ldapTLS             = OC_Appconfig::getValue('user_ldap', 'ldap_tls',0);
-			self::$ldapNoCase          = OC_Appconfig::getValue('user_ldap', 'ldap_nocase', 0);
-			self::$ldapUserDisplayName = OC_Appconfig::getValue('user_ldap', 'ldap_display_name', OC_USER_BACKEND_LDAP_DEFAULT_DISPLAY_NAME);
+			self::$ldapHost             = OC_Appconfig::getValue('user_ldap', 'ldap_host', '');
+			self::$ldapPort             = OC_Appconfig::getValue('user_ldap', 'ldap_port', OC_USER_BACKEND_LDAP_DEFAULT_PORT);
+			self::$ldapAgentName        = OC_Appconfig::getValue('user_ldap', 'ldap_dn','');
+			self::$ldapAgentPassword    = OC_Appconfig::getValue('user_ldap', 'ldap_password','');
+			self::$ldapBase             = OC_Appconfig::getValue('user_ldap', 'ldap_base', '');
+			self::$ldapBaseUsers        = OC_Appconfig::getValue('user_ldap', 'ldap_base_users',self::$ldapBase);
+			self::$ldapBaseGroups       = OC_Appconfig::getValue('user_ldap', 'ldap_base_groups', self::$ldapBase);
+			self::$ldapTLS              = OC_Appconfig::getValue('user_ldap', 'ldap_tls',0);
+			self::$ldapNoCase           = OC_Appconfig::getValue('user_ldap', 'ldap_nocase', 0);
+			self::$ldapUserDisplayName  = OC_Appconfig::getValue('user_ldap', 'ldap_display_name', OC_USER_BACKEND_LDAP_DEFAULT_DISPLAY_NAME);
+			self::$ldapUserFilter       = OC_Appconfig::getValue('user_ldap', 'ldap_userlist_filter','objectClass=person');
+			self::$ldapGroupDisplayName = OC_Appconfig::getValue('user_ldap', 'ldap_group_display_name', LDAP_GROUP_DISPLAY_NAME_ATTR);
 
 			if(
 				   !empty(self::$ldapHost)
@@ -247,5 +585,23 @@ class OC_LDAP {
 		}
 	}
 
+	/**
+	* taken from http://www.php.net/manual/en/function.array-search.php#97645
+	* TODO: move somewhere, where its better placed since it is not LDAP specific. OC_Helper maybe?
+	*/
+	static public function recursiveArraySearch($haystack, $needle, $index = null) {
+		$aIt = new RecursiveArrayIterator($haystack);
+		$it = new RecursiveIteratorIterator($aIt);
+
+		while($it->valid()) {
+			if (((isset($index) AND ($it->key() == $index)) OR (!isset($index))) AND ($it->current() == $needle)) {
+				return $aIt->key();
+			}
+
+			$it->next();
+		}
+
+		return false;
+	}
 
  }
\ No newline at end of file