Browse Source

Merge pull request #2504 from nextcloud/downstream-ldap-15

Downstream LDAP Range Support
Roeland Jago Douma 8 years ago
parent
commit
f86f4eb98d
1 changed files with 122 additions and 22 deletions
  1. 122 22
      apps/user_ldap/lib/Access.php

+ 122 - 22
apps/user_ldap/lib/Access.php

@@ -182,46 +182,146 @@ class Access extends LDAPUtility implements IUserTools {
 		// 0 won't result in replies, small numbers may leave out groups
 		// (cf. #12306), 500 is default for paging and should work everywhere.
 		$maxResults = $pagingSize > 20 ? $pagingSize : 500;
-		$this->initPagedSearch($filter, array($dn), array($attr), $maxResults, 0);
+		$attr = mb_strtolower($attr, 'UTF-8');
+		// the actual read attribute later may contain parameters on a ranged
+		// request, e.g. member;range=99-199. Depends on server reply.
+		$attrToRead = $attr;
+
+		$values = [];
+		$isRangeRequest = false;
+		do {
+			$result = $this->executeRead($cr, $dn, $attrToRead, $filter, $maxResults);
+			if(is_bool($result)) {
+				// when an exists request was run and it was successful, an empty
+				// array must be returned
+				return $result ? [] : false;
+			}
+
+			if (!$isRangeRequest) {
+				$values = $this->extractAttributeValuesFromResult($result, $attr);
+				if (!empty($values)) {
+					return $values;
+				}
+			}
+
+			$isRangeRequest = false;
+			$result = $this->extractRangeData($result, $attr);
+			if (!empty($result)) {
+				$normalizedResult = $this->extractAttributeValuesFromResult(
+					[ $attr => $result['values'] ],
+					$attr
+				);
+				$values = array_merge($values, $normalizedResult);
+
+				if($result['rangeHigh'] === '*') {
+					// when server replies with * as high range value, there are
+					// no more results left
+					return $values;
+				} else {
+					$low  = $result['rangeHigh'] + 1;
+					$attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
+					$isRangeRequest = true;
+				}
+			}
+		} while($isRangeRequest);
+
+		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
+		return false;
+	}
+
+	/**
+	 * Runs an read operation against LDAP
+	 *
+	 * @param resource $cr the LDAP connection
+	 * @param string $dn
+	 * @param string $attribute
+	 * @param string $filter
+	 * @param int $maxResults
+	 * @return array|bool false if there was any error, true if an exists check
+	 *                    was performed and the requested DN found, array with the
+	 *                    returned data on a successful usual operation
+	 */
+	public function executeRead($cr, $dn, $attribute, $filter, $maxResults) {
+		$this->initPagedSearch($filter, array($dn), array($attribute), $maxResults, 0);
 		$dn = $this->helper->DNasBaseParameter($dn);
-		$rr = @$this->ldap->read($cr, $dn, $filter, array($attr));
-		if(!$this->ldap->isResource($rr)) {
-			if ($attr !== '') {
+		$rr = @$this->ldap->read($cr, $dn, $filter, array($attribute));
+		if (!$this->ldap->isResource($rr)) {
+			if ($attribute !== '') {
 				//do not throw this message on userExists check, irritates
-				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, \OCP\Util::DEBUG);
+				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, \OCP\Util::DEBUG);
 			}
 			//in case an error occurs , e.g. object does not exist
 			return false;
 		}
-		if ($attr === '' && ($filter === 'objectclass=*' || $this->ldap->countEntries($cr, $rr) === 1)) {
-			\OCP\Util::writeLog('user_ldap', 'readAttribute: '.$dn.' found', \OCP\Util::DEBUG);
-			return array();
+		if ($attribute === '' && ($filter === 'objectclass=*' || $this->ldap->countEntries($cr, $rr) === 1)) {
+			\OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', \OCP\Util::DEBUG);
+			return true;
 		}
 		$er = $this->ldap->firstEntry($cr, $rr);
-		if(!$this->ldap->isResource($er)) {
+		if (!$this->ldap->isResource($er)) {
 			//did not match the filter, return false
 			return false;
 		}
 		//LDAP attributes are not case sensitive
 		$result = \OCP\Util::mb_array_change_key_case(
-				$this->ldap->getAttributes($cr, $er), MB_CASE_LOWER, 'UTF-8');
-		$attr = mb_strtolower($attr, 'UTF-8');
+			$this->ldap->getAttributes($cr, $er), MB_CASE_LOWER, 'UTF-8');
 
-		if(isset($result[$attr]) && $result[$attr]['count'] > 0) {
-			$values = array();
-			for($i=0;$i<$result[$attr]['count'];$i++) {
-				if($this->resemblesDN($attr)) {
-					$values[] = $this->helper->sanitizeDN($result[$attr][$i]);
-				} elseif(strtolower($attr) === 'objectguid' || strtolower($attr) === 'guid') {
-					$values[] = $this->convertObjectGUID2Str($result[$attr][$i]);
+		return $result;
+	}
+
+	/**
+	 * Normalizes a result grom getAttributes(), i.e. handles DNs and binary
+	 * data if present.
+	 *
+	 * @param array $result from ILDAPWrapper::getAttributes()
+	 * @param string $attribute the attribute name that was read
+	 * @return string[]
+	 */
+	public function extractAttributeValuesFromResult($result, $attribute) {
+		$values = [];
+		if(isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
+			$lowercaseAttribute = strtolower($attribute);
+			for($i=0;$i<$result[$attribute]['count'];$i++) {
+				if($this->resemblesDN($attribute)) {
+					$values[] = $this->helper->sanitizeDN($result[$attribute][$i]);
+				} elseif($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
+					$values[] = $this->convertObjectGUID2Str($result[$attribute][$i]);
 				} else {
-					$values[] = $result[$attr][$i];
+					$values[] = $result[$attribute][$i];
 				}
 			}
-			return $values;
 		}
-		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
-		return false;
+		return $values;
+	}
+
+	/**
+	 * Attempts to find ranged data in a getAttribute results and extracts the
+	 * returned values as well as information on the range and full attribute
+	 * name for further processing.
+	 *
+	 * @param array $result from ILDAPWrapper::getAttributes()
+	 * @param string $attribute the attribute name that was read. Without ";range=…"
+	 * @return array If a range was detected with keys 'values', 'attributeName',
+	 *               'attributeFull' and 'rangeHigh', otherwise empty.
+	 */
+	public function extractRangeData($result, $attribute) {
+		$keys = array_keys($result);
+		foreach($keys as $key) {
+			if($key !== $attribute && strpos($key, $attribute) === 0) {
+				$queryData = explode(';', $key);
+				if(strpos($queryData[1], 'range=') === 0) {
+					$high = substr($queryData[1], 1 + strpos($queryData[1], '-'));
+					$data = [
+						'values' => $result[$key],
+						'attributeName' => $queryData[0],
+						'attributeFull' => $key,
+						'rangeHigh' => $high,
+					];
+					return $data;
+				}
+			}
+		}
+		return [];
 	}
 	
 	/**