1<?php
2
3declare(strict_types=1);
4
5
6/**
7 * Circles - Bring cloud-users closer together.
8 *
9 * This file is licensed under the Affero General Public License version 3 or
10 * later. See the COPYING file.
11 *
12 * @author Maxence Lange <maxence@artificial-owl.com>
13 * @copyright 2021
14 * @license GNU AGPL version 3 or any later version
15 *
16 * This program is free software: you can redistribute it and/or modify
17 * it under the terms of the GNU Affero General Public License as
18 * published by the Free Software Foundation, either version 3 of the
19 * License, or (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 * GNU Affero General Public License for more details.
25 *
26 * You should have received a copy of the GNU Affero General Public License
27 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28 *
29 */
30
31
32namespace OCA\Circles\Command;
33
34use ArtificialOwl\MySmallPhpTools\Console\Nextcloud\nc22\NC22InteractiveShell;
35use ArtificialOwl\MySmallPhpTools\Exceptions\InvalidItemException;
36use ArtificialOwl\MySmallPhpTools\IInteractiveShellClient;
37use ArtificialOwl\MySmallPhpTools\Model\Nextcloud\nc22\NC22InteractiveShellSession;
38use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Deserialize;
39use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools;
40use OC\Core\Command\Base;
41use OCA\Circles\Exceptions\InitiatorNotFoundException;
42use OCA\Circles\Exceptions\UnknownInterfaceException;
43use OCA\Circles\Model\Circle;
44use OCA\Circles\Model\Federated\RemoteInstance;
45use OCA\Circles\Model\FederatedUser;
46use OCA\Circles\Model\Member;
47use OCA\Circles\Model\Membership;
48use OCA\Circles\Model\Probes\CircleProbe;
49use OCA\Circles\Model\Report;
50use OCA\Circles\Service\CircleService;
51use OCA\Circles\Service\ConfigService;
52use OCA\Circles\Service\FederatedUserService;
53use OCA\Circles\Service\InterfaceService;
54use OCA\Circles\Service\MemberService;
55use Symfony\Component\Console\Input\InputInterface;
56use Symfony\Component\Console\Input\InputOption;
57use Symfony\Component\Console\Output\ConsoleOutput;
58use Symfony\Component\Console\Output\OutputInterface;
59
60/**
61 * Class CirclesReport
62 *
63 * @package OCA\Circles\Command
64 */
65class CirclesReport extends Base implements IInteractiveShellClient {
66	use TNC22Deserialize;
67	use TArrayTools;
68
69
70	/** @var FederatedUserService */
71	private $federatedUserService;
72
73	/** @var CircleService */
74	private $circleService;
75
76	/** @var MemberService */
77	private $memberService;
78
79	/** @var InterfaceService */
80	private $interfaceService;
81
82	/** @var ConfigService */
83	private $configService;
84
85
86	/** @var OutputInterface */
87	private $output;
88
89	/** @var Report */
90	private $report;
91
92
93	/**
94	 * CirclesReport constructor.
95	 *
96	 * @param FederatedUserService $federatedUserService
97	 * @param CircleService $circleService
98	 * @param MemberService $memberService
99	 * @param ConfigService $configService
100	 */
101	public function __construct(
102		FederatedUserService $federatedUserService,
103		CircleService $circleService,
104		MemberService $memberService,
105		InterfaceService $interfaceService,
106		ConfigService $configService
107	) {
108		parent::__construct();
109
110		$this->federatedUserService = $federatedUserService;
111		$this->circleService = $circleService;
112		$this->memberService = $memberService;
113		$this->interfaceService = $interfaceService;
114		$this->configService = $configService;
115	}
116
117
118	/**
119	 *
120	 */
121	protected function configure() {
122		parent::configure();
123		$this->setName('circles:report')
124			 ->setDescription('Read and write obfuscated report')
125			 ->addOption('local', '', InputOption::VALUE_NONE, 'Use local report')
126			 ->addOption('read', '', InputOption::VALUE_REQUIRED, 'File containing the report to read', '');
127	}
128
129
130	/**
131	 * @param InputInterface $input
132	 * @param OutputInterface $output
133	 *
134	 * @return int
135	 * @throws InvalidItemException
136	 * @throws InitiatorNotFoundException
137	 */
138	protected function execute(InputInterface $input, OutputInterface $output): int {
139		$filename = $input->getOption('read');
140		$local = $input->getOption('local');
141		$this->output = $output;
142
143		$report = null;
144		if ($filename === '' || $local) {
145			$report = $this->generateReport();
146			if (!$local) {
147				$this->output->writeln(json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
148
149				return 0;
150			}
151		}
152
153		if ($filename !== '') {
154			/** @var Report $report */
155			$data = file_get_contents($filename);
156			$report = $this->deserialize(json_decode($data, true), Report::class);
157		}
158
159		if (!is_null($report)) {
160			$this->readReport($input, $report);
161		}
162
163		return 0;
164	}
165
166
167	/**
168	 * @throws InitiatorNotFoundException
169	 * @throws UnknownInterfaceException
170	 */
171	private function generateReport(): Report {
172		$report = new Report();
173		$report->setSource($this->interfaceService->getLocalInstance());
174		$this->federatedUserService->bypassCurrentUserCondition(true);
175
176		$probe = new CircleProbe();
177		$probe->includeSystemCircles();
178		$raw = $this->circleService->getCircles($probe);
179
180		$circles = [];
181		foreach ($raw as $circle) {
182			$circle->getMembers();
183
184			$circles[] = $this->obfuscateCircle($circle);
185		}
186
187		$report->setCircles($circles);
188
189		return $report;
190	}
191
192
193	/**
194	 * @param InputInterface $input
195	 * @param Report $report
196	 */
197	private function readReport(InputInterface $input, Report $report) {
198		$output = new ConsoleOutput();
199		$this->output = $output->section();
200		$this->report = $report;
201
202		$interactiveShell = new NC22InteractiveShell($this, $input, $this);
203		$commands = [
204			'circles.list',
205			'circles.delete.#circleId',
206			'members.list.#circleId',
207			'members.details.#memberId',
208			'remoteInstance.list'
209		];
210
211		$interactiveShell->setCommands($commands);
212
213		$interactiveShell->run();
214	}
215
216
217	/**
218	 * @param string $source
219	 * @param string $field
220	 *
221	 * @return string[]
222	 */
223	public function fillCommandList(string $source, string $field): array {
224		echo $source . ' ' . $field . "\n";
225
226		return ['abcd', 'abdde', 'erfg'];
227	}
228
229
230	/**
231	 * @param string $command
232	 */
233	public function manageCommand(string $command): void {
234//		echo $command . "\n";
235	}
236
237
238	/**
239	 * @param Circle $circle
240	 *
241	 * @return Circle
242	 */
243	private function obfuscateCircle(Circle $circle): Circle {
244		$singleId = $this->obfuscateId($circle->getSingleId());
245		$circle->setSingleId($singleId)
246			   ->setName($singleId)
247			   ->setDisplayName($singleId)
248			   ->setDescription('')
249			   ->setCreation(0);
250
251		if ($circle->hasOwner()) {
252			$circle->setOwner($this->obfuscateMember($circle->getOwner()));
253		}
254
255		if ($circle->hasInitiator()) {
256			$circle->setInitiator($this->obfuscateMember($circle->getInitiator()));
257		}
258
259		if ($circle->hasMembers()) {
260			$members = [];
261			foreach ($circle->getMembers() as $member) {
262				$members[] = $this->obfuscateMember($member);
263			}
264			$circle->setMembers($members);
265		}
266
267		if ($circle->hasMemberships()) {
268			$memberships = [];
269			foreach ($circle->getMemberships() as $membership) {
270				$memberships[] = $this->obfuscateMembership($membership);
271			}
272			$circle->setMemberships($memberships);
273		}
274
275
276		return $circle;
277	}
278
279
280	/**
281	 * @param Member $member
282	 *
283	 * @return Member
284	 */
285	private function obfuscateMember(Member $member): Member {
286		$memberId = $this->obfuscateId($member->getId());
287		$singleId = $this->obfuscateId($member->getSingleId());
288		$circleId = $this->obfuscateId($member->getCircleId());
289
290		$member->setSingleId($singleId)
291			   ->setCircleId($circleId)
292			   ->setId($memberId)
293			   ->setUserId($singleId)
294			   ->setDisplayName($singleId)
295			   ->setDisplayUpdate(0)
296			   ->setNotes('')
297			   ->setContactId('')
298			   ->setContactMeta('')
299			   ->setJoined(0);
300
301		if ($member->hasCircle()) {
302			$member->setCircle($this->obfuscateCircle($member->getCircle()));
303		}
304
305		if ($member->hasBasedOn()) {
306			$member->setBasedOn($this->obfuscateCircle($member->getBasedOn()));
307		}
308
309		if ($member->hasInheritedBy()) {
310			$member->setInheritedBy($this->obfuscateFederatedUser($member->getInheritedBy()));
311		}
312
313		if ($member->hasInheritanceFrom()) {
314			$member->setInheritanceFrom($this->obfuscateMember($member->getInheritanceFrom()));
315		}
316
317		if ($member->hasRemoteInstance()) {
318			$member->setRemoteInstance($this->obfuscateRemoteInstance($member->getRemoteInstance()));
319		}
320
321		if ($member->hasMemberships()) {
322			$memberships = [];
323			foreach ($member->getMemberships() as $membership) {
324				$memberships[] = $this->obfuscateMembership($membership);
325			}
326			$member->setMemberships($memberships);
327		}
328
329		return $member;
330	}
331
332
333	/**
334	 * @param FederatedUser $federatedUser
335	 *
336	 * @return FederatedUser
337	 */
338	private function obfuscateFederatedUser(FederatedUser $federatedUser): FederatedUser {
339		$singleId = $this->obfuscateId($federatedUser->getSingleId());
340		$federatedUser->setSingleId($singleId)
341					  ->setUserId($singleId);
342
343		if ($federatedUser->hasBasedOn()) {
344			$federatedUser->setBasedOn($this->obfuscateCircle($federatedUser->getBasedOn()));
345		}
346
347		// what was that for ?
348//		if ($federatedUser->hasLink()) {
349//			$federatedUser->setLink($this->obfuscateMembership($federatedUser->getLink()));
350//		}
351
352		if ($federatedUser->hasMemberships()) {
353			$memberships = [];
354			foreach ($federatedUser->getMemberships() as $membership) {
355				$memberships[] = $this->obfuscateMembership($membership);
356			}
357			$federatedUser->setMemberships($memberships);
358		}
359
360		return $federatedUser;
361	}
362
363
364	/**
365	 * @param Membership $membership
366	 *
367	 * @return Membership
368	 */
369	private function obfuscateMembership(Membership $membership): Membership {
370		$membership->setSingleId($this->obfuscateId($membership->getSingleId()));
371		$membership->setCircleId($this->obfuscateId($membership->getCircleId()));
372		$membership->setInheritanceFirst($this->obfuscateId($membership->getInheritanceFirst()));
373		$membership->setInheritanceLast($this->obfuscateId($membership->getInheritanceLast()));
374
375		$path = [];
376		foreach ($membership->getInheritancePath() as $item) {
377			$path[] = $this->obfuscateId($item);
378		}
379		$membership->setInheritancePath($path);
380
381		return $membership;
382	}
383
384
385	/**
386	 * @param RemoteInstance $remoteInstance
387	 *
388	 * @return RemoteInstance
389	 */
390	private function obfuscateRemoteInstance(RemoteInstance $remoteInstance): RemoteInstance {
391		return $remoteInstance;
392	}
393
394
395	/**
396	 * @param string $id
397	 *
398	 * @return string
399	 */
400	private function obfuscateId(string $id): string {
401		return substr($id, 0, 5) . '.' . md5(substr($id, 5));
402	}
403
404
405	/**
406	 * @param NC22InteractiveShellSession $session
407	 */
408	public function onNewPrompt(NC22InteractiveShellSession $session): void {
409		$prompt =
410			'Circles Report [<info>' . $this->report->getSource() . '</info>]:<comment>%PATH%</comment>';
411
412		$commands = [];
413		if ($session->getData()->g('currentStatus') === 'write') {
414			$commands[] = 'cancel';
415			$commands[] = 'write';
416			$prompt .= '<error>#</error> ';
417		} else {
418			$commands[] = 'edit';
419			$prompt .= '$ ';
420		}
421
422		$session->setGlobalCommands($commands)
423				->setPrompt($prompt);
424	}
425
426
427	/**
428	 * @param NC22InteractiveShellSession $session
429	 * @param $command
430	 */
431	public function onNewCommand(NC22InteractiveShellSession $session, $command): void {
432		echo $session->getPath();
433	}
434}
435