Passed
Push — master ( e12aaa...884da1 )
by Julius
15:05 queued 13s
created

AppConfig::clearCachedConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
1
<?php
2
/**
3
 * @copyright Copyright (c) 2017, Joas Schilling <[email protected]>
4
 * @copyright Copyright (c) 2016, ownCloud, Inc.
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Jakob Sack <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author michaelletzgus <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program. If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
namespace OC;
34
35
use OC\DB\Connection;
36
use OC\DB\OracleConnection;
37
use OCP\IAppConfig;
38
use OCP\IConfig;
39
40
/**
41
 * This class provides an easy way for apps to store config values in the
42
 * database.
43
 */
44
class AppConfig implements IAppConfig {
45
46
	/** @var array[] */
47
	protected $sensitiveValues = [
48
		'circles' => [
49
			'/^key_pairs$/',
50
			'/^local_gskey$/',
51
		],
52
		'external' => [
53
			'/^sites$/',
54
		],
55
		'integration_discourse' => [
56
			'/^private_key$/',
57
			'/^public_key$/',
58
		],
59
		'integration_dropbox' => [
60
			'/^client_id$/',
61
			'/^client_secret$/',
62
		],
63
		'integration_github' => [
64
			'/^client_id$/',
65
			'/^client_secret$/',
66
		],
67
		'integration_gitlab' => [
68
			'/^client_id$/',
69
			'/^client_secret$/',
70
			'/^oauth_instance_url$/',
71
		],
72
		'integration_google' => [
73
			'/^client_id$/',
74
			'/^client_secret$/',
75
		],
76
		'integration_jira' => [
77
			'/^client_id$/',
78
			'/^client_secret$/',
79
			'/^forced_instance_url$/',
80
		],
81
		'integration_onedrive' => [
82
			'/^client_id$/',
83
			'/^client_secret$/',
84
		],
85
		'integration_openproject' => [
86
			'/^client_id$/',
87
			'/^client_secret$/',
88
			'/^oauth_instance_url$/',
89
		],
90
		'integration_reddit' => [
91
			'/^client_id$/',
92
			'/^client_secret$/',
93
		],
94
		'integration_suitecrm' => [
95
			'/^client_id$/',
96
			'/^client_secret$/',
97
			'/^oauth_instance_url$/',
98
		],
99
		'integration_twitter' => [
100
			'/^consumer_key$/',
101
			'/^consumer_secret$/',
102
			'/^followed_user$/',
103
		],
104
		'integration_zammad' => [
105
			'/^client_id$/',
106
			'/^client_secret$/',
107
			'/^oauth_instance_url$/',
108
		],
109
		'notify_push' => [
110
			'/^cookie$/',
111
		],
112
		'spreed' => [
113
			'/^bridge_bot_password$/',
114
			'/^hosted-signaling-server-(.*)$/',
115
			'/^signaling_servers$/',
116
			'/^signaling_ticket_secret$/',
117
			'/^signaling_token_privkey_(.*)$/',
118
			'/^signaling_token_pubkey_(.*)$/',
119
			'/^sip_bridge_dialin_info$/',
120
			'/^sip_bridge_shared_secret$/',
121
			'/^stun_servers$/',
122
			'/^turn_servers$/',
123
			'/^turn_server_secret$/',
124
		],
125
		'support' => [
126
			'/^last_response$/',
127
			'/^potential_subscription_key$/',
128
			'/^subscription_key$/',
129
		],
130
		'theming' => [
131
			'/^imprintUrl$/',
132
			'/^privacyUrl$/',
133
			'/^slogan$/',
134
			'/^url$/',
135
		],
136
		'user_ldap' => [
137
			'/^(s..)?ldap_agent_password$/',
138
		],
139
		'user_saml' => [
140
			'/^idp-x509cert$/',
141
		],
142
	];
143
144
	/** @var Connection */
145
	protected $conn;
146
147
	/** @var array[] */
148
	private $cache = [];
149
150
	/** @var bool */
151
	private $configLoaded = false;
152
153
	/**
154
	 * @param Connection $conn
155
	 */
156
	public function __construct(Connection $conn) {
157
		$this->conn = $conn;
158
	}
159
160
	/**
161
	 * @param string $app
162
	 * @return array
163
	 */
164
	private function getAppValues($app) {
165
		$this->loadConfigValues();
166
167
		if (isset($this->cache[$app])) {
168
			return $this->cache[$app];
169
		}
170
171
		return [];
172
	}
173
174
	/**
175
	 * Get all apps using the config
176
	 *
177
	 * @return array an array of app ids
178
	 *
179
	 * This function returns a list of all apps that have at least one
180
	 * entry in the appconfig table.
181
	 */
182
	public function getApps() {
183
		$this->loadConfigValues();
184
185
		return $this->getSortedKeys($this->cache);
186
	}
187
188
	/**
189
	 * Get the available keys for an app
190
	 *
191
	 * @param string $app the app we are looking for
192
	 * @return array an array of key names
193
	 *
194
	 * This function gets all keys of an app. Please note that the values are
195
	 * not returned.
196
	 */
197
	public function getKeys($app) {
198
		$this->loadConfigValues();
199
200
		if (isset($this->cache[$app])) {
201
			return $this->getSortedKeys($this->cache[$app]);
202
		}
203
204
		return [];
205
	}
206
207
	public function getSortedKeys($data) {
208
		$keys = array_keys($data);
209
		sort($keys);
210
		return $keys;
211
	}
212
213
	/**
214
	 * Gets the config value
215
	 *
216
	 * @param string $app app
217
	 * @param string $key key
218
	 * @param string $default = null, default value if the key does not exist
219
	 * @return string the value or $default
220
	 *
221
	 * This function gets a value from the appconfig table. If the key does
222
	 * not exist the default value will be returned
223
	 */
224
	public function getValue($app, $key, $default = null) {
225
		$this->loadConfigValues();
226
227
		if ($this->hasKey($app, $key)) {
228
			return $this->cache[$app][$key];
229
		}
230
231
		return $default;
232
	}
233
234
	/**
235
	 * check if a key is set in the appconfig
236
	 *
237
	 * @param string $app
238
	 * @param string $key
239
	 * @return bool
240
	 */
241
	public function hasKey($app, $key) {
242
		$this->loadConfigValues();
243
244
		return isset($this->cache[$app][$key]);
245
	}
246
247
	/**
248
	 * Sets a value. If the key did not exist before it will be created.
249
	 *
250
	 * @param string $app app
251
	 * @param string $key key
252
	 * @param string|float|int $value value
253
	 * @return bool True if the value was inserted or updated, false if the value was the same
254
	 */
255
	public function setValue($app, $key, $value) {
256
		if (!$this->hasKey($app, $key)) {
257
			$inserted = (bool) $this->conn->insertIfNotExist('*PREFIX*appconfig', [
258
				'appid' => $app,
259
				'configkey' => $key,
260
				'configvalue' => $value,
261
			], [
262
				'appid',
263
				'configkey',
264
			]);
265
266
			if ($inserted) {
267
				if (!isset($this->cache[$app])) {
268
					$this->cache[$app] = [];
269
				}
270
271
				$this->cache[$app][$key] = $value;
272
				return true;
273
			}
274
		}
275
276
		$sql = $this->conn->getQueryBuilder();
277
		$sql->update('appconfig')
278
			->set('configvalue', $sql->createNamedParameter($value))
279
			->where($sql->expr()->eq('appid', $sql->createNamedParameter($app)))
280
			->andWhere($sql->expr()->eq('configkey', $sql->createNamedParameter($key)));
281
282
		/*
283
		 * Only limit to the existing value for non-Oracle DBs:
284
		 * http://docs.oracle.com/cd/E11882_01/server.112/e26088/conditions002.htm#i1033286
285
		 * > Large objects (LOBs) are not supported in comparison conditions.
286
		 */
287
		if (!($this->conn instanceof OracleConnection)) {
288
289
			/*
290
			 * Only update the value when it is not the same
291
			 * Note that NULL requires some special handling. Since comparing
292
			 * against null can have special results.
293
			 */
294
295
			if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
296
				$sql->andWhere(
297
					$sql->expr()->isNotNull('configvalue')
298
				);
299
			} else {
300
				$sql->andWhere(
301
					$sql->expr()->orX(
302
						$sql->expr()->isNull('configvalue'),
303
						$sql->expr()->neq('configvalue', $sql->createNamedParameter($value))
304
					)
305
				);
306
			}
307
		}
308
309
		$changedRow = (bool) $sql->execute();
310
311
		$this->cache[$app][$key] = $value;
312
313
		return $changedRow;
314
	}
