Passed
Push — master ( ba155a...1cfa87 )
by Roeland
10:09
created

getServiceNotAvailableResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 4
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\AppFramework\Http;
32
use OC\Authentication\Exceptions\InvalidTokenException;
33
use OC\Authentication\Exceptions\PasswordlessTokenException;
34
use OC\Authentication\Token\INamedToken;
35
use OC\Authentication\Token\IProvider;
36
use OC\Authentication\Token\IToken;
37
use OC\Settings\Activity\Provider;
38
use OCP\Activity\IManager;
39
use OC\Authentication\Token\PublicKeyToken;
40
use OCP\AppFramework\Controller;
41
use OCP\AppFramework\Http\JSONResponse;
42
use OCP\ILogger;
43
use OCP\IRequest;
44
use OCP\ISession;
45
use OCP\Security\ISecureRandom;
46
use OCP\Session\Exceptions\SessionNotAvailableException;
47
48
class AuthSettingsController extends Controller {
49
50
	/** @var IProvider */
51
	private $tokenProvider;
52
53
	/** @var ISession */
54
	private $session;
55
56
	/** @var string */
57
	private $uid;
58
59
	/** @var ISecureRandom */
60
	private $random;
61
62
	/** @var IManager */
63
	private $activityManager;
64
65
	/** @var ILogger */
66
	private $logger;
67
68
	/**
69
	 * @param string $appName
70
	 * @param IRequest $request
71
	 * @param IProvider $tokenProvider
72
	 * @param ISession $session
73
	 * @param ISecureRandom $random
74
	 * @param string|null $userId
75
	 * @param IManager $activityManager
76
	 * @param ILogger $logger
77
	 */
78
	public function __construct(string $appName,
79
								IRequest $request,
80
								IProvider $tokenProvider,
81
								ISession $session,
82
								ISecureRandom $random,
83
								?string $userId,
84
								IManager $activityManager,
85
								ILogger $logger) {
86
		parent::__construct($appName, $request);
87
		$this->tokenProvider = $tokenProvider;
88
		$this->uid = $userId;
89
		$this->session = $session;
90
		$this->random = $random;
91
		$this->activityManager = $activityManager;
92
		$this->logger = $logger;
93
	}
94
95
	/**
96
	 * @NoAdminRequired
97
	 * @NoSubadminRequired
98
	 * @PasswordConfirmationRequired
99
	 *
100
	 * @param string $name
101
	 * @return JSONResponse
102
	 */
103
	public function create($name) {
104
		try {
105
			$sessionId = $this->session->getId();
106
		} catch (SessionNotAvailableException $ex) {
107
			return $this->getServiceNotAvailableResponse();
108
		}
109
110
		try {
111
			$sessionToken = $this->tokenProvider->getToken($sessionId);
112
			$loginName = $sessionToken->getLoginName();
113
			try {
114
				$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
115
			} catch (PasswordlessTokenException $ex) {
116
				$password = null;
117
			}
118
		} catch (InvalidTokenException $ex) {
119
			return $this->getServiceNotAvailableResponse();
120
		}
121
122
		$token = $this->generateRandomDeviceToken();
123
		$deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN);
124
		$tokenData = $deviceToken->jsonSerialize();
125
		$tokenData['canDelete'] = true;
126
		$tokenData['canRename'] = true;
127
128
		$this->publishActivity(Provider::APP_TOKEN_CREATED, $deviceToken->getId(), ['name' => $deviceToken->getName()]);
129
130
		return new JSONResponse([
131
			'token' => $token,
132
			'loginName' => $loginName,
133
			'deviceToken' => $tokenData,
134
		]);
135
	}
136
137
	/**
138
	 * @return JSONResponse
139
	 */
140
	private function getServiceNotAvailableResponse() {
141
		$resp = new JSONResponse();
142
		$resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
143
		return $resp;
144
	}
145
146
	/**
147
	 * Return a 25 digit device password
148
	 *
149
	 * Example: AbCdE-fGhJk-MnPqR-sTwXy-23456
150
	 *
151
	 * @return string
152
	 */
153
	private function generateRandomDeviceToken() {
154
		$groups = [];
155
		for ($i = 0; $i < 5; $i++) {
156
			$groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE);
157
		}
158
		return implode('-', $groups);
159
	}
160
161
	/**
162
	 * @NoAdminRequired
163
	 * @NoSubadminRequired
164
	 *
165
	 * @param int $id
166
	 * @return array|JSONResponse
167
	 */
168
	public function destroy($id) {
169
		try {
170
			$token = $this->findTokenByIdAndUser($id);
171
		} catch (InvalidTokenException $e) {
172
			return new JSONResponse([], Http::STATUS_NOT_FOUND);
173
		}
174
175
		$this->tokenProvider->invalidateTokenById($this->uid, $token->getId());
176
		$this->publishActivity(Provider::APP_TOKEN_DELETED, $token->getId(), ['name' => $token->getName()]);
177
		return [];
178
	}
179
180
	/**
181
	 * @NoAdminRequired
182
	 * @NoSubadminRequired
183
	 *
184
	 * @param int $id
185
	 * @param array $scope
186
	 * @param string $name
187
	 * @return array|JSONResponse
188
	 */
189
	public function update($id, array $scope, string $name) {
190
		try {
191
			$token = $this->findTokenByIdAndUser($id);
192
		} catch (InvalidTokenException $e) {
193
			return new JSONResponse([], Http::STATUS_NOT_FOUND);
194
		}
195
196
		$currentName = $token->getName();
197
198
		if ($scope !== $token->getScopeAsArray()) {
199
			$token->setScope(['filesystem' => $scope['filesystem']]);
200
			$this->publishActivity($scope['filesystem'] ? Provider::APP_TOKEN_FILESYSTEM_GRANTED : Provider::APP_TOKEN_FILESYSTEM_REVOKED, $token->getId(), ['name' => $currentName]);
201
		}
202
203
		if ($token instanceof INamedToken && $name !== $currentName) {
204
			$token->setName($name);
205
			$this->publishActivity(Provider::APP_TOKEN_RENAMED, $token->getId(), ['name' => $currentName, 'newName' => $name]);
206
		}
207
208
		$this->tokenProvider->updateToken($token);
209
		return [];
210
	}
211
212
	/**
213
	 * @param string $subject
214
	 * @param int $id
215
	 * @param array $parameters
216
	 */
217
	private function publishActivity(string $subject, int $id, array $parameters = []): void {
218
		$event = $this->activityManager->generateEvent();
219
		$event->setApp('settings')
220
			->setType('security')
221
			->setAffectedUser($this->uid)
222
			->setAuthor($this->uid)
223
			->setSubject($subject, $parameters)
224
			->setObject('app_token', $id, 'App Password');
225
226
		try {
227
			$this->activityManager->publish($event);
228
		} catch (BadMethodCallException $e) {
229
			$this->logger->warning('could not publish activity');
230
			$this->logger->logException($e);
231
		}
232
	}
233
234
	/**
235
	 * Find a token by given id and check if uid for current session belongs to this token
236
	 *
237
	 * @param int $id
238
	 * @return IToken
239
	 * @throws InvalidTokenException
240
	 * @throws \OC\Authentication\Exceptions\ExpiredTokenException
241
	 */
242
	private function findTokenByIdAndUser(int $id): IToken {
243
		$token = $this->tokenProvider->getTokenById($id);
244
		if ($token->getUID() !== $this->uid) {
245
			throw new InvalidTokenException('This token does not belong to you!');
246
		}
247
		return $token;
248
	}
249
}
250