Passed
Push — master ( 356350...344eac )
by Morris
20:06 queued 02:55
created

Helper   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 100
c 1
b 0
f 0
dl 0
loc 253
rs 10
wmc 30

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getDomainFromURL() 0 14 4
A __construct() 0 5 1
A deleteServerConfiguration() 0 22 3
A getNextServerConfigurationPrefix() 0 11 2
A getServerConfigurationHosts() 0 13 2
A getServerConfigurationPrefixes() 0 17 4
A haveDisabledConfigurations() 0 5 2
A getServersConfig() 0 12 3
A DNasBaseParameter() 0 2 1
A sanitizeDN() 0 45 5
A loginName2UserName() 0 9 3
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Brice Maron <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Jörn Friedrich Dreyer <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Roger Szabo <[email protected]>
14
 * @author root <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 * @author Vincent Petry <[email protected]>
17
 * @author Vinicius Cubas Brand <[email protected]>
18
 *
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program. If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
35
namespace OCA\User_LDAP;
36
37
use OC\Cache\CappedMemoryCache;
38
use OCP\DB\QueryBuilder\IQueryBuilder;
39
use OCP\IConfig;
40
use OCP\IDBConnection;
41
42
class Helper {
43
44
	/** @var IConfig */
45
	private $config;
46
47
	/** @var IDBConnection */
48
	private $connection;
49
50
	/** @var CappedMemoryCache */
51
	protected $sanitizeDnCache;
52
53
	public function __construct(IConfig $config,
54
								IDBConnection $connection) {
55
		$this->config = $config;
56
		$this->connection = $connection;
57
		$this->sanitizeDnCache = new CappedMemoryCache(10000);
58
	}
59
60
	/**
61
	 * returns prefixes for each saved LDAP/AD server configuration.
62
	 *
63
	 * @param bool $activeConfigurations optional, whether only active configuration shall be
64
	 * retrieved, defaults to false
65
	 * @return array with a list of the available prefixes
66
	 *
67
	 * Configuration prefixes are used to set up configurations for n LDAP or
68
	 * AD servers. Since configuration is stored in the database, table
69
	 * appconfig under appid user_ldap, the common identifiers in column
70
	 * 'configkey' have a prefix. The prefix for the very first server
71
	 * configuration is empty.
72
	 * Configkey Examples:
73
	 * Server 1: ldap_login_filter
74
	 * Server 2: s1_ldap_login_filter
75
	 * Server 3: s2_ldap_login_filter
76
	 *
77
	 * The prefix needs to be passed to the constructor of Connection class,
78
	 * except the default (first) server shall be connected to.
79
	 *
80
	 */
81
	public function getServerConfigurationPrefixes($activeConfigurations = false): array {
82
		$referenceConfigkey = 'ldap_configuration_active';
83
84
		$keys = $this->getServersConfig($referenceConfigkey);
85
86
		$prefixes = [];
87
		foreach ($keys as $key) {
88
			if ($activeConfigurations && $this->config->getAppValue('user_ldap', $key, '0') !== '1') {
89
				continue;
90
			}
91
92
			$len = strlen($key) - strlen($referenceConfigkey);
93
			$prefixes[] = substr($key, 0, $len);
94
		}
95
		asort($prefixes);
96
97
		return $prefixes;
98
	}
99
100
	/**
101
	 *
102
	 * determines the host for every configured connection
103
	 *
104
	 * @return array an array with configprefix as keys
105
	 *
106
	 */
107
	public function getServerConfigurationHosts() {
108
		$referenceConfigkey = 'ldap_host';
109
110
		$keys = $this->getServersConfig($referenceConfigkey);
111
112
		$result = [];
113
		foreach ($keys as $key) {
114
			$len = strlen($key) - strlen($referenceConfigkey);
115
			$prefix = substr($key, 0, $len);
116
			$result[$prefix] = $this->config->getAppValue('user_ldap', $key);
117
		}
118
119
		return $result;
120
	}
121
122
	/**
123
	 * return the next available configuration prefix
124
	 *
125
	 * @return string
126
	 */
127
	public function getNextServerConfigurationPrefix() {
128
		$serverConnections = $this->getServerConfigurationPrefixes();
129
130
		if (count($serverConnections) === 0) {
131
			return 's01';
132
		}
133
134
		sort($serverConnections);
135
		$lastKey = array_pop($serverConnections);
136
		$lastNumber = (int)str_replace('s', '', $lastKey);
137
		return 's' . str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT);
138
	}
139
140
	private function getServersConfig($value) {
141
		$regex = '/' . $value . '$/S';
142
143
		$keys = $this->config->getAppKeys('user_ldap');
144
		$result = [];
145
		foreach ($keys as $key) {
146
			if (preg_match($regex, $key) === 1) {
147
				$result[] = $key;
148
			}
149
		}
150
151
		return $result;
152
	}
153
154
	/**
155
	 * deletes a given saved LDAP/AD server configuration.
156
	 *
157
	 * @param string $prefix the configuration prefix of the config to delete
158
	 * @return bool true on success, false otherwise
159
	 */