315
316
	/**
317
	 * Deletes a key
318
	 *
319
	 * @param string $app app
320
	 * @param string $key key
321
	 * @return boolean
322
	 */
323
	public function deleteKey($app, $key) {
324
		$this->loadConfigValues();
325
326
		$sql = $this->conn->getQueryBuilder();
327
		$sql->delete('appconfig')
328
			->where($sql->expr()->eq('appid', $sql->createParameter('app')))
329
			->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey')))
330
			->setParameter('app', $app)
331
			->setParameter('configkey', $key);
332
		$sql->execute();
333
334
		unset($this->cache[$app][$key]);
335
		return false;
336
	}
337
338
	/**
339
	 * Remove app from appconfig
340
	 *
341
	 * @param string $app app
342
	 * @return boolean
343
	 *
344
	 * Removes all keys in appconfig belonging to the app.
345
	 */
346
	public function deleteApp($app) {
347
		$this->loadConfigValues();
348
349
		$sql = $this->conn->getQueryBuilder();
350
		$sql->delete('appconfig')
351
			->where($sql->expr()->eq('appid', $sql->createParameter('app')))
352
			->setParameter('app', $app);
353
		$sql->execute();
354
355
		unset($this->cache[$app]);
356
		return false;
357
	}
358
359
	/**
360
	 * get multiple values, either the app or key can be used as wildcard by setting it to false
361
	 *
362
	 * @param string|false $app
363
	 * @param string|false $key
364
	 * @return array|false
365
	 */
