aboutsummaryrefslogtreecommitdiffstats
path: root/apps/user_ldap/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/user_ldap/lib')
-rw-r--r--apps/user_ldap/lib/access.php176
-rw-r--r--apps/user_ldap/lib/connection.php320
-rw-r--r--apps/user_ldap/lib/helper.php105
-rw-r--r--apps/user_ldap/lib/proxy.php104
4 files changed, 622 insertions, 83 deletions
diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php
index f888577aedb..68cbe4a5e75 100644
--- a/apps/user_ldap/lib/access.php
+++ b/apps/user_ldap/lib/access.php
@@ -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.
@@ -690,6 +719,50 @@ abstract class Access {
return $combinedFilter;
}
+ /**
+ * @brief creates a filter part for to perfrom search for users
+ * @param string $search the search term
+ * @return string the final filter part to use in LDAP searches
+ */
+ public function getFilterPartForUserSearch($search) {
+ return $this->getFilterPartForSearch($search, $this->connection->ldapAttributesForUserSearch, $this->connection->ldapUserDisplayName);
+ }
+
+ /**
+ * @brief creates a filter part for to perfrom search for groups
+ * @param string $search the search term
+ * @return string the final filter part to use in LDAP searches
+ */
+ public function getFilterPartForGroupSearch($search) {
+ return $this->getFilterPartForSearch($search, $this->connection->ldapAttributesForGroupSearch, $this->connection->ldapGroupDisplayName);
+ }
+
+ /**
+ * @brief creates a filter part for searches
+ * @param string $search the search term
+ * @param string $fallbackAttribute a fallback attribute in case the user
+ * did not define search attributes. Typically the display name attribute.
+ * @returns string the final filter part to use in LDAP searches
+ */
+ private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
+ $filter = array();
+ $search = empty($search) ? '*' : '*'.$search.'*';
+ if(!is_array($searchAttributes) || count($searchAttributes) == 0) {
+ if(empty($fallbackAttribute)) {
+ return '';
+ }
+ $filter[] = $fallbackAttribute . '=' . $search;
+ } else {
+ foreach($searchAttributes as $attribute) {
+ $filter[] = $attribute . '=' . $search;
+ }
+ }
+ if(count($filter) == 1) {
+ return '('.$filter[0].')';
+ }
+ return $this->combineFilterWithOr($filter);
+ }
+
public function areCredentialsValid($name, $password) {
$name = $this->DNasBaseParameter($name);
$testConnection = clone $this->connection;
@@ -792,19 +865,40 @@ abstract class Access {
}
/**
+ * @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 +908,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 +936,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, array($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);
+
}
}
diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php
index b14cdafff89..933f2f42074 100644
--- a/apps/user_ldap/lib/connection.php
+++ b/apps/user_ldap/lib/connection.php
@@ -4,7 +4,7 @@
* ownCloud – LDAP Access
*
* @author Arthur Schiwon
- * @copyright 2012 Arthur Schiwon blizzz@owncloud.com
+ * @copyright 2012, 2013 Arthur Schiwon blizzz@owncloud.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -25,6 +25,7 @@ namespace OCA\user_ldap\lib;
class Connection {
private $ldapConnectionRes = null;
+ private $configPrefix;
private $configID;
private $configured = false;
@@ -35,6 +36,8 @@ class Connection {
protected $config = array(
'ldapHost' => null,
'ldapPort' => null,
+ 'ldapBackupHost' => null,
+ 'ldapBackupPort' => null,
'ldapBase' => null,
'ldapBaseUsers' => null,
'ldapBaseGroups' => null,
@@ -48,6 +51,7 @@ class Connection {
'ldapUserFilter' => null,
'ldapGroupFilter' => null,
'ldapGroupDisplayName' => null,
+ 'ldapGroupMemberAssocAttr' => null,
'ldapLoginFilter' => null,
'ldapQuotaAttribute' => null,
'ldapQuotaDefault' => null,
@@ -55,15 +59,24 @@ class Connection {
'ldapCacheTTL' => null,
'ldapUuidAttribute' => null,
'ldapOverrideUuidAttribute' => null,
+ 'ldapOverrideMainServer' => false,
+ 'ldapConfigurationActive' => false,
+ 'ldapAttributesForUserSearch' => null,
+ 'ldapAttributesForGroupSearch' => null,
'homeFolderNamingRule' => null,
'hasPagedResultSupport' => false,
);
- public function __construct($configID = 'user_ldap') {
+ /**
+ * @brief Constructor
+ * @param $configPrefix a string with the prefix for the configkey column (appconfig table)
+ * @param $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
+ */
+ public function __construct($configPrefix = '', $configID = 'user_ldap') {
+ $this->configPrefix = $configPrefix;
$this->configID = $configID;
$this->cache = \OC_Cache::getGlobalCache();
$this->config['hasPagedResultSupport'] = (function_exists('ldap_control_paged_result') && function_exists('ldap_control_paged_result_response'));
- \OCP\Util::writeLog('user_ldap', 'PHP supports paged results? '.print_r($this->config['hasPagedResultSupport'], true), \OCP\Util::INFO);
}
public function __destruct() {
@@ -84,12 +97,12 @@ class Connection {
public function __set($name, $value) {
$changed = false;
- //omly few options are writable
+ //only 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::setAppValue($this->configID, 'ldap_uuid_attribute', $value);
+ \OCP\Config::setAppValue($this->configID, $this->configPrefix.'ldap_uuid_attribute', $value);
}
$changed = true;
}
@@ -126,7 +139,7 @@ class Connection {
}
private function getCacheKey($key) {
- $prefix = 'LDAP-'.$this->configID.'-';
+ $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
if(is_null($key)) {
return $prefix;
}
@@ -164,7 +177,8 @@ class Connection {
if(!$this->configured) {
$this->readConfiguration();
}
- if(!$this->config['ldapCacheTTL']) {
+ if(!$this->config['ldapCacheTTL']
+ || !$this->config['ldapConfigurationActive']) {
return null;
}
$key = $this->getCacheKey($key);
@@ -176,43 +190,97 @@ class Connection {
$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);
+ }
+
/**
* Caches the general LDAP configuration.
*/
private function readConfiguration($force = false) {
- \OCP\Util::writeLog('user_ldap', 'Checking conf state: isConfigured? '.print_r($this->configured, true).' isForce? '.print_r($force, true).' configID? '.print_r($this->configID, true), \OCP\Util::DEBUG);
if((!$this->configured || $force) && !is_null($this->configID)) {
- \OCP\Util::writeLog('user_ldap', 'Reading the configuration', \OCP\Util::DEBUG);
- $this->config['ldapHost'] = \OCP\Config::getAppValue($this->configID, 'ldap_host', '');
- $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['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);
- $this->config['ldapUserDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, 'ldap_display_name', 'uid'), 'UTF-8');
- $this->config['ldapUserFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_userlist_filter', 'objectClass=person');
- $this->config['ldapGroupFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_group_filter', '(objectClass=posixGroup)');
- $this->config['ldapLoginFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_login_filter', '(uid=%uid)');
- $this->config['ldapGroupDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, 'ldap_group_display_name', 'uid'), 'UTF-8');
- $this->config['ldapQuotaAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_quota_attr', '');
- $this->config['ldapQuotaDefault'] = \OCP\Config::getAppValue($this->configID, 'ldap_quota_def', '');
- $this->config['ldapEmailAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_email_attr', '');
- $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->config['homeFolderNamingRule'] = \OCP\Config::getAppValue($this->configID, 'home_folder_naming_rule', 'opt:username');
+ $defaults = $this->getDefaults();
+ $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'));
+ $rawLdapBase = $this->$v('ldap_base');
+ $this->config['ldapBase']
+ = preg_split('/\r\n|\r|\n/', $rawLdapBase);
+ $this->config['ldapBaseUsers']
+ = preg_split('/\r\n|\r|\n/', ($this->$v('ldap_base_users')));
+ $this->config['ldapBaseGroups']
+ = preg_split('/\r\n|\r|\n/', $this->$v('ldap_base_groups'));
+ unset($rawLdapBase);
+ $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['ldapUuidAttribute']
+ = $this->$v('ldap_uuid_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->configured = $this->validateConfiguration();
}
}
/**
+ * @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');
+ 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
@@ -223,11 +291,14 @@ class Connection {
return false;
}
- $params = array('ldap_host'=>'ldapHost', 'ldap_port'=>'ldapPort', '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');
+ $params = $this->getConfigTranslationArray();
foreach($config as $parameter => $value) {
+ if(($parameter == 'homeFolderNamingRule'
+ || $params[$parameter] == 'homeFolderNamingRule')
+ && !empty($value)) {
+ $value = 'attr:'.$value;
+ }
if(isset($this->config[$parameter])) {
$this->config[$parameter] = $value;
if(is_array($setParameters)) {
@@ -247,6 +318,71 @@ class Connection {
}
/**
+ * @brief saves the current Configuration in the database
+ */
+ public function saveConfiguration() {
+ $trans = array_flip($this->getConfigTranslationArray());
+ foreach($this->config as $key => $value) {
+ \OCP\Util::writeLog('user_ldap', 'LDAP: storing key '.$key.' value '.$value, \OCP\Util::DEBUG);
+ switch ($key) {
+ case 'ldapAgentPassword':
+ $value = base64_encode($value);
+ break;
+ case 'homeFolderNamingRule':
+ $value = empty($value) ? 'opt:username' : $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 'ldapUuidAttribute':
+ case 'hasPagedResultSupport':
+ continue 2;
+ }
+ if(is_null($value)) {
+ $value = '';
+ }
+
+ $this->setValue($trans[$key], $value);
+ }
+ $this->clearCache();
+ }
+
+ /**
+ * @brief get the current LDAP configuration
+ * @return array
+ */
+ public function getConfiguration() {
+ $this->readConfiguration();
+ $trans = $this->getConfigTranslationArray();
+ $config = array();
+ foreach($trans as $dbKey => $classKey) {
+ if($classKey == 'homeFolderNamingRule') {
+ if(strpos($this->config[$classKey], 'opt') === 0) {
+ $config[$dbKey] = '';
+ } else {
+ $config[$dbKey] = substr($this->config[$classKey], 5);
+ }
+ continue;
+ } else if((strpos($classKey, 'ldapBase') !== false)
+ || (strpos($classKey, 'ldapAttributes') !== false)) {
+ $config[$dbKey] = implode("\n", $this->config[$classKey]);
+ continue;
+ }
+ $config[$dbKey] = $this->config[$classKey];
+ }
+
+ return $config;
+ }
+
+ /**
* @brief Validates the user specified configuration
* @returns true if configuration seems OK, false otherwise
*/
@@ -264,9 +400,26 @@ class Connection {
\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')) && (!is_null($this->configID))) {
- \OCP\Config::setAppValue($this->configID, 'ldap_uuid_attribute', 'auto');
+ \OCP\Config::setAppValue($this->configID, $this->configPrefix.'ldap_uuid_attribute', 'auto');
\OCP\Util::writeLog('user_ldap', 'Illegal value for the UUID Attribute, reset to autodetect.', \OCP\Util::INFO);
}
+ 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.
@@ -311,9 +464,50 @@ class Connection {
}
/**
+ * @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' => 'opt:username',
+ 'ldap_turn_off_cert_check' => 0,
+ 'ldap_configuration_active' => 1,
+ 'ldap_attributes_for_user_search' => '',
+ 'ldap_attributes_for_group_search' => '',
+ );
+ }
+
+ /**
* Connects and Binds to LDAP
*/
private function establishConnection() {
+ if(!$this->config['ldapConfigurationActive']) {
+ return null;
+ }
static $phpLDAPinstalled = true;
if(!$phpLDAPinstalled) {
return false;
@@ -336,16 +530,43 @@ class Connection {
\OCP\Util::writeLog('user_ldap', 'Could not turn off SSL certificate validation.', \OCP\Util::WARN);
}
}
- $this->ldapConnectionRes = ldap_connect($this->config['ldapHost'], $this->config['ldapPort']);
- if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
- if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
- if($this->config['ldapTLS']) {
- ldap_start_tls($this->ldapConnectionRes);
+ if(!$this->config['ldapOverrideMainServer'] && !$this->getFromCache('overrideMainServer')) {
+ $this->doConnect($this->config['ldapHost'], $this->config['ldapPort']);
+ $bindStatus = $this->bind();
+ $error = is_resource($this->ldapConnectionRes) ? ldap_errno($this->ldapConnectionRes) : -1;
+ } else {
+ $bindStatus = false;
+ $error = null;
+ }
+
+ $error = null;
+ //if LDAP server is not reachable, try the Backup (Replica!) Server
+ if((!$bindStatus && ($error == -1))
+ || $this->config['ldapOverrideMainServer']
+ || $this->getFromCache('overrideMainServer')) {
+ $this->doConnect($this->config['ldapBackupHost'], $this->config['ldapBackupPort']);
+ $bindStatus = $this->bind();
+ if($bindStatus && $error == -1) {
+ //when bind to backup server succeeded and failed to main server,
+ //skip contacting him until next cache refresh
+ $this->writeToCache('overrideMainServer', true);
}
- }
}
+ return $bindStatus;
+ }
+ }
- return $this->bind();
+ private function doConnect($host, $port) {
+ if(empty($host)) {
+ return false;
+ }
+ $this->ldapConnectionRes = ldap_connect($host, $port);
+ if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
+ if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
+ if($this->config['ldapTLS']) {
+ ldap_start_tls($this->ldapConnectionRes);
+ }
+ }
}
}
@@ -353,9 +574,16 @@ class Connection {
* Binds to LDAP
*/
public function bind() {
- $ldapLogin = @ldap_bind($this->getConnectionResource(), $this->config['ldapAgentName'], $this->config['ldapAgentPassword']);
+ if(!$this->config['ldapConfigurationActive']) {
+ return false;
+ }
+ $cr = $this->getConnectionResource();
+ if(!is_resource($cr)) {
+ return false;
+ }
+ $ldapLogin = @ldap_bind($cr, $this->config['ldapAgentName'], $this->config['ldapAgentPassword']);
if(!$ldapLogin) {
- \OCP\Util::writeLog('user_ldap', 'Bind failed: ' . ldap_errno($this->ldapConnectionRes) . ': ' . ldap_error($this->ldapConnectionRes), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('user_ldap', 'Bind failed: ' . ldap_errno($cr) . ': ' . ldap_error($cr), \OCP\Util::ERROR);
$this->ldapConnectionRes = null;
return false;
}
diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php
new file mode 100644
index 00000000000..29ce998dae7
--- /dev/null
+++ b/apps/user_ldap/lib/helper.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * ownCloud – LDAP Helper
+ *
+ * @author Arthur Schiwon
+ * @copyright 2013 Arthur Schiwon blizzz@owncloud.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\user_ldap\lib;
+
+class Helper {
+
+ /**
+ * @brief returns prefixes for each saved LDAP/AD server configuration.
+ * @param bool optional, whether only active configuration shall be
+ * retrieved, defaults to false
+ * @return array with a list of the available prefixes
+ *
+ * Configuration prefixes are used to set up configurations for n LDAP or
+ * AD servers. Since configuration is stored in the database, table
+ * appconfig under appid user_ldap, the common identifiers in column
+ * 'configkey' have a prefix. The prefix for the very first server
+ * configuration is empty.
+ * Configkey Examples:
+ * Server 1: ldap_login_filter
+ * Server 2: s1_ldap_login_filter
+ * Server 3: s2_ldap_login_filter
+ *
+ * The prefix needs to be passed to the constructor of Connection class,
+ * except the default (first) server shall be connected to.
+ *
+ */
+ static public function getServerConfigurationPrefixes($activeConfigurations = false) {
+ $referenceConfigkey = 'ldap_configuration_active';
+
+ $query = '
+ SELECT DISTINCT `configkey`
+ FROM `*PREFIX*appconfig`
+ WHERE `configkey` LIKE ?
+ ';
+ if($activeConfigurations) {
+ $query .= ' AND `configvalue` = 1';
+ }
+ $query = \OCP\DB::prepare($query);
+
+ $serverConfigs = $query->execute(array('%'.$referenceConfigkey))->fetchAll();
+ $prefixes = array();
+
+ foreach($serverConfigs as $serverConfig) {
+ $len = strlen($serverConfig['configkey']) - strlen($referenceConfigkey);
+ $prefixes[] = substr($serverConfig['configkey'], 0, $len);
+ }
+
+ return $prefixes;
+ }
+
+ /**
+ * @brief deletes a given saved LDAP/AD server configuration.
+ * @param string the configuration prefix of the config to delete
+ * @return bool true on success, false otherwise
+ */
+ static public function deleteServerConfiguration($prefix) {
+ //just to be on the safe side
+ \OCP\User::checkAdminUser();
+
+ if(!in_array($prefix, self::getServerConfigurationPrefixes())) {
+ return false;
+ }
+
+ $query = \OCP\DB::prepare('
+ DELETE
+ FROM `*PREFIX*appconfig`
+ WHERE `configkey` LIKE ?
+ AND `appid` = "user_ldap"
+ AND `configkey` NOT IN ("enabled", "installed_version", "types", "bgjUpdateGroupsLastRun")
+ ');
+ $res = $query->execute(array($prefix.'%'));
+
+ if(\OCP\DB::isError($res)) {
+ return false;
+ }
+
+ if($res->numRows() == 0) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
diff --git a/apps/user_ldap/lib/proxy.php b/apps/user_ldap/lib/proxy.php
new file mode 100644
index 00000000000..c80e2163475
--- /dev/null
+++ b/apps/user_ldap/lib/proxy.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * ownCloud – LDAP Backend Proxy
+ *
+ * @author Arthur Schiwon
+ * @copyright 2013 Arthur Schiwon blizzz@owncloud.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\user_ldap\lib;
+
+abstract class Proxy {
+ static private $connectors = array();
+
+ public function __construct() {
+ $this->cache = \OC_Cache::getGlobalCache();
+ }
+
+ private function addConnector($configPrefix) {
+ self::$connectors[$configPrefix] = new \OCA\user_ldap\lib\Connection($configPrefix);
+ }
+
+ protected function getConnector($configPrefix) {
+ if(!isset(self::$connectors[$configPrefix])) {
+ $this->addConnector($configPrefix);
+ }
+ return self::$connectors[$configPrefix];
+ }
+
+ protected function getConnectors() {
+ return self::$connectors;
+ }
+
+ protected function getUserCacheKey($uid) {
+ return 'user-'.$uid.'-lastSeenOn';
+ }
+
+ protected function getGroupCacheKey($gid) {
+ return 'group-'.$gid.'-lastSeenOn';
+ }
+
+ abstract protected function callOnLastSeenOn($id, $method, $parameters);
+ abstract protected function walkBackends($id, $method, $parameters);
+
+ /**
+ * @brief Takes care of the request to the User backend
+ * @param $uid string, the uid connected to the request
+ * @param $method string, the method of the user backend that shall be called
+ * @param $parameters an array of parameters to be passed
+ * @return mixed, the result of the specified method
+ */
+ protected function handleRequest($id, $method, $parameters) {
+ if(!$result = $this->callOnLastSeenOn($id, $method, $parameters)) {
+ $result = $this->walkBackends($id, $method, $parameters);
+ }
+ return $result;
+ }
+
+ private function getCacheKey($key) {
+ $prefix = 'LDAP-Proxy-';
+ if(is_null($key)) {
+ return $prefix;
+ }
+ return $prefix.md5($key);
+ }
+
+ public function getFromCache($key) {
+ if(!$this->isCached($key)) {
+ return null;
+ }
+ $key = $this->getCacheKey($key);
+
+ return unserialize(base64_decode($this->cache->get($key)));
+ }
+
+ public function isCached($key) {
+ $key = $this->getCacheKey($key);
+ return $this->cache->hasKey($key);
+ }
+
+ public function writeToCache($key, $value) {
+ $key = $this->getCacheKey($key);
+ $value = base64_encode(serialize($value));
+ $this->cache->set($key, $value, '2592000');
+ }
+
+ public function clearCache() {
+ $this->cache->clear($this->getCacheKey(null));
+ }
+} \ No newline at end of file