Passed
Push — master ( 50dbde...ac9260 )
by Morris
12:33
created

AuthSettingsController::findTokenByIdAndUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Fabrizio Steiner <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Marcel Waldvogel <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
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, version 3,
24
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
28
namespace OC\Settings\Controller;
29
30
use BadMethodCallException;
31
use OC\Authentication\Exceptions\InvalidTokenException;
32
use OC\Authentication\Exceptions\PasswordlessTokenException;
33
use OC\Authentication\Exceptions\WipeTokenException;
34
use OC\Authentication\Token\INamedToken;
35
use OC\Authentication\Token\IProvider;
36
use OC\Authentication\Token\IToken;
37
use OC\Authentication\Token\IWipeableToken;
38
use OC\Settings\Activity\Provider;
39
use OCP\Activity\IManager;
40
use OCP\AppFramework\Controller;
41
use OCP\AppFramework\Http;
42
use OCP\AppFramework\Http\JSONResponse;
43
use OCP\ILogger;
44
use OCP\IRequest;
45
use OCP\ISession;
46
use OCP\Security\ISecureRandom;
47
use OCP\Session\Exceptions\SessionNotAvailableException;
48
49
class AuthSettingsController extends Controller {
50
51
	/** @var IProvider */
52
	private $tokenProvider;
53
54
	/** @var ISession */
55
	private $session;
56
57
	/** @var string */
58
	private $uid;
59
60
	/** @var ISecureRandom */
61
	private $random;
62
63
	/** @var IManager */
64
	private $activityManager;
65
66
	/** @var ILogger */
67
	private $logger;
68
69
	/**
70
	 * @param string $appName
71
	 * @param IRequest $request
72
	 * @param IProvider $tokenProvider
73
	 * @param ISession $session
74
	 * @param ISecureRandom $random
75
	 * @param string|null $userId
76
	 * @param IManager $activityManager
77
	 * @param ILogger $logger
78
	 */
79
	public function __construct(string $appName,
80
								IRequest $request,
81
								IProvider $tokenProvider,
82
								ISession $session,
83
								ISecureRandom $random,
84
								?string $userId,
85
								IManager $activityManager,
86
								ILogger $logger) {
87
		parent::__construct($appName, $request);
88
		$this->tokenProvider = $tokenProvider;
89
		$this->uid = $userId;
90
		$this->session = $session;
91
		$this->random = $random;
92
		$this->activityManager = $activityManager;
93
		$this->logger = $logger;
94
	}
95
96
	/**
97
	 * @NoAdminRequired
98
	 * @NoSubadminRequired
99
	 * @PasswordConfirmationRequired
100
	 *
101
	 * @param string $name
102
	 * @return JSONResponse
103
	 */
104
	public function create($name) {
105
		try {
106
			$sessionId = $this->session->getId();
107
		} catch (SessionNotAvailableException $ex) {
108
			return $this->getServiceNotAvailableResponse();
109
		}
110
111
		try {
112
			$sessionToken = $this->tokenProvider->getToken($sessionId);
113
			$loginName = $sessionToken->getLoginName();
114
			try {
115
				$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
116
			} catch (PasswordlessTokenException $ex) {
117
				$password = null;
118
			}
119
		} catch (InvalidTokenException $ex) {
120
			return $this->getServiceNotAvailableResponse();
121
		}
122
123
		$token = $this->generateRandomDeviceToken();
124
		$deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN);
125
		$tokenData = $deviceToken->jsonSerialize();
126
		$tokenData['canDelete'] = true;
127
		$tokenData['canRename'] = true;
128
129
		$this->publishActivity(Provider::APP_TOKEN_CREATED, $deviceToken->getId(), ['name' => $deviceToken->getName()]);
130
131
		return new JSONResponse([
132
			'token' => $token,
133
			'loginName' => $loginName,
134
			'deviceToken' => $tokenData,
135
		]);
136
	}
137
138
	/**
139
	 * @return JSONResponse
140
	 */
141
	private function getServiceNotAvailableResponse() {
142
		$resp = new JSONResponse();
143
		$resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
144
		return $resp;
145
	}
146
147
	/**
148
	 * Return a 25 digit device password
149
	 *
150
	 * Example: AbCdE-fGhJk-MnPqR-sTwXy-23456
151
	 *
152
	 * @return string
153
	 */
