Completed
Pull Request — master (#3869)
by Morris
11:40
created

VerifyUserData::shouldRun()   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 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Bjoern Schiessle <[email protected]>
4
 *
5
 * @license GNU AGPL version 3 or any later version
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as
9
 * published by the Free Software Foundation, either version 3 of the
10
 * License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 *
20
 */
21
22
23
namespace OC\Settings\BackgroundJobs;
24
25
26
use OC\Accounts\AccountManager;
27
use OC\BackgroundJob\Job;
28
use OC\BackgroundJob\JobList;
29
use OCP\AppFramework\Http;
30
use OCP\BackgroundJob\IJobList;
31
use OCP\Http\Client\IClientService;
32
use OCP\IConfig;
33
use OCP\ILogger;
34
use OCP\IUserManager;
35
36
class VerifyUserData extends Job {
37
38
	/** @var  bool */
39
	private $retainJob = true;
40
41
	/** @var int max number of attempts to send the request */
42
	private $maxTry = 24;
43
44
	/** @var int how much time should be between two tries (1 hour) */
45
	private $interval = 3600;
46
47
	/** @var AccountManager */
48
	private $accountManager;
49
50
	/** @var IUserManager */
51
	private $userManager;
52
53
	/** @var IClientService */
54
	private $httpClientService;
55
56
	/** @var ILogger */
57
	private $logger;
58
59
	/** @var string */
60
	private $lookupServerUrl;
61
62
	/**
63
	 * VerifyUserData constructor.
64
	 *
65
	 * @param AccountManager $accountManager
66
	 * @param IUserManager $userManager
67
	 * @param IClientService $clientService
68
	 * @param ILogger $logger
69
	 * @param IConfig $config
70
	 */
71
	public function __construct(AccountManager $accountManager,
72
								IUserManager $userManager,
73
								IClientService $clientService,
74
								ILogger $logger,
75
								IConfig $config
76
	) {
77
		$this->accountManager = $accountManager;
78
		$this->userManager = $userManager;
79
		$this->httpClientService = $clientService;
80
		$this->logger = $logger;
81
82
		$lookupServerUrl = $config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com');
83
		$this->lookupServerUrl = rtrim($lookupServerUrl, '/');
84
	}
85
86
	/**
87
	 * run the job, then remove it from the jobList
88
	 *
89
	 * @param JobList $jobList
90
	 * @param ILogger $logger
91
	 */
92 View Code Duplication
	public function execute($jobList, ILogger $logger = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
93
94
		if ($this->shouldRun($this->argument)) {
95
			parent::execute($jobList, $logger);
96
			$jobList->remove($this, $this->argument);
97
			if ($this->retainJob) {
98
				$this->reAddJob($jobList, $this->argument);
99
			}
100
		}
101
102
	}
103
104
	protected function run($argument) {
105
106
		$try = (int)$argument['try'] + 1;
107
108
		switch($argument['type']) {
109
			case AccountManager::PROPERTY_WEBSITE:
110
				$result = $this->verifyWebsite($argument);
111
				break;
112
			case AccountManager::PROPERTY_TWITTER:
113
			case AccountManager::PROPERTY_EMAIL:
114
				$result = $this->verifyViaLookupServer($argument, $argument['type']);
115
				break;
116
			default:
117
				// no valid type given, no need to retry
118
				$this->logger->error($argument['type'] . ' is no valid type for user account data.');
119
				$result = true;
120
		}
121
122
		if ($result === true || $try > $this->maxTry) {
123
			$this->retainJob = false;
124
		}
125
	}
126
127
	/**
128
	 * verify web page
129
	 *
130
	 * @param array $argument
131
	 * @return bool true if we could check the verification code, otherwise false
132
	 */
133
	protected function verifyWebsite(array $argument) {
134
135
		$result = false;
136
137
		$url = rtrim($argument['data'], '/') . '/.well-known/' . 'CloudIdVerificationCode.txt';
138
139
		$client = $this->httpClientService->newClient();
140
		try {
141
			$response = $client->get($url);
142
		} catch (\Exception $e) {
143
			return false;
144
		}
145
146
		if ($response->getStatusCode() === Http::STATUS_OK) {
147
			$result = true;
148
			$publishedCode = $response->getBody();
149
			// remove new lines and spaces
150
			$publishedCodeSanitized = trim(preg_replace('/\s\s+/', ' ', $publishedCode));
151
			$user = $this->userManager->get($argument['uid']);
152
			// we don't check a valid user -> give up
153
			if ($user === null) {
154
				$this->logger->error($argument['uid'] . ' doesn\'t exist, can\'t verify user data.');
155
				return $result;
156
			}
157
			$userData = $this->accountManager->getUser($user);
158
159
			if ($publishedCodeSanitized === $argument['verificationCode']) {
160
				$userData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFIED;
161
			} else {
162
				$userData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::NOT_VERIFIED;
163
			}
164
165
			$this->accountManager->updateUser($user, $userData);
166
		}
167
168
		return $result;
169
	}
170
171
	/**
172
	 * verify email address
173
	 *
174
	 * @param array $argument
175
	 * @param string $dataType
176
	 * @return bool true if we could check the verification code, otherwise false
177
	 */
178
	protected function verifyViaLookupServer(array $argument, $dataType) {
179
180
		$user = $this->userManager->get($argument['uid']);
181
182
		// we don't check a valid user -> give up
183
		if ($user === null) {
184
			$this->logger->error($argument['uid'] . ' doesn\'t exist, can\'t verify user data.');
185
			return true;
186
		}
187
188
		$localUserData = $this->accountManager->getUser($user);
189
		$cloudId = $user->getCloudId();
190
191
		// ask lookup-server for user data
192
		$lookupServerData = $this->queryLookupServer($cloudId);
193
194
		// for some reasons we couldn't read any data from the lookup server, try again later
195
		if (empty($lookupServerData)) {
196
			return false;
197
		}
198
199
		// lookup server has verification data for wrong user data (e.g. email address), try again later
200
		if ($lookupServerData[$dataType]['value'] !== $argument['data']) {
201
			return false;
202
		}
203
204
		// lookup server hasn't verified the email address so far, try again later
205
		if ($lookupServerData[$dataType]['verified'] === AccountManager::NOT_VERIFIED) {
206
			return false;
207
		}
208
209
		$localUserData[$dataType]['verified'] = AccountManager::VERIFIED;
210
		$this->accountManager->updateUser($user, $localUserData);
211
212
		return true;
213
	}
214
215
	/**
216
	 * @param string $cloudId
217
	 * @return array
218
	 */
219
	protected function queryLookupServer($cloudId) {
220
		try {
221
			$client = $this->httpClientService->newClient();
222
			$response = $client->get(
223
				$this->lookupServerUrl . '/users?search=' . urlencode($cloudId) . '&exactCloudId=1',
224
				[
225
					'timeout' => 10,
226
					'connect_timeout' => 3,
227
				]
228
			);
229
230
			$body = json_decode($response->getBody(), true);
231
232
			if ($body['federationId'] === $cloudId) {
233
				return $body;
234
			}
235
236
		} catch (\Exception $e) {
237
			// do nothing, we will just re-try later
238
		}
239
240
		return [];
241
	}
242
243
	/**
244
	 * re-add background job with new arguments
245
	 *
246
	 * @param IJobList $jobList
247
	 * @param array $argument
248
	 */
249 View Code Duplication
	protected function reAddJob(IJobList $jobList, array $argument) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250
		$jobList->add('OC\Settings\BackgroundJobs\VerifyUserData',
251
			[
252
				'verificationCode' => $argument['verificationCode'],
253
				'data' => $argument['data'],
254
				'type' => $argument['type'],
255
				'uid' => $argument['uid'],
256
				'try' => (int)$argument['try'] + 1,
257
				'lastRun' => time()
258
			]
259
		);
260
	}
261
262
	/**
263
	 * test if it is time for the next run
264
	 *
265
	 * @param array $argument
266
	 * @return bool
267
	 */
268
	protected function shouldRun(array $argument) {
269
		$lastRun = (int)$argument['lastRun'];
270
		return ((time() - $lastRun) > $this->interval);
271
	}
272
273
}
274