Passed
Push — master ( b249c1...ba03c1 )
by Roeland
11:56
created

AuthSettingsController::publishActivity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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