154
	private function generateRandomDeviceToken() {
155
		$groups = [];
156
		for ($i = 0; $i < 5; $i++) {
157
			$groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE);
158
		}
159
		return implode('-', $groups);
160
	}
161
162
	/**
163
	 * @NoAdminRequired
164
	 * @NoSubadminRequired
165
	 *
166
	 * @param int $id
167
	 * @return array|JSONResponse
168
	 */
169
	public function destroy($id) {
170
		try {
171
			$token = $this->findTokenByIdAndUser($id);
172
		} catch (WipeTokenException $e) {
173
			//continue as we can destroy tokens in wipe
174
			$token = $e->getToken();
175
		} catch (InvalidTokenException $e) {
176
			return new JSONResponse([], Http::STATUS_NOT_FOUND);
177
		}
178
179
		$this->tokenProvider->invalidateTokenById($this->uid, $token->getId());
180
		$this->publishActivity(Provider::APP_TOKEN_DELETED, $token->getId(), ['name' => $token->getName()]);
181
		return [];
182
	}
183
184
	/**
185
	 * @NoAdminRequired
186
	 * @NoSubadminRequired
187
	 *
188
	 * @param int $id
189
	 * @param array $scope
190
	 * @param string $name
191
	 * @return array|JSONResponse
192
	 */
193
	public function update($id, array $scope, string $name) {
194
		try {
195
			$token = $this->findTokenByIdAndUser($id);
196
		} catch (InvalidTokenException $e) {
197
			return new JSONResponse([], Http::STATUS_NOT_FOUND);
198
		}
199
200
		$currentName = $token->getName();
201
202
		if ($scope !== $token->getScopeAsArray()) {
203
			$token->setScope(['filesystem' => $scope['filesystem']]);
204
			$this->publishActivity($scope['filesystem'] ? Provider::APP_TOKEN_FILESYSTEM_GRANTED : Provider::APP_TOKEN_FILESYSTEM_REVOKED, $token->getId(), ['name' => $currentName]);
205
		}
206
207
		if ($token instanceof INamedToken && $name !== $currentName) {
208
			$token->setName($name);
209
			$this->publishActivity(Provider::APP_TOKEN_RENAMED, $token->getId(), ['name' => $currentName, 'newName' => $name]);
210
		}
211
212
		$this->tokenProvider->updateToken($token);
213
		return [];
214
	}
215
216
	/**
217
	 * @param string $subject
218
	 * @param int $id
219
	 * @param array $parameters
220
	 */
221
	private function publishActivity(string $subject, int $id, array $parameters = []): void {
222
		$event = $this->activityManager->generateEvent();
223
		$event->setApp('settings')
224
			->setType('security')
225
			->setAffectedUser($this->uid)
226
			->setAuthor($this->uid)
227
			->setSubject($subject, $parameters)
228
			->setObject('app_token', $id, 'App Password');
229
230
		try {
231
			$this->activityManager->publish($event);
232
		} catch (BadMethodCallException $e) {
233
			$this->logger->warning('could not publish activity');
234
			$this->logger->logException($e);
235
		}
236
	}
237
238
	/**
239
	 * Find a token by given id and check if uid for current session belongs to this token
240
	 *
241
	 * @param int $id
242
	 * @return IToken
243
	 * @throws InvalidTokenException
244
	 * @throws \OC\Authentication\Exceptions\ExpiredTokenException
245
	 */
246
	private function findTokenByIdAndUser(int $id): IToken {
247
		$token = $this->tokenProvider->getTokenById($id);
248
		if ($token->getUID() !== $this->uid) {
249
			throw new InvalidTokenException('This token does not belong to you!');
250
		}
251
		return $token;
252
	}
253
254
	/**
255
	 * @NoAdminRequired
256
	 * @NoSubadminRequired
257
	 * @PasswordConfirmationRequired
258
	 *
259
	 * @param int $id
260
	 * @return JSONResponse
261
	 * @throws InvalidTokenException
262
	 * @throws \OC\Authentication\Exceptions\ExpiredTokenException
263
	 */
264
	public function wipe(int $id): JSONResponse {
265
		$token = $this->tokenProvider->getTokenById($id);
266
267
		if (!($token instanceof IWipeableToken)) {
268
			return new JSONResponse([], Http::STATUS_BAD_REQUEST);
269
		}
270
271
		$token->wipe();
272
		$this->tokenProvider->updateToken($token);
273
274
		return new JSONResponse([]);
275
	}
276
}
277