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