Passed
Push — master ( 65fd87...291b97 )
by Pauli
03:21
created

SettingController::getUserKeys()   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 declare(strict_types=1);
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Morris Jobke <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Morris Jobke 2013, 2014
12
 * @copyright Pauli Järvinen 2017 - 2023
13
 */
14
15
namespace OCA\Music\Controller;
16
17
use OCP\AppFramework\Controller;
18
use OCP\AppFramework\Http;
19
use OCP\AppFramework\Http\JSONResponse;
20
use OCP\IRequest;
21
use OCP\IURLGenerator;
22
use OCP\Security\ISecureRandom;
23
24
use OCA\Music\AppFramework\Core\Logger;
25
use OCA\Music\Db\AmpacheUserMapper;
26
use OCA\Music\Http\ErrorResponse;
27
use OCA\Music\Utility\AppInfo;
28
use OCA\Music\Utility\LibrarySettings;
29
use OCA\Music\Utility\Scanner;
30
use OCA\Music\Utility\Util;
31
32
class SettingController extends Controller {
33
	const DEFAULT_PASSWORD_LENGTH = 10;
34
	/* Character set without look-alike characters. Similar but even more stripped set would be found
35
	 * on Nextcloud as ISecureRandom::CHAR_HUMAN_READABLE but that's not availalbe on ownCloud. */
36
	const API_KEY_CHARSET = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
37
38
	private $ampacheUserMapper;
39
	private $scanner;
40
	private $userId;
41
	private $librarySettings;
42
	private $secureRandom;
43
	private $urlGenerator;
44
	private $logger;
45
46
	public function __construct(string $appName,
47
								IRequest $request,
48
								AmpacheUserMapper $ampacheUserMapper,
49
								Scanner $scanner,
50
								?string $userId,
51
								LibrarySettings $librarySettings,
52
								ISecureRandom $secureRandom,
53
								IURLGenerator $urlGenerator,
54
								Logger $logger) {
55
		parent::__construct($appName, $request);
56
57
		$this->ampacheUserMapper = $ampacheUserMapper;
58
		$this->scanner = $scanner;
59
		$this->userId = $userId ?? ''; // ensure non-null to satisfy Scrutinizer; the null case should happen only when the user has already logged out
60
		$this->librarySettings = $librarySettings;
61
		$this->secureRandom = $secureRandom;
62
		$this->urlGenerator = $urlGenerator;
63
		$this->logger = $logger;
64
	}
65
66
	/**
67
	 * @NoAdminRequired
68
	 * @NoCSRFRequired
69
	 * @UseSession to keep the session reserved while execution in progress
70
	 */
71
	public function userPath(string $value) {
72
		$prevPath = $this->librarySettings->getPath($this->userId);
73
		$success = $this->librarySettings->setPath($this->userId, $value);
74
75
		if ($success) {
76
			$this->scanner->updatePath($prevPath, $value, $this->userId);
77
		}
78
79
		return new JSONResponse(['success' => $success]);
80
	}
81
82
	/**
83
	 * @NoAdminRequired
84
	 * @NoCSRFRequired
85
	 */
86
	public function userExcludedPaths(array $value) {
87
		$success = $this->librarySettings->setExcludedPaths($this->userId, $value);
88
		return new JSONResponse(['success' => $success]);
89
	}
90
91
	/**
92
	 * @NoAdminRequired
93
	 * @NoCSRFRequired
94
	 */
95
	public function enableScanMetadata(bool $value) {
96
		$this->librarySettings->setScanMetadataEnabled($this->userId, $value);
97
		return new JSONResponse(['success' => true]);
98
	}
99
100
	/**
101
	 * @NoAdminRequired
102
	 * @NoCSRFRequired
103
	 */
104
	public function ignoredArticles(array $value) {
105
		$this->librarySettings->setIgnoredArticles($this->userId, $value);
106
		return new JSONResponse(['success' => true]);
107
	}
108
109
	/**
110
	 * @NoAdminRequired
111
	 * @NoCSRFRequired
112
	 */
113
	public function getAll() {
114
		return [
115
			'path' => $this->librarySettings->getPath($this->userId),
116
			'excludedPaths' => $this->librarySettings->getExcludedPaths($this->userId),
117
			'scanMetadata' => $this->librarySettings->getScanMetadataEnabled($this->userId),
118
			'ignoredArticles' => $this->librarySettings->getIgnoredArticles($this->userId),
119
			'ampacheUrl' => $this->getAmpacheUrl(),
120
			'subsonicUrl' => $this->getSubsonicUrl(),
121
			'ampacheKeys' => $this->getUserKeys(),
122
			'appVersion' => AppInfo::getVersion(),
123
			'user' => $this->userId
124
		];
125
	}
126
127
	/**
128
	 * @NoAdminRequired
129
	 * @NoCSRFRequired
130
	 */
131
	public function getUserKeys() {
132
		return $this->ampacheUserMapper->getAll($this->userId);
133
	}
134
135
	private function getAmpacheUrl() {
136
		return \str_replace('/server/xml.server.php', '',
137
				$this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('music.ampache.xmlApi')));
138
	}
139
140
	private function getSubsonicUrl() {
141
		return \str_replace('/rest/dummy', '',
142
				$this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute(
143
						'music.subsonic.handleRequest', ['method' => 'dummy'])));
144
	}
145
146
	private function storeUserKey($description, $password) {
147
		$hash = \hash('sha256', $password);
148
		$description = Util::truncate($description, 64); // some DB setups can't truncate automatically to column max size
149
		return $this->ampacheUserMapper->addUserKey($this->userId, $hash, $description);
150
	}
151
152
	/**
153
	 * @NoAdminRequired
154
	 */
155
	public function createUserKey($length, $description) {
156
		if ($length == null || $length < self::DEFAULT_PASSWORD_LENGTH) {
157
			$length = self::DEFAULT_PASSWORD_LENGTH;
158
		}
159
160
		$password = $this->secureRandom->generate($length, self::API_KEY_CHARSET);
161
162
		$id = $this->storeUserKey($description, $password);
163
164
		if ($id === null) {
165
			return new ErrorResponse(Http::STATUS_INTERNAL_SERVER_ERROR, 'Error while saving the credentials');
166
		}
167
168
		return new JSONResponse(['id' => $id, 'password' => $password, 'description' => $description], Http::STATUS_CREATED);
169
	}
170
171
	/**
172
	 * The CORS-version of the key creation function is targeted for external clients. We need separate function
173
	 * because the CORS middleware blocks the normal internal access on Nextcloud versions older than 25 as well
174
	 * as on ownCloud 10.0, at least (but not on OC 10.4+).
175
	 *
176
	 * @NoAdminRequired
177
	 * @CORS
178
	 */
179
	public function createUserKeyCors($length, $description) {
180
		return $this->createUserKey($length, $description);
181
	}
182
183
	/**
184
	 * @NoAdminRequired
185
	 * @NoCSRFRequired
186
	 */
187
	public function removeUserKey($id) {
188
		$this->ampacheUserMapper->removeUserKey($this->userId, (int)$id);
189
		return new JSONResponse(['success' => true]);
190
	}
191
}
192