160
	public function deleteServerConfiguration($prefix) {
161
		if (!in_array($prefix, self::getServerConfigurationPrefixes())) {
0 ignored issues
show
Bug Best Practice introduced by
The method OCA\User_LDAP\Helper::ge...ConfigurationPrefixes() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

161
		if (!in_array($prefix, self::/** @scrutinizer ignore-call */ getServerConfigurationPrefixes())) {
Loading history...
162
			return false;
163
		}
164
165
		$query = $this->connection->getQueryBuilder();
166
		$query->delete('appconfig')
167
			->where($query->expr()->eq('appid', $query->createNamedParameter('user_ldap')))
168
			->andWhere($query->expr()->like('configkey', $query->createNamedParameter((string)$prefix . '%')))
169
			->andWhere($query->expr()->notIn('configkey', $query->createNamedParameter([
170
				'enabled',
171
				'installed_version',
172
				'types',
173
				'bgjUpdateGroupsLastRun',
174
			], IQueryBuilder::PARAM_STR_ARRAY)));
175
176
		if (empty($prefix)) {
177
			$query->andWhere($query->expr()->notLike('configkey', $query->createNamedParameter('s%')));
178
		}
179
180
		$deletedRows = $query->execute();
181
		return $deletedRows !== 0;
182
	}
183
184
	/**
185
	 * checks whether there is one or more disabled LDAP configurations
186
	 */
187
	public function haveDisabledConfigurations(): bool {
188
		$all = $this->getServerConfigurationPrefixes(false);
189
		$active = $this->getServerConfigurationPrefixes(true);
190
191
		return count($all) !== count($active) || count($all) === 0;
192
	}
193
194
	/**
195
	 * extracts the domain from a given URL
196
	 *
197
	 * @param string $url the URL
198
	 * @return string|false domain as string on success, false otherwise
199
	 */
200
	public function getDomainFromURL($url) {
201
		$uinfo = parse_url($url);
202
		if (!is_array($uinfo)) {
0 ignored issues
show
introduced by
The condition is_array($uinfo) is always true.
Loading history...
203
			return false;
204
		}
205
206
		$domain = false;
207
		if (isset($uinfo['host'])) {
208
			$domain = $uinfo['host'];
209
		} elseif (isset($uinfo['path'])) {
210
			$domain = $uinfo['path'];
211
		}
212
213
		return $domain;
214
	}
215
216
	/**
217
	 * sanitizes a DN received from the LDAP server
218
	 *
219
	 * @param array $dn the DN in question
220
	 * @return array|string the sanitized DN
221
	 */
222
	public function sanitizeDN($dn) {
223
		//treating multiple base DNs
224
		if (is_array($dn)) {
0 ignored issues
show
introduced by
The condition is_array($dn) is always true.
Loading history...
225
			$result = [];
226
			foreach ($dn as $singleDN) {
227
				$result[] = $this->sanitizeDN($singleDN);
228
			}
229
			return $result;
230
		}
231
232
		if (!is_string($dn)) {
233
			throw new \LogicException('String expected ' . \gettype($dn) . ' given');
234
		}
235
236
		if (($sanitizedDn = $this->sanitizeDnCache->get($dn)) !== null) {
237
			return $sanitizedDn;
238
		}
239
240
		//OID sometimes gives back DNs with whitespace after the comma
241
		// a la "uid=foo, cn=bar, dn=..." We need to tackle this!
242
		$sanitizedDn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
243
244
		//make comparisons and everything work
245
		$sanitizedDn = mb_strtolower($sanitizedDn, 'UTF-8');
246
247
		//escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
248
		//to use the DN in search filters, \ needs to be escaped to \5c additionally
249
		//to use them in bases, we convert them back to simple backslashes in readAttribute()
250
		$replacements = [
251
			'\,' => '\5c2C',
252
			'\=' => '\5c3D',
253
			'\+' => '\5c2B',
254
			'\<' => '\5c3C',
255
			'\>' => '\5c3E',
256
			'\;' => '\5c3B',
257
			'\"' => '\5c22',
258
			'\#' => '\5c23',
259
			'(' => '\28',
260
			')' => '\29',
261
			'*' => '\2A',
262
		];
263
		$sanitizedDn = str_replace(array_keys($replacements), array_values($replacements), $sanitizedDn);
264
		$this->sanitizeDnCache->set($dn, $sanitizedDn);
265
266
		return $sanitizedDn;
267
	}
268
269
	/**
270
	 * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
271
	 *
272
	 * @param string $dn the DN
273
	 * @return string
274
	 */
275
	public function DNasBaseParameter($dn) {
276
		return str_ireplace('\\5c', '\\', $dn);
277
	}
278
279
	/**
280
	 * listens to a hook thrown by server2server sharing and replaces the given
281
	 * login name by a username, if it matches an LDAP user.
282
	 *
283
	 * @param array $param
284
	 * @throws \Exception
285
	 */
286
	public static function loginName2UserName($param) {
287
		if (!isset($param['uid'])) {
288
			throw new \Exception('key uid is expected to be set in $param');
289
		}
290
291
		$userBackend = \OC::$server->get(User_Proxy::class);
292
		$uid = $userBackend->loginName2UserName($param['uid']);
293
		if ($uid !== false) {
294
			$param['uid'] = $uid;
295
		}
296
	}
297
}
298