1<?php
2
3declare(strict_types=1);
4
5/**
6 * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
7 *
8 * @author Joas Schilling <coding@schilljs.com>
9 * @author Roeland Jago Douma <roeland@famdouma.nl>
10 *
11 * @license GNU AGPL version 3 or any later version
12 *
13 * This program is free software: you can redistribute it and/or modify
14 * it under the terms of the GNU Affero General Public License as
15 * published by the Free Software Foundation, either version 3 of the
16 * License, or (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Affero General Public License for more details.
22 *
23 * You should have received a copy of the GNU Affero General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 *
26 */
27namespace OCA\Provisioning_API\Controller;
28
29use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
30use OCP\AppFramework\Http;
31use OCP\AppFramework\Http\DataResponse;
32use OCP\AppFramework\OCSController;
33use OCP\IAppConfig;
34use OCP\IConfig;
35use OCP\IGroupManager;
36use OCP\IL10N;
37use OCP\IRequest;
38use OCP\IUser;
39use OCP\IUserSession;
40use OCP\Settings\IDelegatedSettings;
41use OCP\Settings\IManager;
42
43class AppConfigController extends OCSController {
44
45	/** @var IConfig */
46	protected $config;
47
48	/** @var IAppConfig */
49	protected $appConfig;
50
51	/** @var IUserSession */
52	private $userSession;
53
54	/** @var IL10N */
55	private $l10n;
56
57	/** @var IGroupManager */
58	private $groupManager;
59
60	/** @var IManager */
61	private $settingManager;
62
63	/**
64	 * @param string $appName
65	 * @param IRequest $request
66	 * @param IConfig $config
67	 * @param IAppConfig $appConfig
68	 */
69	public function __construct(string $appName,
70								IRequest $request,
71								IConfig $config,
72								IAppConfig $appConfig,
73								IUserSession $userSession,
74								IL10N $l10n,
75								IGroupManager $groupManager,
76								IManager $settingManager) {
77		parent::__construct($appName, $request);
78		$this->config = $config;
79		$this->appConfig = $appConfig;
80		$this->userSession = $userSession;
81		$this->l10n = $l10n;
82		$this->groupManager = $groupManager;
83		$this->settingManager = $settingManager;
84	}
85
86	/**
87	 * @return DataResponse
88	 */
89	public function getApps(): DataResponse {
90		return new DataResponse([
91			'data' => $this->appConfig->getApps(),
92		]);
93	}
94
95	/**
96	 * @param string $app
97	 * @return DataResponse
98	 */
99	public function getKeys(string $app): DataResponse {
100		try {
101			$this->verifyAppId($app);
102		} catch (\InvalidArgumentException $e) {
103			return new DataResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_FORBIDDEN);
104		}
105		return new DataResponse([
106			'data' => $this->config->getAppKeys($app),
107		]);
108	}
109
110	/**
111	 * @param string $app
112	 * @param string $key
113	 * @param string $defaultValue
114	 * @return DataResponse
115	 */
116	public function getValue(string $app, string $key, string $defaultValue = ''): DataResponse {
117		try {
118			$this->verifyAppId($app);
119		} catch (\InvalidArgumentException $e) {
120			return new DataResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_FORBIDDEN);
121		}
122		return new DataResponse([
123			'data' => $this->config->getAppValue($app, $key, $defaultValue),
124		]);
125	}
126
127	/**
128	 * @PasswordConfirmationRequired
129	 * @NoSubAdminRequired
130	 * @NoAdminRequired
131	 * @param string $app
132	 * @param string $key
133	 * @param string $value
134	 * @return DataResponse
135	 */
136	public function setValue(string $app, string $key, string $value): DataResponse {
137		$user = $this->userSession->getUser();
138		if ($user === null) {
139			throw new \Exception("User is not logged in."); // Should not happen, since method is guarded by middleware
140		}
141
142		if (!$this->isAllowedToChangedKey($user, $app, $key)) {
143			throw new NotAdminException($this->l10n->t('Logged in user must be an administrator or have authorization to edit this setting.'));
144		}
145
146		try {
147			$this->verifyAppId($app);
148			$this->verifyConfigKey($app, $key, $value);
149		} catch (\InvalidArgumentException $e) {
150			return new DataResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_FORBIDDEN);
151		}
152
153		$this->config->setAppValue($app, $key, $value);
154		return new DataResponse();
155	}
156
157	/**
158	 * @PasswordConfirmationRequired
159	 * @param string $app
160	 * @param string $key
161	 * @return DataResponse
162	 */
163	public function deleteKey(string $app, string $key): DataResponse {
164		try {
165			$this->verifyAppId($app);
166			$this->verifyConfigKey($app, $key, '');
167		} catch (\InvalidArgumentException $e) {
168			return new DataResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_FORBIDDEN);
169		}
170
171		$this->config->deleteAppValue($app, $key);
172		return new DataResponse();
173	}
174
175	/**
176	 * @param string $app
177	 * @throws \InvalidArgumentException
178	 */
179	protected function verifyAppId(string $app) {
180		if (\OC_App::cleanAppId($app) !== $app) {
181			throw new \InvalidArgumentException('Invalid app id given');
182		}
183	}
184
185	/**
186	 * @param string $app
187	 * @param string $key
188	 * @param string $value
189	 * @throws \InvalidArgumentException
190	 */
191	protected function verifyConfigKey(string $app, string $key, string $value) {
192		if (in_array($key, ['installed_version', 'enabled', 'types'])) {
193			throw new \InvalidArgumentException('The given key can not be set');
194		}
195
196		if ($app === 'core' && $key === 'encryption_enabled' && $value !== 'yes') {
197			throw new \InvalidArgumentException('The given key can not be set');
198		}
199
200		if ($app === 'core' && (strpos($key, 'public_') === 0 || strpos($key, 'remote_') === 0)) {
201			throw new \InvalidArgumentException('The given key can not be set');
202		}
203
204		if ($app === 'files'
205			&& $key === 'default_quota'
206			&& $value === 'none'
207			&& $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '0') {
208			throw new \InvalidArgumentException('The given key can not be set, unlimited quota is forbidden on this instance');
209		}
210	}
211
212	private function isAllowedToChangedKey(IUser $user, string $app, string $key): bool {
213		// Admin right verification
214		$isAdmin = $this->groupManager->isAdmin($user->getUID());
215		if ($isAdmin) {
216			return true;
217		}
218
219		$settings = $this->settingManager->getAllAllowedAdminSettings($user);
220		foreach ($settings as $setting) {
221			if (!($setting instanceof IDelegatedSettings)) {
222				continue;
223			}
224			$allowedKeys = $setting->getAuthorizedAppConfig();
225			if (!array_key_exists($app, $allowedKeys)) {
226				continue;
227			}
228			foreach ($allowedKeys[$app] as $regex) {
229				if ($regex === $key
230					|| (str_starts_with($regex, '/') && preg_match($regex, $key) === 1)) {
231					return true;
232				}
233			}
234		}
235		return false;
236	}
237}
238