aboutsummaryrefslogtreecommitdiffstats
path: root/apps/user_ldap/lib/Helper.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/user_ldap/lib/Helper.php')
-rw-r--r--apps/user_ldap/lib/Helper.php297
1 files changed, 144 insertions, 153 deletions
diff --git a/apps/user_ldap/lib/Helper.php b/apps/user_ldap/lib/Helper.php
index 3157a7ab09d..d3abf04fd1e 100644
--- a/apps/user_ldap/lib/Helper.php
+++ b/apps/user_ldap/lib/Helper.php
@@ -1,58 +1,34 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Brice Maron <brice@bmaron.net>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Roger Szabo <roger.szabo@web.de>
- * @author root <root@localhost.localdomain>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <pvince81@owncloud.com>
- * @author Vinicius Cubas Brand <vinicius@eita.org.br>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program 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, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
-
namespace OCA\User_LDAP;
-use OCP\IConfig;
+use OCP\Cache\CappedMemoryCache;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IAppConfig;
+use OCP\IDBConnection;
+use OCP\Server;
class Helper {
-
- /** @var IConfig */
- private $config;
-
- /**
- * Helper constructor.
- *
- * @param IConfig $config
- */
- public function __construct(IConfig $config) {
- $this->config = $config;
+ /** @var CappedMemoryCache<string> */
+ protected CappedMemoryCache $sanitizeDnCache;
+
+ public function __construct(
+ private IAppConfig $appConfig,
+ private IDBConnection $connection,
+ ) {
+ $this->sanitizeDnCache = new CappedMemoryCache(10000);
}
/**
* returns prefixes for each saved LDAP/AD server configuration.
+ *
* @param bool $activeConfigurations optional, whether only active configuration shall be
- * retrieved, defaults to false
+ * 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
@@ -69,20 +45,37 @@ class Helper {
* except the default (first) server shall be connected to.
*
*/
- public function getServerConfigurationPrefixes($activeConfigurations = false) {
+ public function getServerConfigurationPrefixes(bool $activeConfigurations = false): array {
+ $all = $this->getAllServerConfigurationPrefixes();
+ if (!$activeConfigurations) {
+ return $all;
+ }
+ return array_values(array_filter(
+ $all,
+ fn (string $prefix): bool => ($this->appConfig->getValueString('user_ldap', $prefix . 'ldap_configuration_active') === '1')
+ ));
+ }
+
+ protected function getAllServerConfigurationPrefixes(): array {
+ $unfilled = ['UNFILLED'];
+ $prefixes = $this->appConfig->getValueArray('user_ldap', 'configuration_prefixes', $unfilled);
+ if ($prefixes !== $unfilled) {
+ return $prefixes;
+ }
+
+ /* Fallback to browsing key for migration from Nextcloud<32 */
$referenceConfigkey = 'ldap_configuration_active';
$keys = $this->getServersConfig($referenceConfigkey);
$prefixes = [];
foreach ($keys as $key) {
- if ($activeConfigurations && $this->config->getAppValue('user_ldap', $key, '0') !== '1') {
- continue;
- }
-
$len = strlen($key) - strlen($referenceConfigkey);
$prefixes[] = substr($key, 0, $len);
}
+ sort($prefixes);
+
+ $this->appConfig->setValueArray('user_ldap', 'configuration_prefixes', $prefixes);
return $prefixes;
}
@@ -90,47 +83,46 @@ class Helper {
/**
*
* determines the host for every configured connection
- * @return array an array with configprefix as keys
+ *
+ * @return array<string,string> an array with configprefix as keys
*
*/
- public function getServerConfigurationHosts() {
- $referenceConfigkey = 'ldap_host';
+ public function getServerConfigurationHosts(): array {
+ $prefixes = $this->getServerConfigurationPrefixes();
- $keys = $this->getServersConfig($referenceConfigkey);
-
- $result = array();
- foreach($keys as $key) {
- $len = strlen($key) - strlen($referenceConfigkey);
- $prefix = substr($key, 0, $len);
- $result[$prefix] = $this->config->getAppValue('user_ldap', $key);
+ $referenceConfigkey = 'ldap_host';
+ $result = [];
+ foreach ($prefixes as $prefix) {
+ $result[$prefix] = $this->appConfig->getValueString('user_ldap', $prefix . $referenceConfigkey);
}
return $result;
}
/**
- * return the next available configuration prefix
- *
- * @return string
+ * return the next available configuration prefix and register it as used
*/
- public function getNextServerConfigurationPrefix() {
- $serverConnections = $this->getServerConfigurationPrefixes();
-
- if(count($serverConnections) === 0) {
- return 's01';
+ public function getNextServerConfigurationPrefix(): string {
+ $prefixes = $this->getServerConfigurationPrefixes();
+
+ if (count($prefixes) === 0) {
+ $prefix = 's01';
+ } else {
+ sort($prefixes);
+ $lastKey = array_pop($prefixes);
+ $lastNumber = (int)str_replace('s', '', $lastKey);
+ $prefix = 's' . str_pad((string)($lastNumber + 1), 2, '0', STR_PAD_LEFT);
}
- sort($serverConnections);
- $lastKey = array_pop($serverConnections);
- $lastNumber = intval(str_replace('s', '', $lastKey));
- $nextPrefix = 's' . str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT);
- return $nextPrefix;
+ $prefixes[] = $prefix;
+ $this->appConfig->setValueArray('user_ldap', 'configuration_prefixes', $prefixes);
+ return $prefix;
}
- private function getServersConfig($value) {
+ private function getServersConfig(string $value): array {
$regex = '/' . $value . '$/S';
- $keys = $this->config->getAppKeys('user_ldap');
+ $keys = $this->appConfig->getKeys('user_ldap');
$result = [];
foreach ($keys as $key) {
if (preg_match($regex, $key) === 1) {
@@ -143,115 +135,125 @@ class Helper {
/**
* deletes a given saved LDAP/AD server configuration.
+ *
* @param string $prefix the configuration prefix of the config to delete
* @return bool true on success, false otherwise
*/
public function deleteServerConfiguration($prefix) {
- if(!in_array($prefix, self::getServerConfigurationPrefixes())) {
+ $prefixes = $this->getServerConfigurationPrefixes();
+ $index = array_search($prefix, $prefixes);
+ if ($index === false) {
return false;
}
- $saveOtherConfigurations = '';
- if(empty($prefix)) {
- $saveOtherConfigurations = 'AND `configkey` NOT LIKE \'s%\'';
+ $query = $this->connection->getQueryBuilder();
+ $query->delete('appconfig')
+ ->where($query->expr()->eq('appid', $query->createNamedParameter('user_ldap')))
+ ->andWhere($query->expr()->like('configkey', $query->createNamedParameter((string)$prefix . '%')))
+ ->andWhere($query->expr()->notIn('configkey', $query->createNamedParameter([
+ 'enabled',
+ 'installed_version',
+ 'types',
+ 'bgjUpdateGroupsLastRun',
+ ], IQueryBuilder::PARAM_STR_ARRAY)));
+
+ if (empty($prefix)) {
+ $query->andWhere($query->expr()->notLike('configkey', $query->createNamedParameter('s%')));
}
- $query = \OCP\DB::prepare('
- DELETE
- FROM `*PREFIX*appconfig`
- WHERE `configkey` LIKE ?
- '.$saveOtherConfigurations.'
- AND `appid` = \'user_ldap\'
- AND `configkey` NOT IN (\'enabled\', \'installed_version\', \'types\', \'bgjUpdateGroupsLastRun\')
- ');
- $delRows = $query->execute(array($prefix.'%'));
-
- if(\OCP\DB::isError($delRows)) {
- return false;
- }
+ $deletedRows = $query->executeStatement();
- if($delRows === 0) {
- return false;
- }
+ unset($prefixes[$index]);
+ $this->appConfig->setValueArray('user_ldap', 'configuration_prefixes', array_values($prefixes));
- return true;
+ return $deletedRows !== 0;
}
/**
* checks whether there is one or more disabled LDAP configurations
- * @throws \Exception
- * @return bool
*/
- public function haveDisabledConfigurations() {
- $all = $this->getServerConfigurationPrefixes(false);
- $active = $this->getServerConfigurationPrefixes(true);
-
- if(!is_array($all) || !is_array($active)) {
- throw new \Exception('Unexpected Return Value');
+ public function haveDisabledConfigurations(): bool {
+ $all = $this->getServerConfigurationPrefixes();
+ foreach ($all as $prefix) {
+ if ($this->appConfig->getValueString('user_ldap', $prefix . 'ldap_configuration_active') !== '1') {
+ return true;
+ }
}
-
- return count($all) !== count($active) || count($all) === 0;
+ return false;
}
/**
* extracts the domain from a given URL
+ *
* @param string $url the URL
* @return string|false domain as string on success, false otherwise
*/
public function getDomainFromURL($url) {
$uinfo = parse_url($url);
- if(!is_array($uinfo)) {
+ if (!is_array($uinfo)) {
return false;
}
$domain = false;
- if(isset($uinfo['host'])) {
+ if (isset($uinfo['host'])) {
$domain = $uinfo['host'];
- } else if(isset($uinfo['path'])) {
+ } elseif (isset($uinfo['path'])) {
$domain = $uinfo['path'];
}
return $domain;
}
-
+
/**
+ * sanitizes a DN received from the LDAP server
*
- * Set the LDAPProvider in the config
+ * This is used and done to have a stable format of DNs that can be compared
+ * and identified again. The input DN value is modified as following:
*
- */
- public function setLDAPProvider() {
- $current = \OC::$server->getConfig()->getSystemValue('ldapProviderFactory', null);
- if(is_null($current)) {
- \OC::$server->getConfig()->setSystemValue('ldapProviderFactory', '\\OCA\\User_LDAP\\LDAPProviderFactory');
- }
- }
-
- /**
- * sanitizes a DN received from the LDAP server
- * @param array $dn the DN in question
+ * 1) whitespaces after commas are removed
+ * 2) the DN is turned to lower-case
+ * 3) the DN is escaped according to RFC 2253
+ *
+ * When a future DN is supposed to be used as a base parameter, it has to be
+ * run through DNasBaseParameter() first, to recode \5c into a backslash
+ * again, otherwise the search or read operation will fail with LDAP error
+ * 32, NO_SUCH_OBJECT. Regular usage in LDAP filters requires the backslash
+ * being escaped, however.
+ *
+ * Internally, DNs are stored in their sanitized form.
+ *
+ * @param array|string $dn the DN in question
* @return array|string the sanitized DN
*/
public function sanitizeDN($dn) {
//treating multiple base DNs
- if(is_array($dn)) {
- $result = array();
- foreach($dn as $singleDN) {
+ if (is_array($dn)) {
+ $result = [];
+ foreach ($dn as $singleDN) {
$result[] = $this->sanitizeDN($singleDN);
}
return $result;
}
+ if (!is_string($dn)) {
+ throw new \LogicException('String expected ' . \gettype($dn) . ' given');
+ }
+
+ if (($sanitizedDn = $this->sanitizeDnCache->get($dn)) !== null) {
+ return $sanitizedDn;
+ }
+
//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);
+ $sanitizedDn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
//make comparisons and everything work
- $dn = mb_strtolower($dn, 'UTF-8');
+ $sanitizedDn = mb_strtolower($sanitizedDn, 'UTF-8');
//escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
//to use the DN in search filters, \ needs to be escaped to \5c additionally
//to use them in bases, we convert them back to simple backslashes in readAttribute()
- $replacements = array(
+ $replacements = [
'\,' => '\5c2C',
'\=' => '\5c3D',
'\+' => '\5c2B',
@@ -260,17 +262,19 @@ class Helper {
'\;' => '\5c3B',
'\"' => '\5c22',
'\#' => '\5c23',
- '(' => '\28',
- ')' => '\29',
- '*' => '\2A',
- );
- $dn = str_replace(array_keys($replacements), array_values($replacements), $dn);
-
- return $dn;
+ '(' => '\28',
+ ')' => '\29',
+ '*' => '\2A',
+ ];
+ $sanitizedDn = str_replace(array_keys($replacements), array_values($replacements), $sanitizedDn);
+ $this->sanitizeDnCache->set($dn, $sanitizedDn);
+
+ return $sanitizedDn;
}
-
+
/**
* converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
+ *
* @param string $dn the DN
* @return string
*/
@@ -282,30 +286,17 @@ class Helper {
* listens to a hook thrown by server2server sharing and replaces the given
* login name by a username, if it matches an LDAP user.
*
- * @param array $param
+ * @param array $param contains a reference to a $uid var under 'uid' key
* @throws \Exception
*/
- public static function loginName2UserName($param) {
- if(!isset($param['uid'])) {
+ public static function loginName2UserName($param): void {
+ if (!isset($param['uid'])) {
throw new \Exception('key uid is expected to be set in $param');
}
- //ain't it ironic?
- $helper = new Helper(\OC::$server->getConfig());
-
- $configPrefixes = $helper->getServerConfigurationPrefixes(true);
- $ldapWrapper = new LDAP();
- $ocConfig = \OC::$server->getConfig();
- $notificationManager = \OC::$server->getNotificationManager();
-
- $userSession = \OC::$server->getUserSession();
- $userPluginManager = \OC::$server->query('LDAPUserPluginManager');
-
- $userBackend = new User_Proxy(
- $configPrefixes, $ldapWrapper, $ocConfig, $notificationManager, $userSession, $userPluginManager
- );
- $uid = $userBackend->loginName2UserName($param['uid'] );
- if($uid !== false) {
+ $userBackend = Server::get(User_Proxy::class);
+ $uid = $userBackend->loginName2UserName($param['uid']);
+ if ($uid !== false) {
$param['uid'] = $uid;
}
}