Completed
Push — master ( fef361...5daa4f )
by Blizzz
69:27
created

VerifyUserData   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 256
Duplicated Lines 5.08 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 13
loc 256
rs 9.92
c 0
b 0
f 0
wmc 31
lcom 1
cbo 11

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
A execute() 13 13 3
B run() 0 22 6
A verifyWebsite() 0 37 5
B verifyViaLookupServer() 0 39 7
A queryLookupServer() 0 23 5
A reAddJob() 0 12 1
A shouldRun() 0 4 1
A resetVerificationState() 0 8 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Bjoern Schiessle <[email protected]>
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Lukas Reschke <[email protected]>
7
 * @author Patrik Kernstock <[email protected]>
8
 *
9
 * @license GNU AGPL version 3 or any later version
10
 *
11
 * This program is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License as
13
 * published by the Free Software Foundation, either version 3 of the
14
 * License, or (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23
 *
24
 */
25
26
27
namespace OC\Settings\BackgroundJobs;
28
29
30
use OC\Accounts\AccountManager;
31
use OC\BackgroundJob\Job;
32
use OC\BackgroundJob\JobList;
33
use OCP\AppFramework\Http;
34
use OCP\BackgroundJob\IJobList;
35
use OCP\Http\Client\IClientService;
36
use OCP\IConfig;
37
use OCP\ILogger;
38
use OCP\IUserManager;
39
40
class VerifyUserData extends Job {
41
42
	/** @var  bool */
43
	private $retainJob = true;
44
45
	/** @var int max number of attempts to send the request */
46
	private $maxTry = 24;
47
48
	/** @var int how much time should be between two tries (1 hour) */
49
	private $interval = 3600;
50
51
	/** @var AccountManager */
52
	private $accountManager;
53
54
	/** @var IUserManager */
55
	private $userManager;
56
57
	/** @var IClientService */
58
	private $httpClientService;
59
60
	/** @var ILogger */
61
	private $logger;
62
63
	/** @var string */
64
	private $lookupServerUrl;
65
66
	/**
67
	 * VerifyUserData constructor.
68
	 *
69
	 * @param AccountManager $accountManager
70
	 * @param IUserManager $userManager
71
	 * @param IClientService $clientService
72
	 * @param ILogger $logger
73
	 * @param IConfig $config
74
	 */
75
	public function __construct(AccountManager $accountManager,
76
								IUserManager $userManager,
77
								IClientService $clientService,
78
								ILogger $logger,
79
								IConfig $config
80
	) {
81
		$this->accountManager = $accountManager;
82
		$this->userManager = $userManager;
83
		$this->httpClientService = $clientService;
84
		$this->logger = $logger;
85
86
		$lookupServerUrl = $config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com');
87
		$this->lookupServerUrl = rtrim($lookupServerUrl, '/');
88
	}
89
90
	/**
91
	 * run the job, then remove it from the jobList
92
	 *
93
	 * @param JobList $jobList
94
	 * @param ILogger|null $logger
95
	 */
96 View Code Duplication
	public function execute($jobList, ILogger $logger = null) {
97
98
		if ($this->shouldRun($this->argument)) {
99
			parent::execute($jobList, $logger);
100
			$jobList->remove($this, $this->argument);
101
			if ($this->retainJob) {
102
				$this->reAddJob($jobList, $this->argument);
103
			} else {
104
				$this->resetVerificationState();
105
			}
106
		}
107
108
	}
109
110
	protected function run($argument) {
111
112
		$try = (int)$argument['try'] + 1;
113
114
		switch($argument['type']) {
115
			case AccountManager::PROPERTY_WEBSITE:
116
				$result = $this->verifyWebsite($argument);
117
				break;
118
			case AccountManager::PROPERTY_TWITTER:
119
			case AccountManager::PROPERTY_EMAIL:
120
				$result = $this->verifyViaLookupServer($argument, $argument['type']);
121
				break;
122
			default:
123
				// no valid type given, no need to retry
124
				$this->logger->error($argument['type'] . ' is no valid type for user account data.');
125
				$result = true;
126
		}
127
128
		if ($result === true || $try > $this->maxTry) {
129
			$this->retainJob = false;
130
		}
131
	}
132
133
	/**
134
	 * verify web page
135
	 *
136
	 * @param array $argument
137
	 * @return bool true if we could check the verification code, otherwise false
138
	 */
139
	protected function verifyWebsite(array $argument) {
140
141
		$result = false;
142
143
		$url = rtrim($argument['data'], '/') . '/.well-known/' . 'CloudIdVerificationCode.txt';
144
145
		$client = $this->httpClientService->newClient();
146
		try {
147
			$response = $client->get($url);
148
		} catch (\Exception $e) {
149
			return false;
150
		}
151
152
		if ($response->getStatusCode() === Http::STATUS_OK) {
153
			$result = true;
154
			$publishedCode = $response->getBody();
155
			// remove new lines and spaces
156
			$publishedCodeSanitized = trim(preg_replace('/\s\s+/', ' ', $publishedCode));
157
			$user = $this->userManager->get($argument['uid']);
158
			// we don't check a valid user -> give up
159
			if ($user === null) {
160
				$this->logger->error($argument['uid'] . ' doesn\'t exist, can\'t verify user data.');
161
				return $result;
162
			}
163
			$userData = $this->accountManager->getUser($user);
164
165
			if ($publishedCodeSanitized === $argument['verificationCode']) {
166
				$userData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFIED;
167
			} else {
168
				$userData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::NOT_VERIFIED;
169
			}
170
171
			$this->accountManager->updateUser($user, $userData);
172
		}
173
174
		return $result;
175
	}
176
177
	/**
178
	 * verify email address
179
	 *
180
	 * @param array $argument
181
	 * @param string $dataType
182
	 * @return bool true if we could check the verification code, otherwise false
183
	 */
184
	protected function verifyViaLookupServer(array $argument, $dataType) {
185
		if(empty($this->lookupServerUrl) || $this->config->getSystemValue('has_internet_connection', true) === false) {
0 ignored issues
show
Bug introduced by
The property config does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
186
			return false;
187
		}
188
189
		$user = $this->userManager->get($argument['uid']);
190
191
		// we don't check a valid user -> give up
192
		if ($user === null) {
193
			$this->logger->info($argument['uid'] . ' doesn\'t exist, can\'t verify user data.');
194
			return true;
195
		}
196
197
		$localUserData = $this->accountManager->getUser($user);
198
		$cloudId = $user->getCloudId();
199
200
		// ask lookup-server for user data
201
		$lookupServerData = $this->queryLookupServer($cloudId);
202
203
		// for some reasons we couldn't read any data from the lookup server, try again later
204
		if (empty($lookupServerData)) {
205
			return false;
206
		}
207
208
		// lookup server has verification data for wrong user data (e.g. email address), try again later
209
		if ($lookupServerData[$dataType]['value'] !== $argument['data']) {
210
			return false;
211
		}
212
213
		// lookup server hasn't verified the email address so far, try again later
214
		if ($lookupServerData[$dataType]['verified'] === AccountManager::NOT_VERIFIED) {
215
			return false;
216
		}
217
218
		$localUserData[$dataType]['verified'] = AccountManager::VERIFIED;
219
		$this->accountManager->updateUser($user, $localUserData);
220
221
		return true;
222
	}
223
224
	/**
225
	 * @param string $cloudId
226
	 * @return array
227
	 */
228
	protected function queryLookupServer($cloudId) {
229
		try {
230
			$client = $this->httpClientService->newClient();
231
			$response = $client->get(
232
				$this->lookupServerUrl . '/users?search=' . urlencode($cloudId) . '&exactCloudId=1',
233
				[
234
					'timeout' => 10,
235
					'connect_timeout' => 3,
236
				]
237
			);
238
239
			$body = json_decode($response->getBody(), true);
240
241
			if (is_array($body) && isset($body['federationId']) && $body['federationId'] === $cloudId) {
242
				return $body;
243
			}
244
245
		} catch (\Exception $e) {
246
			// do nothing, we will just re-try later
247
		}
248
249
		return [];
250
	}
251
252
	/**
253
	 * re-add background job with new arguments
254
	 *
255
	 * @param IJobList $jobList
256
	 * @param array $argument
257
	 */
258
	protected function reAddJob(IJobList $jobList, array $argument) {
259
		$jobList->add(VerifyUserData::class,
260
			[
261
				'verificationCode' => $argument['verificationCode'],
262
				'data' => $argument['data'],
263
				'type' => $argument['type'],
264
				'uid' => $argument['uid'],
265
				'try' => (int)$argument['try'] + 1,
266
				'lastRun' => time()
267
			]
268
		);
269
	}
270
271
	/**
272
	 * test if it is time for the next run
273
	 *
274
	 * @param array $argument
275
	 * @return bool
276
	 */
277
	protected function shouldRun(array $argument) {
278
		$lastRun = (int)$argument['lastRun'];
279
		return ((time() - $lastRun) > $this->interval);
280
	}
281
282
283
	/**
284
	 * reset verification state after max tries are reached
285
	 */
286
	protected function resetVerificationState() {
287
		$user = $this->userManager->get($this->argument['uid']);
288
		if ($user !== null) {
289
			$accountData = $this->accountManager->getUser($user);
290
			$accountData[$this->argument['type']]['verified'] = AccountManager::NOT_VERIFIED;
291
			$this->accountManager->updateUser($user, $accountData);
292
		}
293
	}
294
295
}
296