366
	public function getValues($app, $key) {
367
		if (($app !== false) === ($key !== false)) {
368
			return false;
369
		}
370
371
		if ($key === false) {
372
			return $this->getAppValues($app);
373
		} else {
374
			$appIds = $this->getApps();
375
			$values = array_map(function ($appId) use ($key) {
376
				return isset($this->cache[$appId][$key]) ? $this->cache[$appId][$key] : null;
377
			}, $appIds);
378
			$result = array_combine($appIds, $values);
379
380
			return array_filter($result);
381
		}
382
	}
383
384
	/**
385
	 * get all values of the app or and filters out sensitive data
386
	 *
387
	 * @param string $app
388
	 * @return array
389
	 */
390
	public function getFilteredValues($app) {
391
		$values = $this->getValues($app, false);
392
393
		if (isset($this->sensitiveValues[$app])) {
394
			foreach ($this->sensitiveValues[$app] as $sensitiveKeyExp) {
395
				$sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
396
				foreach ($sensitiveKeys as $sensitiveKey) {
397
					$values[$sensitiveKey] = IConfig::SENSITIVE_VALUE;
398
				}
399
			}
400
		}
401
402
		return $values;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $values could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
403
	}
404
405
	/**
406
	 * Load all the app config values
407
	 */
408
	protected function loadConfigValues() {
409
		if ($this->configLoaded) {
410
			return;
411
		}
412
413
		$this->cache = [];
414
415
		$sql = $this->conn->getQueryBuilder();
416
		$sql->select('*')
417
			->from('appconfig');
418
		$result = $sql->execute();
419
420
		// we are going to store the result in memory anyway
421
		$rows = $result->fetchAll();
422
		foreach ($rows as $row) {
423
			if (!isset($this->cache[$row['appid']])) {
424
				$this->cache[(string)$row['appid']] = [];
425
			}
426
427
			$this->cache[(string)$row['appid']][(string)$row['configkey']] = (string)$row['configvalue'];
428
		}
429
		$result->closeCursor();
430
431
		$this->configLoaded = true;
432
	}
433
434
435
	/**
436
	 * Clear all the cached app config values
437
	 * New cache will be generated next time a config value is retrieved
438
	 */
439
	public function clearCachedConfig(): void {
440
		$this->configLoaded = false;
441
	}
442
}
443