Merge pull request #1087 from owncloud/ldap_multiple_dns

LDAP: ability to configure more than one base DN
This commit is contained in:
blizzz 2013-01-15 04:01:57 -08:00
commit bb9cc227c2
4 changed files with 105 additions and 45 deletions

View File

@ -2,9 +2,11 @@
width: 20%;
max-width: 200px;
display: inline-block;
vertical-align: top;
padding-top: 9px;
}
#ldap fieldset input {
#ldap fieldset input, #ldap fieldset textarea {
width: 70%;
display: inline-block;
}

View File

@ -114,6 +114,15 @@ abstract class Access {
* @return the sanitized DN
*/
private function sanitizeDN($dn) {
//treating multiple base DNs
if(is_array($dn)) {
$result = array();
foreach($dn as $singleDN) {
$result[] = $this->sanitizeDN($singleDN);
}
return $result;
}
//OID sometimes gives back DNs with whitespace after the comma a la "uid=foo, cn=bar, dn=..." We need to tackle this!
$dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
@ -212,9 +221,13 @@ abstract class Access {
* returns the internal ownCloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
*/
public function dn2groupname($dn, $ldapname = null) {
if(mb_strripos($dn, $this->sanitizeDN($this->connection->ldapBaseGroups), 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($this->sanitizeDN($this->connection->ldapBaseGroups), 'UTF-8'))) {
//To avoid bypassing the base DN settings under certain circumstances
//with the group support, check whether the provided DN matches one of
//the given Bases
if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
return false;
}
return $this->dn2ocname($dn, $ldapname, false);
}
@ -227,9 +240,13 @@ abstract class Access {
* returns the internal ownCloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
*/
public function dn2username($dn, $ldapname = null) {
if(mb_strripos($dn, $this->sanitizeDN($this->connection->ldapBaseUsers), 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($this->sanitizeDN($this->connection->ldapBaseUsers), 'UTF-8'))) {
//To avoid bypassing the base DN settings under certain circumstances
//with the group support, check whether the provided DN matches one of
//the given Bases
if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseUsers)) {
return false;
}
return $this->dn2ocname($dn, $ldapname, true);
}
@ -521,7 +538,7 @@ abstract class Access {
/**
* @brief executes an LDAP search
* @param $filter the LDAP filter for the search
* @param $base the LDAP subtree that shall be searched
* @param $base an array containing the LDAP subtree(s) that shall be searched
* @param $attr optional, when a certain attribute shall be filtered out
* @returns array with the search result
*
@ -544,18 +561,28 @@ abstract class Access {
//check wether paged search should be attempted
$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, $limit, $offset);
$sr = ldap_search($link_resource, $base, $filter, $attr);
if(!$sr) {
$linkResources = array_pad(array(), count($base), $link_resource);
$sr = ldap_search($linkResources, $base, $filter, $attr);
$error = ldap_errno($link_resource);
if(!is_array($sr) || $error > 0) {
\OCP\Util::writeLog('user_ldap', 'Error when searching: '.ldap_error($link_resource).' code '.ldap_errno($link_resource), \OCP\Util::ERROR);
\OCP\Util::writeLog('user_ldap', 'Attempt for Paging? '.print_r($pagedSearchOK, true), \OCP\Util::ERROR);
return array();
}
$findings = ldap_get_entries($link_resource, $sr );
$findings = array();
foreach($sr as $key => $res) {
$findings = array_merge($findings, ldap_get_entries($link_resource, $res ));
}
if($pagedSearchOK) {
\OCP\Util::writeLog('user_ldap', 'Paged search successful', \OCP\Util::INFO);
ldap_control_paged_result_response($link_resource, $sr, $cookie);
\OCP\Util::writeLog('user_ldap', 'Set paged search cookie '.$cookie, \OCP\Util::INFO);
$this->setPagedResultCookie($filter, $limit, $offset, $cookie);
foreach($sr as $key => $res) {
$cookie = null;
if(ldap_control_paged_result_response($link_resource, $res, $cookie)) {
\OCP\Util::writeLog('user_ldap', 'Set paged search cookie', \OCP\Util::INFO);
$this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
}
}
//browsing through prior pages to get the cookie for the new one
if($skipHandling) {
return;
@ -565,7 +592,9 @@ abstract class Access {
$this->pagedSearchedSuccessful = true;
}
} else {
\OCP\Util::writeLog('user_ldap', 'Paged search failed :(', \OCP\Util::INFO);
if(!is_null($limit)) {
\OCP\Util::writeLog('user_ldap', 'Paged search failed :(', \OCP\Util::INFO);
}
}
// if we're here, probably no connection resource is returned.
@ -791,20 +820,41 @@ abstract class Access {
return str_replace('\\5c', '\\', $dn);
}
/**
* @brief checks if the given DN is part of the given base DN(s)
* @param $dn the DN
* @param $bases array containing the allowed base DN or DNs
* @returns Boolean
*/
private function isDNPartOfBase($dn, $bases) {
$bases = $this->sanitizeDN($bases);
foreach($bases as $base) {
$belongsToBase = true;
if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base))) {
$belongsToBase = false;
}
if($belongsToBase) {
break;
}
}
return $belongsToBase;
}
/**
* @brief get a cookie for the next LDAP paged search
* @param $base a string with the base DN for the search
* @param $filter the search filter to identify the correct search
* @param $limit the limit (or 'pageSize'), to identify the correct search well
* @param $offset the offset for the new search to identify the correct search really good
* @returns string containing the key or empty if none is cached
*/
private function getPagedResultCookie($filter, $limit, $offset) {
private function getPagedResultCookie($base, $filter, $limit, $offset) {
if($offset == 0) {
return '';
}
$offset -= $limit;
//we work with cache here
$cachekey = 'lc' . dechex(crc32($filter)) . '-' . $limit . '-' . $offset;
$cachekey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . $limit . '-' . $offset;
$cookie = $this->connection->getFromCache($cachekey);
if(is_null($cookie)) {
$cookie = '';
@ -814,15 +864,16 @@ abstract class Access {
/**
* @brief set a cookie for LDAP paged search run
* @param $base a string with the base DN for the search
* @param $filter the search filter to identify the correct search
* @param $limit the limit (or 'pageSize'), to identify the correct search well
* @param $offset the offset for the run search to identify the correct search really good
* @param $cookie string containing the cookie returned by ldap_control_paged_result_response
* @return void
*/
private function setPagedResultCookie($filter, $limit, $offset) {
private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
if(!empty($cookie)) {
$cachekey = 'lc' . dechex(crc32($filter)) . '-' . $limit . '-' . $offset;
$cachekey = 'lc' . dechex(crc32($base)) . '-' . dechex(crc32($filter)) . '-' .$limit . '-' . $offset;
$cookie = $this->connection->writeToCache($cachekey, $cookie);
}
}
@ -841,40 +892,47 @@ abstract class Access {
/**
* @brief prepares a paged search, if possible
* @param $filter the LDAP filter for the search
* @param $base the LDAP subtree that shall be searched
* @param $bases an array containing the LDAP subtree(s) that shall be searched
* @param $attr optional, when a certain attribute shall be filtered outside
* @param $limit
* @param $offset
*
*/
private function initPagedSearch($filter, $base, $attr, $limit, $offset) {
private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
$pagedSearchOK = false;
if($this->connection->hasPagedResultSupport && !is_null($limit)) {
$offset = intval($offset); //can be null
\OCP\Util::writeLog('user_ldap', 'initializing paged search for Filter'.$filter.' base '.$base.' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset, \OCP\Util::DEBUG);
\OCP\Util::writeLog('user_ldap', 'initializing paged search for Filter'.$filter.' base '.print_r($bases, true).' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset, \OCP\Util::INFO);
//get the cookie from the search for the previous search, required by LDAP
$cookie = $this->getPagedResultCookie($filter, $limit, $offset);
if(empty($cookie) && ($offset > 0)) {
//no cookie known, although the offset is not 0. Maybe cache run out. We need to start all over *sigh* (btw, Dear Reader, did you need LDAP paged searching was designed by MSFT?)
$reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
//a bit recursive, $offset of 0 is the exit
\OCP\Util::writeLog('user_ldap', 'Looking for cookie L/O '.$limit.'/'.$reOffset, \OCP\Util::INFO);
$this->search($filter, $base, $attr, $limit, $reOffset, true);
$cookie = $this->getPagedResultCookie($filter, $limit, $offset);
//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
//TODO: remember this, probably does not change in the next request...
if(empty($cookie)) {
$cookie = null;
foreach($bases as $base) {
$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
if(empty($cookie) && ($offset > 0)) {
//no cookie known, although the offset is not 0. Maybe cache run out. We need to start all over *sigh* (btw, Dear Reader, did you need LDAP paged searching was designed by MSFT?)
$reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
//a bit recursive, $offset of 0 is the exit
\OCP\Util::writeLog('user_ldap', 'Looking for cookie L/O '.$limit.'/'.$reOffset, \OCP\Util::INFO);
$this->search($filter, $base, $attr, $limit, $reOffset, true);
$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
//TODO: remember this, probably does not change in the next request...
if(empty($cookie)) {
$cookie = null;
}
}
}
if(!is_null($cookie)) {
if($offset > 0) {
\OCP\Util::writeLog('user_ldap', 'Cookie '.$cookie, \OCP\Util::INFO);
if(!is_null($cookie)) {
if($offset > 0) {
\OCP\Util::writeLog('user_ldap', 'Cookie '.$cookie, \OCP\Util::INFO);
}
$pagedSearchOK = ldap_control_paged_result($this->connection->getConnectionResource(), $limit, false, $cookie);
if(!$pagedSearchOK) {
return false;
}
\OCP\Util::writeLog('user_ldap', 'Ready for a paged search', \OCP\Util::INFO);
} else {
\OCP\Util::writeLog('user_ldap', 'No paged search for us, Cpt., Limit '.$limit.' Offset '.$offset, \OCP\Util::INFO);
}
$pagedSearchOK = ldap_control_paged_result($this->connection->getConnectionResource(), $limit, false, $cookie);
\OCP\Util::writeLog('user_ldap', 'Ready for a paged search', \OCP\Util::INFO);
} else {
\OCP\Util::writeLog('user_ldap', 'No paged search for us, Cpt., Limit '.$limit.' Offset '.$offset, \OCP\Util::INFO);
}
}

View File

@ -187,9 +187,9 @@ class Connection {
$this->config['ldapPort'] = \OCP\Config::getAppValue($this->configID, 'ldap_port', 389);
$this->config['ldapAgentName'] = \OCP\Config::getAppValue($this->configID, 'ldap_dn', '');
$this->config['ldapAgentPassword'] = base64_decode(\OCP\Config::getAppValue($this->configID, 'ldap_agent_password', ''));
$this->config['ldapBase'] = \OCP\Config::getAppValue($this->configID, 'ldap_base', '');
$this->config['ldapBaseUsers'] = \OCP\Config::getAppValue($this->configID, 'ldap_base_users', $this->config['ldapBase']);
$this->config['ldapBaseGroups'] = \OCP\Config::getAppValue($this->configID, 'ldap_base_groups', $this->config['ldapBase']);
$this->config['ldapBase'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, 'ldap_base', ''));
$this->config['ldapBaseUsers'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, 'ldap_base_users', $this->config['ldapBase']));
$this->config['ldapBaseGroups'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, 'ldap_base_groups', $this->config['ldapBase']));
$this->config['ldapTLS'] = \OCP\Config::getAppValue($this->configID, 'ldap_tls', 0);
$this->config['ldapNoCase'] = \OCP\Config::getAppValue($this->configID, 'ldap_nocase', 0);
$this->config['turnOffCertCheck'] = \OCP\Config::getAppValue($this->configID, 'ldap_turn_off_cert_check', 0);

View File

@ -8,12 +8,12 @@
echo '<p class="ldapwarning">'.$l->t('<b>Warning:</b> Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour. Please ask your system administrator to disable one of them.').'</p>';
}
if(!function_exists('ldap_connect')) {
echo '<p class="ldapwarning">'.$l->t('<b>Warning:</b> The PHP LDAP module needs is not installed, the backend will not work. Please ask your system administrator to install it.').'</p>';
echo '<p class="ldapwarning">'.$l->t('<b>Warning:</b> The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it.').'</p>';
}
?>
<fieldset id="ldapSettings-1">
<p><label for="ldap_host"><?php echo $l->t('Host');?></label><input type="text" id="ldap_host" name="ldap_host" value="<?php echo $_['ldap_host']; ?>" title="<?php echo $l->t('You can omit the protocol, except you require SSL. Then start with ldaps://');?>"></p>
<p><label for="ldap_base"><?php echo $l->t('Base DN');?></label><input type="text" id="ldap_base" name="ldap_base" value="<?php echo $_['ldap_base']; ?>" title="<?php echo $l->t('You can specify Base DN for users and groups in the Advanced tab');?>" /></p>
<p><label for="ldap_base"><?php echo $l->t('Base DN');?></label><textarea id="ldap_base" name="ldap_base" placeholder="<?php echo $l->t('One Base DN per line');?>" title="<?php echo $l->t('You can specify Base DN for users and groups in the Advanced tab');?>"><?php echo $_['ldap_base']; ?></textarea></p>
<p><label for="ldap_dn"><?php echo $l->t('User DN');?></label><input type="text" id="ldap_dn" name="ldap_dn" value="<?php echo $_['ldap_dn']; ?>" title="<?php echo $l->t('The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty.');?>" /></p>
<p><label for="ldap_agent_password"><?php echo $l->t('Password');?></label><input type="password" id="ldap_agent_password" name="ldap_agent_password" value="<?php echo $_['ldap_agent_password']; ?>" title="<?php echo $l->t('For anonymous access, leave DN and Password empty.');?>" /></p>
<p><label for="ldap_login_filter"><?php echo $l->t('User Login Filter');?></label><input type="text" id="ldap_login_filter" name="ldap_login_filter" value="<?php echo $_['ldap_login_filter']; ?>" title="<?php echo $l->t('Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action.');?>" /><br /><small><?php echo $l->t('use %%uid placeholder, e.g. "uid=%%uid"');?></small></p>
@ -22,8 +22,8 @@
</fieldset>
<fieldset id="ldapSettings-2">
<p><label for="ldap_port"><?php echo $l->t('Port');?></label><input type="text" id="ldap_port" name="ldap_port" value="<?php echo $_['ldap_port']; ?>" /></p>
<p><label for="ldap_base_users"><?php echo $l->t('Base User Tree');?></label><input type="text" id="ldap_base_users" name="ldap_base_users" value="<?php echo $_['ldap_base_users']; ?>" /></p>
<p><label for="ldap_base_groups"><?php echo $l->t('Base Group Tree');?></label><input type="text" id="ldap_base_groups" name="ldap_base_groups" value="<?php echo $_['ldap_base_groups']; ?>" /></p>
<p><label for="ldap_base_users"><?php echo $l->t('Base User Tree');?></label><textarea id="ldap_base_users" name="ldap_base_users" placeholder="<?php echo $l->t('One User Base DN per line');?>" title="<?php echo $l->t('Base User Tree');?>"><?php echo $_['ldap_base_users']; ?></textarea></p>
<p><label for="ldap_base_groups"><?php echo $l->t('Base Group Tree');?></label><textarea id="ldap_base_groups" name="ldap_base_groups" placeholder="<?php echo $l->t('One Group Base DN per line');?>" title="<?php echo $l->t('Base Group Tree');?>"><?php echo $_['ldap_base_groups']; ?></textarea></p>
<p><label for="ldap_group_member_assoc_attribute"><?php echo $l->t('Group-Member association');?></label><select id="ldap_group_member_assoc_attribute" name="ldap_group_member_assoc_attribute"><option value="uniqueMember"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] == 'uniqueMember')) echo ' selected'; ?>>uniqueMember</option><option value="memberUid"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] == 'memberUid')) echo ' selected'; ?>>memberUid</option><option value="member"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] == 'member')) echo ' selected'; ?>>member (AD)</option></select></p>
<p><label for="ldap_tls"><?php echo $l->t('Use TLS');?></label><input type="checkbox" id="ldap_tls" name="ldap_tls" value="1"<?php if ($_['ldap_tls']) echo ' checked'; ?> title="<?php echo $l->t('Do not use it for SSL connections, it will fail.');?>" /></p>
<p><label for="ldap_nocase"><?php echo $l->t('Case insensitve LDAP server (Windows)');?></label> <input type="checkbox" id="ldap_nocase" name="ldap_nocase" value="1"<?php if (isset($_['ldap_nocase']) && ($_['ldap_nocase'])) echo ' checked'; ?>></p>