Completed
Push — master ( 103214...84d122 )
by Thomas
10:00
created

SyncBackend::execute()   D

Complexity

Conditions 14
Paths 33

Size

Total Lines 137
Code Lines 101

Duplication

Lines 42
Ratio 30.66 %

Importance

Changes 0
Metric Value
cc 14
eloc 101
nc 33
nop 2
dl 42
loc 137
rs 4.9516
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Thomas Müller <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2017, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
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, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OC\Core\Command\User;
23
24
25
use OC\User\AccountMapper;
26
use OC\User\SyncService;
27
use OCP\IConfig;
28
use OCP\ILogger;
29
use OCP\IUser;
30
use OCP\IUserManager;
31
use OCP\UserInterface;
32
use Symfony\Component\Console\Command\Command;
33
use Symfony\Component\Console\Helper\ProgressBar;
34
use Symfony\Component\Console\Question\ChoiceQuestion;
35
use Symfony\Component\Console\Input\InputInterface;
36
use Symfony\Component\Console\Input\InputOption;
37
use Symfony\Component\Console\Output\OutputInterface;
38
use Symfony\Component\Console\Input\InputArgument;
39
40
class SyncBackend extends Command {
41
42
	/** @var AccountMapper */
43
	protected $accountMapper;
44
	/** @var IConfig */
45
	private $config;
46
	/** @var IUserManager */
47
	private $userManager;
48
	/** @var ILogger */
49
	private $logger;
50
51
	/**
52
	 * @param AccountMapper $accountMapper
53
	 * @param IConfig $config
54
	 * @param IUserManager $userManager
55
	 * @param ILogger $logger
56
	 */
57 View Code Duplication
	public function __construct(AccountMapper $accountMapper,
58
								IConfig $config,
59
								IUserManager $userManager,
60
								ILogger $logger) {
61
		parent::__construct();
62
		$this->accountMapper = $accountMapper;
63
		$this->config = $config;
64
		$this->userManager = $userManager;
65
		$this->logger = $logger;
66
	}
67
68
	protected function configure() {
69
		$this
70
			->setName('user:sync')
71
			->setDescription('synchronize users from a given backend to the accounts table')
72
			->addArgument(
73
				'backend-class',
74
				InputArgument::OPTIONAL,
75
				'The php class name - e.g. "OCA\User_LDAP\User_LDAP". Please wrap the class name into double quotes. You can use the option --list to list all known backend classes'
76
			)
77
			->addOption('list', 'l', InputOption::VALUE_NONE, 'list all known backend classes')
78
			->addOption('missing-account-action', 'm', InputOption::VALUE_REQUIRED, 'action to do if the account isn\'t connected to a backend any longer. Options are "disable" and "remove". Use quotes. Note that removing the account will also remove the stored data and files for that account');
79
	}
80
81
	protected function execute(InputInterface $input, OutputInterface $output) {
82
		if ($input->getOption('list')) {
83
			$backends = $this->userManager->getBackends();
84
			foreach ($backends as $backend) {
85
				$output->writeln(get_class($backend));
86
			}
87
			return 0;
88
		}
89
		$backendClassName = $input->getArgument('backend-class');
90
		if (is_null($backendClassName)) {
91
			$output->writeln("<error>No backend class name given. Please run ./occ help user:sync to understand how this command works.</error>");
92
			return 1;
93
		}
94
		$backend = $this->getBackend($backendClassName);
95
		if (is_null($backend)) {
96
			$output->writeln("<error>The backend <$backendClassName> does not exist. Did you miss to enable the app?</error>");
97
			return 1;
98
		}
99
100
		$validActions = ['disable', 'remove'];
101
102
		if ($input->getOption('missing-account-action') !== null) {
103
			$missingAccountsAction = $input->getOption('missing-account-action');
104
			if (!in_array($missingAccountsAction, $validActions, true)) {
105
				$output->writeln("<error>Unknown action. Choose between \"disable\" or \"remove\"</error>");
106
				return 1;
107
			}
108
		} else {
109
			// ask (if possible) how to handle missing accounts. Disable the accounts by default.
110
			$helper = $this->getHelper('question');
111
			$question = new ChoiceQuestion(
112
					'If unknown users are found, what do you want to do with their accounts? (removing the account will also remove its data)',
113
					array_merge($validActions, ['ask later']),
114
					0
115
			);
116
			$missingAccountsAction = $helper->ask($input, $output, $question);
117
		}
118
119
		$syncService = new SyncService($this->accountMapper, $backend, $this->config, $this->logger);
120
121
		// insert/update known users
122
		$output->writeln("Insert new and update existing users ...");
123
		$p = new ProgressBar($output);
124
		$max = null;
125
		if ($backend->implementsActions(\OC_User_Backend::COUNT_USERS)) {
126
			$max = $backend->countUsers();
127
		}
128
		$p->start($max);
129
		$syncService->run(function () use ($p) {
130
			$p->advance();
131
		});
132
		$p->finish();
133
		$output->writeln('');
134
		$output->writeln('');
135
136
		// analyse unknown users
137
		$output->writeln("Analyse unknown users ...");
138
		$p = new ProgressBar($output);
139
		$toBeDeleted = $syncService->getNoLongerExistingUsers(function () use ($p) {
140
			$p->advance();
141
		});
142
		$p->finish();
143
		$output->writeln('');
144
		$output->writeln('');
145
146
		if (empty($toBeDeleted)) {
147
			$output->writeln("No unknown users have been detected.");
148
		} else {
149
			$output->writeln("Following users are no longer known with the connected backend.");
150
			switch ($missingAccountsAction) {
151 View Code Duplication
				case 'disable':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
152
					$output->writeln("Proceeding to disable the accounts");
153
					$this->doActionForAccountUids($toBeDeleted,
154
							function($uid, IUser $ac) use ($output) {
155
								$ac->setEnabled(false);
156
								$output->writeln($uid);
157
							},
158
							function($uid) use ($output) {
159
								$output->writeln($uid . " (unknown account for the user)");
160
							});
161
					break;
162 View Code Duplication
				case 'remove':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
163
					$output->writeln("Proceeding to remove the accounts");
164
					$this->doActionForAccountUids($toBeDeleted,
165
							function($uid, IUser $ac) use ($output) {
166
								$ac->delete();
167
								$output->writeln($uid);
168
							},
169
							function($uid) use ($output) {
170
								$output->writeln($uid . " (unknown account for the user)");
171
							});
172
					break;
173
				case 'ask later':
174
					$output->writeln("listing the unknown accounts");
175
					$this->doActionForAccountUids($toBeDeleted,
176
							function($uid) use ($output) {
177
								$output->writeln($uid);
178
							},
179
							function($uid) use ($output) {
180
								$output->writeln($uid . " (unknown account for the user)");
181
							});
182
					// overwriting variables!
183
					$helper = $this->getHelper('question');
184
					$question = new ChoiceQuestion(
185
							'What do you want to do with their accounts? (removing the account will also remove its data)',
186
							$validActions,
187
							0
188
					);
189
					$missingAccountsAction2 = $helper->ask($input, $output, $question);
190
					switch ($missingAccountsAction2) {
191
						// if "nothing" is selected, just ignore and finish
192 View Code Duplication
						case 'disable':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
193
							$output->writeln("Proceeding to disable the accounts");
194
							$this->doActionForAccountUids($toBeDeleted,
195
									function($uid, IUser $ac) {
196
										$ac->setEnabled(false);
197
									},
198
									function($uid) use ($output) {
199
										$output->writeln($uid . " (unknown account for the user)");
200
									});
201
							break;
202 View Code Duplication
						case 'remove':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
203
							$output->writeln("Proceeding to remove the accounts");
204
							$this->doActionForAccountUids($toBeDeleted,
205
									function($uid, IUser $ac) {
206
										$ac->delete();
207
									},
208
									function($uid) use ($output) {
209
										$output->writeln($uid . " (unknown account for the user)");
210
									});
211
							break;
212
					}
213
					break;
214
			}
215
		}
216
		return 0;
217
	}
218
219
	/**
220
	 * @param $backend
221
	 * @return null|UserInterface
222
	 */
223
	private function getBackend($backend) {
224
		$backends = $this->userManager->getBackends();
225
		$match = array_filter($backends, function ($b) use ($backend) {
226
			return get_class($b) === $backend;
227
		});
228
		if (empty($match)) {
229
			return null;
230
		}
231
		return array_pop($match);
232
	}
233
234
	/**
235
	 * @param array $uids a list of uids to the the action
236
	 * @param callable $callbackExists the callback used if the account for the uid exists. The
237
	 * uid and the specific account will be passed as parameter to the callback in that order
238
	 * @param callable $callbackMissing the callback used if the account doesn't exists. The uid (not
239
	 * the account) will be passed as parameter to the callback
240
	 */
241
	private function doActionForAccountUids(array $uids, callable $callbackExists, callable $callbackMissing = null) {
242
		foreach ($uids as $u) {
243
			$userAccount = $this->userManager->get($u);
244
			if ($userAccount === null) {
245
				$callbackMissing($u);
246
			} else {
247
				$callbackExists($u, $userAccount);
248
			}
249
		}
250
	}
251
}
252