migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,896 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use Exception;
use OC;
use OC\AppConfig;
use OC\Core\Command\Base;
use OCA\Circles\AppInfo\Application;
use OCA\Circles\AppInfo\Capabilities;
use OCA\Circles\Exceptions\FederatedEventException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\UnknownInterfaceException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\FederatedItems\LoopbackTest;
use OCA\Circles\Model\Federated\FederatedEvent;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedEventService;
use OCA\Circles\Service\InterfaceService;
use OCA\Circles\Service\RemoteService;
use OCA\Circles\Service\RemoteStreamService;
use OCA\Circles\Service\RemoteUpstreamService;
use OCA\Circles\Tools\Exceptions\RequestNetworkException;
use OCA\Circles\Tools\Exceptions\SignatoryException;
use OCA\Circles\Tools\Model\NCRequest;
use OCA\Circles\Tools\Model\Request;
use OCA\Circles\Tools\Model\SimpleDataStore;
use OCA\Circles\Tools\Traits\TArrayTools;
use OCA\Circles\Tools\Traits\TNCRequest;
use OCA\Circles\Tools\Traits\TStringTools;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
/**
* Class CirclesCheck
*
* @package OCA\Circles\Command
*/
class CirclesCheck extends Base {
use TStringTools;
use TArrayTools;
use TNCRequest;
public static $checks = [
'internal',
'frontal',
'loopback'
];
/** @var Capabilities */
private $capabilities;
/** @var InterfaceService */
private $interfaceService;
/** @var FederatedEventService */
private $federatedEventService;
/** @var RemoteService */
private $remoteService;
/** @var RemoteStreamService */
private $remoteStreamService;
/** @var RemoteUpstreamService */
private $remoteUpstreamService;
/** @var ConfigService */
private $configService;
/** @var array */
private $sessions = [];
/**
* CirclesCheck constructor.
*
* @param Capabilities $capabilities
* @param InterfaceService $interfaceService
* @param FederatedEventService $federatedEventService
* @param RemoteService $remoteService
* @param RemoteStreamService $remoteStreamService
* @param RemoteUpstreamService $remoteUpstreamService
* @param ConfigService $configService
*/
public function __construct(
Capabilities $capabilities,
InterfaceService $interfaceService,
FederatedEventService $federatedEventService,
RemoteService $remoteService,
RemoteStreamService $remoteStreamService,
RemoteUpstreamService $remoteUpstreamService,
ConfigService $configService
) {
parent::__construct();
$this->capabilities = $capabilities;
$this->interfaceService = $interfaceService;
$this->federatedEventService = $federatedEventService;
$this->remoteService = $remoteService;
$this->remoteStreamService = $remoteStreamService;
$this->remoteUpstreamService = $remoteUpstreamService;
$this->configService = $configService;
}
protected function configure() {
parent::configure();
$this->setName('circles:check')
->setDescription('Checking your configuration')
->addOption('capabilities', '', InputOption::VALUE_NONE, 'listing app\'s capabilities')
->addOption('type', '', InputOption::VALUE_REQUIRED, 'configuration to check', '')
->addOption('alpha', '', InputOption::VALUE_NONE, 'allow ALPHA features')
->addOption('test', '', InputOption::VALUE_REQUIRED, 'specify an url to test', '');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
if ($input->getOption('capabilities')) {
$capabilities = $this->getArray('circles', $this->capabilities->getCapabilities(true));
$output->writeln(json_encode($capabilities, JSON_PRETTY_PRINT));
return 0;
}
$this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
$test = $input->getOption('test');
$type = $input->getOption('type');
if ($test !== '' && $type === '') {
throw new Exception('Please specify a --type for the test');
}
if ($test !== '' && !in_array($type, self::$checks)) {
throw new Exception('Unknown type: ' . implode(', ', self::$checks));
}
// $this->configService->setAppValue(ConfigService::TEST_NC_BASE, $test);
if ($type === '' || $type === 'loopback') {
$output->writeln('### Checking <info>loopback</info> address.');
$this->checkLoopback($input, $output, $test);
$output->writeln('');
$output->writeln('');
}
if ($type === '' || $type === 'internal') {
$output->writeln('### Testing <info>internal</info> address.');
$this->checkInternal($input, $output, $test);
$output->writeln('');
$output->writeln('');
}
if (!$input->getOption('alpha')) {
return 0;
}
if ($type === '' || $type === 'frontal') {
$output->writeln('### Testing <info>frontal</info> address.');
$this->checkFrontal($input, $output, $test);
$output->writeln('');
}
return 0;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $test
*
* @throws Exception
*/
private function checkLoopback(InputInterface $input, OutputInterface $output, string $test = ''): void {
$output->writeln('. The <info>loopback</info> setting is mandatory and can be checked locally.');
$output->writeln(
'. The address you need to define here must be a reachable url of your Nextcloud from the hosting server itself.'
);
$output->writeln(
'. By default, the App will use the entry \'overwrite.cli.url\' from \'config/config.php\'.'
);
$notDefault = false;
if ($test === '') {
$test = $this->configService->getLoopbackPath();
} else {
$notDefault = true;
}
$output->writeln('');
$output->writeln('* testing current address: ' . $test);
try {
$this->setupLoopback($input, $output, $test);
$output->writeln('* <info>Loopback</info> address looks good');
if ($notDefault) {
$this->saveLoopback($input, $output, $test);
}
return;
} catch (Exception $e) {
}
$output->writeln('');
$output->writeln('- <comment>You do not have a valid loopback address setup right now.</comment>');
$output->writeln('');
$helper = $this->getHelper('question');
while (true) {
$question = new Question(
'<info>Please write down a new loopback address to test</info>: ', ''
);
$loopback = $helper->ask($input, $output, $question);
if (is_null($loopback) || $loopback === '') {
$output->writeln('exiting.');
throw new Exception('Your Circles App is not fully configured.');
}
try {
[$scheme, $cloudId, $path] = $this->parseAddress($loopback);
} catch (Exception $e) {
$output->writeln('<error>format must be http[s]://domain.name[:post][/path]</error>');
continue;
}
$loopback = rtrim($scheme . '://' . $cloudId . $path, '/');
$output->writeln('* testing address: ' . $loopback . ' ');
try {
$this->setupLoopback($input, $output, $loopback);
$this->saveLoopback($input, $output, $loopback);
return;
} catch (Exception $e) {
$output->writeln('');
}
}
}
/**
* @throws Exception
*/
private function setupLoopback(InputInterface $input, OutputInterface $output, string $address): void {
$e = null;
try {
[$scheme, $cloudId, $path] = $this->parseAddress($address);
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_SCHEME, $scheme);
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_ID, $cloudId);
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_PATH, $path);
if (!$this->testLoopback($input, $output)) {
throw new Exception();
}
} catch (Exception $e) {
}
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_SCHEME, '');
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_ID, '');
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_PATH, '');
if (!is_null($e)) {
throw $e;
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return bool
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotConfirmedException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestBuilderException
* @throws UnknownRemoteException
*/
private function testLoopback(InputInterface $input, OutputInterface $output): bool {
if (!$this->testRequest($output, 'GET', 'core.CSRFToken.index')) {
return false;
}
if (!$this->testRequest(
$output, 'POST', 'circles.EventWrapper.asyncBroadcast',
['token' => 'test-dummy-token']
)) {
return false;
}
$timer = round(microtime(true) * 1000);
$output->write('- Creating async FederatedEvent ');
$test = new FederatedEvent(LoopbackTest::class);
$this->federatedEventService->newEvent($test);
$output->writeln(
'<info>' . $test->getWrapperToken() . '</info> ' .
'(took ' . (round(microtime(true) * 1000) - $timer) . 'ms)'
);
$output->writeln('- Waiting for async process to finish (5s)');
sleep(5);
$output->write('- Checking status on FederatedEvent ');
$wrappers = $this->remoteUpstreamService->getEventsByToken($test->getWrapperToken());
if (count($wrappers) !== 1) {
$output->writeln('<error>Event created too many Wrappers</error>');
return false;
}
$wrapper = array_shift($wrappers);
$checkVerify = $wrapper->getEvent()->getData()->gInt('verify');
if ($checkVerify === LoopbackTest::VERIFY) {
$output->write('<info>verify=' . $checkVerify . '</info> ');
} else {
$output->writeln('<error>verify=' . $checkVerify . '</error>');
return false;
}
$checkManage = $wrapper->getResult()->gInt('manage');
if ($checkManage === LoopbackTest::MANAGE) {
$output->write('<info>manage=' . $checkManage . '</info> ');
} else {
$output->writeln('<error>manage=' . $checkManage . '</error>');
return false;
}
$output->writeln('');
return true;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $loopback
*
* @throws Exception
*/
private function saveLoopback(InputInterface $input, OutputInterface $output, string $loopback): void {
[$scheme, $cloudId, $path] = $this->parseAddress($loopback);
$question = new ConfirmationQuestion(
'- Do you want to save <info>'
. $loopback
. '</info> as your <info>loopback</info> address ? (y/N) ',
false, '/^(y|Y)/i'
);
$helper = $this->getHelper('question');
if (!$helper->ask($input, $output, $question)) {
$output->writeln('skipping.');
return;
}
$this->configService->setAppValue(ConfigService::LOOPBACK_CLOUD_SCHEME, $scheme);
$this->configService->setAppValue(ConfigService::LOOPBACK_CLOUD_ID, $cloudId);
$this->configService->setAppValue(ConfigService::LOOPBACK_CLOUD_PATH, $path);
$output->writeln(
'- Address <info>' . $loopback . '</info> is now used as <info>loopback</info>'
);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $test
*
* @throws SignatoryException
* @throws UnknownInterfaceException
* @throws Exception
*/
private function checkInternal(InputInterface $input, OutputInterface $output, string $test): void {
$output->writeln(
'. The <info>internal</info> setting should only be enabled if you are willing to use Circles in a GlobalScale setup on a local network.'
);
$output->writeln(
'. The address you need to define here is the local address of your Nextcloud, reachable by all other instances of our GlobalScale.'
);
$question = new ConfirmationQuestion(
'- <comment>Do you want to enable this feature ?</comment> (y/N) ', false, '/^(y|Y)/i'
);
$helper = $this->getHelper('question');
if (!$helper->ask($input, $output, $question)) {
$output->writeln('skipping.');
return;
}
while (true) {
$output->writeln('');
$question = new Question(
'<info>Please write down a new internal address to test</info>: ', ''
);
$internal = $helper->ask($input, $output, $question);
if (is_null($internal) || $internal === '') {
$output->writeln('skipping.');
return;
}
try {
[$scheme, $cloudId, $path] = $this->parseAddress($internal);
} catch (Exception $e) {
$output->writeln('<error>format must be http[s]://domain.name[:post][/path]</error>');
continue;
}
$internal = rtrim($scheme . '://' . $cloudId, '/');
$fullInternal = rtrim($scheme . '://' . $cloudId . $path, '/');
$question = new ConfirmationQuestion(
'<comment>Do you want to check the validity of this internal address?</comment> (Y/n) ', true,
'/^(y|Y)/i'
);
if ($helper->ask($input, $output, $question)) {
$testToken = $this->token();
$this->configService->setAppValue(ConfigService::IFACE_TEST_ID, $cloudId);
$this->configService->setAppValue(ConfigService::IFACE_TEST_SCHEME, $scheme);
$this->configService->setAppValue(ConfigService::IFACE_TEST_PATH, $path);
$this->configService->setAppValue(ConfigService::IFACE_TEST_TOKEN, $testToken);
$output->writeln('');
$output->writeln(
'You will need to run this <info>curl</info> command from a terminal on your local network and paste its result: '
);
$output->writeln(
' curl -L "' . $internal
. '/.well-known/webfinger?resource=http://nextcloud.com/&test='
. $testToken . '"'
);
$output->writeln('paste the result here: ');
$question = new Question('', '');
$pastedWebfinger = new SimpleDataStore();
$pastedWebfinger->json(trim($helper->ask($input, $output, $question)));
if ($pastedWebfinger->g('subject') !== Application::APP_SUBJECT) {
$output->writeln('<error>Cannot extract SUBJECT from the pasted data</error>');
continue;
}
$pastedHref = '';
foreach ($pastedWebfinger->gArray('links') as $link) {
$entry = new SimpleDataStore($link);
if ($entry->g('rel') === Application::APP_REL) {
$pastedHref = $entry->g('href');
}
}
if ($pastedHref === '') {
$output->writeln('<error>Cannot retrieve HREF from the pasted data</error>');
continue;
}
$href = $this->interfaceService->getCloudPath(
'circles.Remote.appService',
[],
InterfaceService::IFACE_TEST
);
if ($pastedHref !== $href) {
$output->writeln(
'<error>The returned data (' . $pastedHref . ') are not the one expected: </error>'
. $href
);
continue;
}
$output->writeln('');
$output->writeln('<info>First step seems fine.</info>');
$output->writeln(
'Next step, please run this <info>curl</info> command from a terminal on your local network and paste its result: '
);
$output->writeln(
' curl -L "' . $pastedHref . '?test=' . $testToken . '" -H "Accept: application/json"'
);
$output->writeln('paste the result here: ');
$question = new Question('', '');
$pastedSignatory = new SimpleDataStore();
$pastedSignatory->json(trim($helper->ask($input, $output, $question)));
// small hack to refresh the cached config
OC::$server->get(AppConfig::class)->clearCachedConfig();
$this->interfaceService->setCurrentInterface(InterfaceService::IFACE_TEST);
$appSignatory = $this->remoteStreamService->getAppSignatory(false);
if ($appSignatory->getUid(true) !== $pastedSignatory->g('uid')
|| $appSignatory->getRoot() !== $pastedSignatory->g('root')) {
$output->writeln(
'<error>The returned data ('
. $pastedSignatory->g('uid') . '/' . $pastedSignatory->g('root')
. ') are not the one expected: </error>'
. $appSignatory->getUid(true) . '/' . $appSignatory->getRoot()
);
continue;
}
$output->writeln('* <info>Internal</info> address looks good');
}
$this->saveInternal($input, $output, $fullInternal);
return;
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $internal
*
* @throws Exception
*/
private function saveInternal(InputInterface $input, OutputInterface $output, string $internal): void {
[$scheme, $cloudId, $path] = $this->parseAddress($internal);
$output->writeln('');
$question = new ConfirmationQuestion(
'- Do you want to save <info>' . $internal
. '</info> as your <info>internal</info> address ? (y/N) ',
false, '/^(y|Y)/i'
);
$helper = $this->getHelper('question');
if (!$helper->ask($input, $output, $question)) {
$output->writeln('skipping.');
return;
}
$this->configService->setAppValue(ConfigService::INTERNAL_CLOUD_SCHEME, $scheme);
$this->configService->setAppValue(ConfigService::INTERNAL_CLOUD_ID, $cloudId);
$this->configService->setAppValue(ConfigService::INTERNAL_CLOUD_PATH, $path);
$output->writeln('- Address <info>' . $internal . '</info> is now used as <info>internal</info>');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $test
*/
private function checkFrontal(InputInterface $input, OutputInterface $output, string $test): void {
$output->writeln('. The <info>frontal</info> setting is optional.');
$output->writeln(
'. The purpose of this address is for your Federated Circle to reach other instances of Nextcloud over the Internet.'
);
$output->writeln(
'. The address you need to define here must be reachable from the Internet.'
);
$output->writeln(
'. By default, this feature is disabled.'
);
$question = new ConfirmationQuestion(
'- <comment>Do you want to enable this feature ?</comment> (y/N) ', false, '/^(y|Y)/i'
);
$helper = $this->getHelper('question');
if (!$helper->ask($input, $output, $question)) {
$output->writeln('skipping.');
return;
}
while (true) {
$question = new Question(
'<info>Please write down a new frontal address to test</info>: ', ''
);
$frontal = $helper->ask($input, $output, $question);
if (is_null($frontal) || $frontal === '') {
$output->writeln('skipping.');
return;
}
try {
[$scheme, $cloudId, $path] = $this->parseAddress($frontal);
} catch (Exception $e) {
$output->writeln('<error>format must be http[s]://domain.name[:post][/path]</error>');
continue;
}
$frontal = rtrim($scheme . '://' . $cloudId, '/');
$fullFrontal = rtrim($scheme . '://' . $cloudId . $path, '/');
$question = new ConfirmationQuestion(
'<comment>Do you want to check the validity of this frontal address?</comment> (y/N) ', false,
'/^(y|Y)/i'
);
if ($helper->ask($input, $output, $question)) {
$testToken = $this->token();
$this->configService->setAppValue(ConfigService::IFACE_TEST_ID, $cloudId);
$this->configService->setAppValue(ConfigService::IFACE_TEST_SCHEME, $scheme);
$this->configService->setAppValue(ConfigService::IFACE_TEST_PATH, $path);
$this->configService->setAppValue(ConfigService::IFACE_TEST_TOKEN, $testToken);
$output->writeln('');
$output->writeln(
'You will need to run this <info>curl</info> command from a remote terminal and paste its result: '
);
$output->writeln(
' curl -L "' . $frontal
. '/.well-known/webfinger?resource=http://nextcloud.com/&test='
. $testToken . '"'
);
$output->writeln('paste the result here: ');
$question = new Question('', '');
$pastedWebfinger = new SimpleDataStore();
$pastedWebfinger->json(trim($helper->ask($input, $output, $question)));
if ($pastedWebfinger->g('subject') !== Application::APP_SUBJECT) {
$output->writeln('<error>Cannot extract SUBJECT from the pasted data</error>');
continue;
}
$pastedHref = '';
foreach ($pastedWebfinger->gArray('links') as $link) {
$entry = new SimpleDataStore($link);
if ($entry->g('rel') === Application::APP_REL) {
$pastedHref = $entry->g('href');
}
}
if ($pastedHref === '') {
$output->writeln('<error>Cannot retrieve HREF from the pasted data</error>');
continue;
}
$href = $this->interfaceService->getCloudPath(
'circles.Remote.appService',
[],
InterfaceService::IFACE_TEST
);
if ($pastedHref !== $href) {
$output->writeln(
'<error>The returned data (' . $pastedHref . ') are not the one expected: </error>'
. $href
);
continue;
}
$output->writeln('');
$output->writeln('<info>First step seems fine.</info>');
$output->writeln(
'Next step, please run this <info>curl</info> command from a remote terminal and paste its result: '
);
$output->writeln(
' curl -L "' . $pastedHref . '?test=' . $testToken . '" -H "Accept: application/json"'
);
$output->writeln('paste the result here: ');
$question = new Question('', '');
$pastedSignatory = new SimpleDataStore();
$pastedSignatory->json(trim($helper->ask($input, $output, $question)));
// small hack to refresh the cached config
OC::$server->get(AppConfig::class)->clearCachedConfig();
$this->interfaceService->setCurrentInterface(InterfaceService::IFACE_TEST);
$appSignatory = $this->remoteStreamService->getAppSignatory(false);
if ($appSignatory->getUid(true) !== $pastedSignatory->g('uid')
|| $appSignatory->getRoot() !== $pastedSignatory->g('root')) {
$output->writeln(
'<error>The returned data ('
. $pastedSignatory->g('uid') . '/' . $pastedSignatory->g('root')
. ') are not the one expected: </error>'
. $appSignatory->getUid(true) . '/' . $appSignatory->getRoot()
);
continue;
}
$output->writeln('* <info>Frontal</info> address looks good');
}
$this->saveFrontal($input, $output, $fullFrontal);
return;
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $frontal
*
* @throws Exception
*/
private function saveFrontal(InputInterface $input, OutputInterface $output, string $frontal): void {
[$scheme, $cloudId, $path] = $this->parseAddress($frontal);
$output->writeln('');
$question = new ConfirmationQuestion(
'- Do you want to save <info>' . $frontal
. '</info> as your <info>frontal</info> address ? (y/N) ',
false, '/^(y|Y)/i'
);
$helper = $this->getHelper('question');
if (!$helper->ask($input, $output, $question)) {
$output->writeln('skipping.');
return;
}
$this->configService->setAppValue(ConfigService::FRONTAL_CLOUD_SCHEME, $scheme);
$this->configService->setAppValue(ConfigService::FRONTAL_CLOUD_ID, $cloudId);
$this->configService->setAppValue(ConfigService::FRONTAL_CLOUD_PATH, $path);
$output->writeln('- Address <info>' . $frontal . '</info> is now used as <info>frontal</info>');
}
/**
* @param OutputInterface $o
* @param string $type
* @param string $route
* @param array $args
*
* @return bool
*/
private function testRequest(
OutputInterface $output,
string $type,
string $route,
array $args = []
): bool {
$request = new NCRequest('', Request::type($type));
$this->configService->configureLoopbackRequest($request, $route, $args);
$request->setFollowLocation(false);
if ($request->getType() !== Request::TYPE_GET) {
$request->setDataSerialize(new SimpleDataStore(['empty' => 1]));
}
$output->write('- ' . $type . ' request on ' . $request->getCompleteUrl() . ': ');
try {
$this->doRequest($request);
$result = $request->getResult();
$color = 'error';
if ($result->getStatusCode() === 200) {
$color = 'info';
}
$output->writeln('<' . $color . '>' . $result->getStatusCode() . '</' . $color . '>');
if ($result->getStatusCode() === 200) {
return true;
}
} catch (RequestNetworkException $e) {
$output->writeln('<error>fail</error>');
}
return false;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $address
*/
private function saveUrl(InputInterface $input, OutputInterface $output, string $address): void {
if ($address === '') {
return;
}
$output->writeln('');
$output->writeln(
'The address <info>' . $address . '</info> seems to reach your local Nextcloud.'
);
$helper = $this->getHelper('question');
$output->writeln('');
$question = new ConfirmationQuestion(
'<info>Do you want to store this address in database ?</info> (y/N) ', false, '/^(y|Y)/i'
);
if (!$helper->ask($input, $output, $question)) {
$output->writeln('Configuration NOT saved');
return;
}
$this->configService->setAppValue(ConfigService::FORCE_NC_BASE, $address);
$output->writeln(
'New configuration <info>' . Application::APP_ID . '.' . ConfigService::FORCE_NC_BASE . '=\''
. $address . '\'</info> stored in database'
);
}
/**
* @param string $test
*
* @return array
* @throws Exception
*/
private function parseAddress(string $test): array {
$scheme = parse_url($test, PHP_URL_SCHEME);
$cloudId = parse_url($test, PHP_URL_HOST);
$cloudIdPort = parse_url($test, PHP_URL_PORT);
$path = parse_url($test, PHP_URL_PATH);
if (is_bool($scheme) || is_bool($cloudId) || is_null($scheme) || is_null($cloudId)) {
throw new Exception();
}
if (is_null($path) || is_bool($path)) {
$path = '';
}
$path = rtrim($path, '/');
if (!is_null($cloudIdPort)) {
$cloudId = $cloudId . ':' . $cloudIdPort;
}
return [$scheme, $cloudId, $path];
}
}
@@ -0,0 +1,249 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedEventException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\FederatedUserService;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesConfig
*
* @package OCA\Circles\Command
*/
class CirclesConfig extends Base {
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/**
* CirclesConfig constructor.
*
* @param FederatedUserService $federatedUserService
* @param CircleService $circlesService
*/
public function __construct(FederatedUserService $federatedUserService, CircleService $circlesService) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->circleService = $circlesService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:manage:config')
->setDescription('edit config/type of a Circle')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addArgument(
'config', InputArgument::IS_ARRAY,
'list of value to change in the configuration of the Circle'
)
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption(
'super-session', '',
InputOption::VALUE_NONE, 'use super session to bypass some condition'
)
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotFoundException
* @throws RequestBuilderException
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotConfirmedException
* @throws InvalidIdException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = (string)$input->getArgument('circle_id');
try {
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
false
);
if ($input->getOption('super-session')) {
$this->federatedUserService->bypassCurrentUserCondition(true);
}
$circle = $this->circleService->getCircle($circleId);
if (empty($input->getArgument('config'))) {
$output->writeln(
json_encode(Circle::getCircleFlags($circle, Circle::FLAGS_LONG), JSON_PRETTY_PRINT)
);
return 0;
}
$new = $this->generateConfig($circle, $input->getArgument('config'));
$outcome = $this->circleService->updateConfig($circleId, $new);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
} elseif (strtolower($input->getOption('output')) !== 'none') {
$circle = $this->circleService->getCircle($circleId);
$output->writeln(
json_encode(
Circle::getCircleFlags($circle, Circle::FLAGS_LONG),
JSON_PRETTY_PRINT
)
);
}
return 0;
}
/**
* @param Circle $circle
* @param array $listing
*
* @return int
*/
private function generateConfig(Circle $circle, array $listing): int {
$current = clone $circle;
$valid = $this->filterValidConfig($current);
foreach ($listing as $item) {
$add = true;
if (substr($item, 0, 1) === '_') {
$add = false;
$item = substr($item, 1);
}
$value = array_search(strtoupper($item), $valid);
if (!$value) {
throw new InvalidArgumentException(
'Invalid config \'' . $item . '\'. Available values: '
. implode(', ', array_values($valid)) . '. '
. 'To disable a config, start the value with an underscore'
);
}
if ($add) {
$current->addConfig($value);
} else {
$current->remConfig($value);
}
}
return $current->getConfig();
}
/**
* @param Circle $circle
*
* @return array
*/
private function filterValidConfig(Circle $circle): array {
$listing = Circle::$DEF_CFG;
$filters = Circle::$DEF_CFG_CORE_FILTER;
if (!$circle->isConfig(Circle::CFG_SYSTEM)) {
$filters = array_merge($filters, Circle::$DEF_CFG_SYSTEM_FILTER);
}
foreach ($filters as $filter) {
unset($listing[$filter]);
}
array_walk(
$listing,
function (string &$v): void {
[, $long] = explode('|', $v);
$v = strtoupper(str_replace(' ', '', $long));
}
);
return $listing;
}
}
@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Tools\Exceptions\InvalidItemException;
use OCA\Circles\Tools\Traits\TDeserialize;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesCreate
*
* @package OCA\Circles\Command
*/
class CirclesCreate extends Base {
use TDeserialize;
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/**
* CirclesCreate constructor.
*
* @param FederatedUserService $federatedUserService
* @param CircleService $circleService
*/
public function __construct(
FederatedUserService $federatedUserService,
CircleService $circleService
) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->circleService = $circleService;
}
protected function configure() {
parent::configure();
$this->setName('circles:manage:create')
->setDescription('create a new circle')
->addArgument('owner', InputArgument::REQUIRED, 'owner of the circle')
->addArgument('name', InputArgument::REQUIRED, 'name of the circle')
->addOption('personal', '', InputOption::VALUE_NONE, 'create a personal circle')
->addOption('local', '', InputOption::VALUE_NONE, 'create a local circle')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception')
->addOption(
'type', '', InputOption::VALUE_REQUIRED, 'type of the owner',
Member::$TYPE[Member::TYPE_SINGLE]
);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedItemException
* @throws InitiatorNotFoundException
* @throws SingleCircleNotFoundException
* @throws RequestBuilderException
* @throws InvalidItemException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$ownerId = $input->getArgument('owner');
$name = $input->getArgument('name');
try {
$this->federatedUserService->bypassCurrentUserCondition(true);
$type = Member::parseTypeString($input->getOption('type'));
$owner = $this->federatedUserService->getFederatedUser($ownerId, $type);
$outcome = $this->circleService->create(
$name,
$owner,
$input->getOption('personal'),
$input->getOption('local')
);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
} elseif (strtolower($input->getOption('output')) !== 'none') {
/** @var Circle $circle */
$circle = $this->deserialize($outcome, Circle::class);
$output->writeln('Id: <info>' . $circle->getSingleId() . '</info>');
$output->writeln('Name: <info>' . $circle->getDisplayName() . '</info>');
$output->writeln('Owner: <info>' . $circle->getOwner()->getDisplayName() . '</info>');
}
return 0;
}
}
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\FederatedUserService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesDestroy
*
* @package OCA\Circles\Command
*/
class CirclesDestroy extends Base {
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/**
* CirclesDestroy constructor.
*
* @param CircleService $circleService
*/
public function __construct(FederatedUserService $federatedUserService, CircleService $circleService) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->circleService = $circleService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:manage:destroy')
->setDescription('destroy a circle by its ID')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle to be destroyed')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws CircleNotFoundException
* @throws FederatedItemException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InvalidIdException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
* @throws RequestBuilderException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = $input->getArgument('circle_id');
try {
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
false
);
$outcome = $this->circleService->destroy($circleId);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
}
@@ -0,0 +1,192 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OCA\Circles\Tools\Exceptions\InvalidItemException;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MemberService;
use OCA\Circles\Service\RemoteService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesDetails
*
* @package OCA\Circles\Command
*/
class CirclesDetails extends Base {
/** @var FederatedUserService */
private $federatedUserService;
/** @var RemoteService */
private $remoteService;
/** @var MemberService */
private $memberService;
/** @var CircleService */
private $circleService;
/** @var ConfigService */
private $configService;
/**
* CirclesDetails constructor.
*
* @param FederatedUserService $federatedUserService
* @param RemoteService $remoteService
* @param CircleService $circlesService
* @param MemberService $membersService
* @param ConfigService $configService
*/
public function __construct(
FederatedUserService $federatedUserService, RemoteService $remoteService,
CircleService $circlesService, MemberService $membersService, ConfigService $configService
) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->remoteService = $remoteService;
$this->circleService = $circlesService;
$this->memberService = $membersService;
$this->configService = $configService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:manage:details')
->setDescription('get details about a circle by its ID')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addOption('instance', '', InputOption::VALUE_REQUIRED, 'Instance of the circle', '')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws CircleNotFoundException
* @throws FederatedItemException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotFoundException
* @throws InvalidItemException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
* @throws InvalidIdException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = (string)$input->getArgument('circle_id');
$instance = $input->getOption('instance');
try {
if ($instance !== '') {
$circle = $this->remoteService->getCircleFromInstance(
$circleId,
$instance,
[
'initiator' => $input->getOption('initiator'),
'initiatorType' => Member::parseTypeString($input->getOption('initiator-type'))
]
);
} else {
try {
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
true
);
$probe = new CircleProbe();
$probe->includeNonVisibleCircles();
$circle = $this->circleService->getCircle($circleId, $probe);
} catch (CircleNotFoundException $e) {
throw new CircleNotFoundException(
'unknown circle, use --instance to retrieve the data from a remote instance'
);
}
}
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
$output->writeln(json_encode($circle, JSON_PRETTY_PRINT));
return 0;
}
}
@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCP\IL10N;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesEdit
*
* @package OCA\Circles\Command
*/
class CirclesEdit extends Base {
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/**
* CirclesEdit constructor.
*
* @param IL10N $l10n
* @param FederatedUserService $federatedUserService
* @param CircleService $circlesService
* @param ConfigService $configService
*/
public function __construct(
IL10N $l10n, FederatedUserService $federatedUserService, CircleService $circlesService,
ConfigService $configService
) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->circleService = $circlesService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:manage:edit')
->setDescription('edit displayName or description of a Circle')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addArgument('edit', InputArgument::REQUIRED, 'displayName or description')
->addArgument('value', InputArgument::REQUIRED, 'new value')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedItemException
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InvalidIdException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
* @throws RequestBuilderException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = (string)$input->getArgument('circle_id');
$edit = strtolower((string)$input->getArgument('edit'));
$newValue = (string)$input->getArgument('value');
try {
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
false
);
switch ($edit) {
case 'displayname':
$outcome = $this->circleService->updateName($circleId, $newValue);
break;
case 'description':
$outcome = $this->circleService->updateDescription($circleId, $newValue);
break;
default:
throw new InvalidArgumentException('edit can only be \'displayName\' or \'description\'');
}
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
}
@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedEventException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\FederatedUserService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesJoin
*
* @package OCA\Circles\Command
*/
class CirclesJoin extends Base {
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/**
* CirclesJoin constructor.
*
* @param FederatedUserService $federatedUserService
* @param CircleService $circlesService
*/
public function __construct(
FederatedUserService $federatedUserService,
CircleService $circlesService
) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->circleService = $circlesService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:manage:join')
->setDescription('emulate a user joining a Circle')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addArgument('initiator', InputArgument::REQUIRED, 'initiator to the request')
->addOption('type', '', InputOption::VALUE_REQUIRED, 'type of the initiator', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotFoundException
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotConfirmedException
* @throws InvalidIdException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = (string)$input->getArgument('circle_id');
try {
$this->federatedUserService->commandLineInitiator(
$input->getArgument('initiator'),
Member::parseTypeString($input->getOption('type')),
'',
false
);
$outcome = $this->circleService->circleJoin($circleId);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
}
@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\FederatedEventException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesLeave
*
* @package OCA\Circles\Command
*/
class CirclesLeave extends Base {
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/** @var ConfigService */
private $configService;
/**
* CirclesLeave constructor.
*
* @param FederatedUserService $federatedUserService
* @param CircleService $circlesService
* @param ConfigService $configService
*/
public function __construct(
FederatedUserService $federatedUserService, CircleService $circlesService,
ConfigService $configService
) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->circleService = $circlesService;
$this->configService = $configService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:manage:leave')
->setDescription('simulate a user joining a Circle')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addArgument('initiator', InputArgument::REQUIRED, 'initiator to the request')
->addOption('type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotFoundException
* @throws RequestBuilderException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = (string)$input->getArgument('circle_id');
try {
$this->federatedUserService->commandLineInitiator(
$input->getArgument('initiator'),
Member::parseTypeString($input->getOption('type')),
'',
false
);
$outcome = $this->circleService->circleLeave($circleId);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
}
@@ -0,0 +1,267 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OCA\Circles\Tools\Exceptions\RequestNetworkException;
use OCA\Circles\Tools\Exceptions\SignatoryException;
use OCA\Circles\Tools\Traits\TArrayTools;
use OCA\Circles\Tools\Traits\TStringTools;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\ModelManager;
use OCA\Circles\Model\Probes\BasicProbe;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\RemoteService;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesList
*
* @package OCA\Circles\Command
*/
class CirclesList extends Base {
use TArrayTools;
use TStringTools;
/** @var ModelManager */
private $modelManager;
/** @var FederatedUserService */
private $federatedUserService;
/** @var RemoteService */
private $remoteService;
/** @var CircleService */
private $circleService;
/** @var ConfigService */
private $configService;
/** @var InputInterface */
private $input;
/**
* CirclesList constructor.
*
* @param ModelManager $modelManager
* @param FederatedUserService $federatedUserService
* @param RemoteService $remoteService
* @param CircleService $circleService
* @param ConfigService $configService
*/
public function __construct(
ModelManager $modelManager, FederatedUserService $federatedUserService, RemoteService $remoteService,
CircleService $circleService, ConfigService $configService
) {
parent::__construct();
$this->modelManager = $modelManager;
$this->federatedUserService = $federatedUserService;
$this->remoteService = $remoteService;
$this->circleService = $circleService;
$this->configService = $configService;
}
protected function configure() {
parent::configure();
$this->setName('circles:manage:list')
->setDescription('listing current circles')
->addOption('instance', '', InputOption::VALUE_REQUIRED, 'Instance of the circle', '')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('member', '', InputOption::VALUE_REQUIRED, 'search for member', '')
->addOption('def', '', InputOption::VALUE_NONE, 'display complete circle configuration')
->addOption('display-name', '', InputOption::VALUE_NONE, 'display the displayName')
->addOption('personal', '', InputOption::VALUE_NONE, 'include Personal Circles')
->addOption('system', '', InputOption::VALUE_NONE, 'include System Circles')
->addOption('hidden', '', InputOption::VALUE_NONE, 'include Hidden Circles')
->addOption('backend', '', InputOption::VALUE_NONE, 'include Backend Circles')
->addOption('single', '', InputOption::VALUE_NONE, 'returns only Single Circles')
->addOption('all', '', InputOption::VALUE_NONE, 'include all Circles');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotFoundException
* @throws InvalidIdException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestNetworkException
* @throws SignatoryException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
* @throws FederatedItemException
* @throws MemberNotFoundException
* @throws SingleCircleNotFoundException
* @throws RequestBuilderException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$this->input = $input;
$member = $input->getOption('member');
$instance = $input->getOption('instance');
$initiator = $input->getOption('initiator');
$filterMember = null;
if ($member !== '') {
$filterMember = $this->federatedUserService->getFederatedMember($member);
}
if (!$this->configService->isLocalInstance($instance)) {
$data = ['filterMember' => $filterMember];
if ($initiator) {
$data['initiator'] = $this->federatedUserService->getFederatedUser(
$initiator,
Member::parseTypeString($input->getOption('initiator-type')),
);
}
$circles = $this->remoteService->getCirclesFromInstance($instance, $data);
} else {
$this->federatedUserService->commandLineInitiator(
$initiator,
Member::parseTypeString($input->getOption('initiator-type')),
'',
true
);
$probe = new CircleProbe();
$probe->filterHiddenCircles()
->filterBackendCircles()
->addDetail(BasicProbe::DETAILS_POPULATION);
if ($input->getOption('system')) {
$probe->includeSystemCircles();
$probe->filterHiddenCircles(false);
}
$probe->includeHiddenCircles($input->getOption('hidden'));
$probe->includeBackendCircles($input->getOption('backend'));
$probe->includePersonalCircles($input->getOption('personal'));
if ($input->getOption('single')) {
$singleCircle = new Circle();
$singleCircle->setConfig(Circle::CFG_SINGLE);
$probe->setFilterCircle($singleCircle)
->includeSingleCircles();
}
if ($input->getOption('all')) {
$probe->includeSystemCircles();
$probe->includeHiddenCircles();
$probe->includeBackendCircles();
$probe->includePersonalCircles();
$probe->includeSingleCircles();
}
$circles = $this->circleService->getCircles($probe);
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($circles, JSON_PRETTY_PRINT));
return 0;
}
$this->displayCircles($circles);
return 0;
}
/**
* @param Circle[] $circles
*/
private function displayCircles(array $circles): void {
$output = new ConsoleOutput();
$output = $output->section();
$table = new Table($output);
$table->setHeaders(
['Single Id', 'Name', 'Config', 'Source', 'Owner', 'Instance', 'Population']
);
$table->render();
$displayName = $this->input->getOption('display-name');
$display = ($this->input->getOption('def') ? Circle::FLAGS_LONG : Circle::FLAGS_SHORT);
foreach ($circles as $circle) {
$owner = $circle->getOwner();
$table->appendRow(
[
$circle->getSingleId(),
$this->cut(($displayName ? $circle->getDisplayName() : $circle->getName()), 40),
json_encode(Circle::getCircleFlags($circle, $display)),
Circle::$DEF_SOURCE[$circle->getSource()],
$this->cut($displayName ? $owner->getDisplayName() : $owner->getUserId(), 40),
$this->configService->displayInstance($owner->getInstance()),
$circle->getPopulation() . '/'
. $this->getInt('members_limit', $circle->getSettings(), -1)
. ' (' . $circle->getPopulationInherited() . ')'
]
);
}
}
}
@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Db\CoreRequestBuilder;
use OCA\Circles\Exceptions\MaintenanceException;
use OCA\Circles\Service\MaintenanceService;
use OCA\Circles\Service\OutputService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
/**
* Class CirclesMaintenance
*
* @package OCA\Circles\Command
*/
class CirclesMaintenance extends Base {
/** @var CoreRequestBuilder */
private $coreRequestBuilder;
/** @var MaintenanceService */
private $maintenanceService;
/** @var OutputService */
private $outputService;
/**
* CirclesMaintenance constructor.
*
* @param CoreRequestBuilder $coreRequestBuilder
* @param MaintenanceService $maintenanceService
* @param OutputService $outputService
*/
public function __construct(
CoreRequestBuilder $coreRequestBuilder,
MaintenanceService $maintenanceService,
OutputService $outputService
) {
parent::__construct();
$this->coreRequestBuilder = $coreRequestBuilder;
$this->maintenanceService = $maintenanceService;
$this->outputService = $outputService;
}
protected function configure() {
parent::configure();
$this->setName('circles:maintenance')
->setDescription('Clean stuff, keeps the app running')
->addOption('level', '', InputOption::VALUE_REQUIRED, 'level of maintenance', '3')
->addOption(
'reset', '', InputOption::VALUE_NONE, 'reset Circles; remove all data related to the App'
)
->addOption(
'clean-shares', '', InputOption::VALUE_NONE, 'remove Circles\' shares'
)
->addOption(
'uninstall', '', InputOption::VALUE_NONE,
'Uninstall the apps and everything related to the app from the database'
)
->addOption('force-refresh', '', InputOption::VALUE_NONE, 'enforce some refresh');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$reset = $input->getOption('reset');
$uninstall = $input->getOption('uninstall');
$level = (int)$input->getOption('level');
if ($reset || $uninstall) {
$action = $uninstall ? 'uninstall' : 'reset';
$output->writeln('');
$output->writeln('');
$output->writeln(
'<error>WARNING! You are about to delete all data related to the Circles App!</error>'
);
$question = new ConfirmationQuestion(
'<comment>Do you really want to ' . $action . ' Circles ?</comment> (y/N) ', false,
'/^(y|Y)/i'
);
$helper = $this->getHelper('question');
if (!$helper->ask($input, $output, $question)) {
$output->writeln('aborted.');
return 0;
}
$output->writeln('');
$output->writeln('<error>WARNING! This operation is not reversible.</error>');
$question = new Question(
'<comment>Please confirm this destructive operation by typing \'' . $action
. '\'</comment>: ', ''
);
$helper = $this->getHelper('question');
$confirmation = $helper->ask($input, $output, $question);
if (strtolower($confirmation) !== $action) {
$output->writeln('aborted.');
return 0;
}
$this->coreRequestBuilder->cleanDatabase($input->getOption('clean-shares'));
if ($uninstall) {
$this->coreRequestBuilder->uninstall();
}
$output->writeln('<info>' . $action . ' done</info>');
return 0;
}
$this->outputService->setOccOutput($output);
$this->maintenanceService->setOccOutput($output);
for ($i = 1; $i <= $level; $i++) {
try {
$this->maintenanceService->runMaintenance($i, $input->getOption('force-refresh'));
} catch (MaintenanceException $e) {
}
}
$output->writeln('');
$output->writeln('<info>done</info>');
return 0;
}
}
@@ -0,0 +1,404 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use Exception;
use OC\Core\Command\Base;
use OCA\Circles\Db\CircleRequest;
use OCA\Circles\Db\MemberRequest;
use OCA\Circles\Db\MembershipRequest;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\FederatedUser;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MembershipService;
use OCA\Circles\Tools\Exceptions\InvalidItemException;
use OCA\Circles\Tools\Exceptions\ItemNotFoundException;
use OCA\Circles\Tools\Exceptions\UnknownTypeException;
use OCA\Circles\Tools\Model\SimpleDataStore;
use OCA\Circles\Tools\Model\TreeNode;
use OCA\Circles\Tools\Traits\TArrayTools;
use OCA\Circles\Tools\Traits\TConsoleTree;
use OCP\IUserManager;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesMemberships
*
* @package OCA\Circles\Command
*/
class CirclesMemberships extends Base {
use TArrayTools;
use TConsoleTree;
/** @var IUserManager */
private $userManager;
/** @var MembershipRequest */
private $membershipRequest;
/** @var MemberRequest */
private $memberRequest;
/** @var CircleRequest */
private $circleRequest;
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/** @var MembershipService */
private $membershipService;
/** @var ConfigService */
private $configService;
/** @var InputInterface */
private $input;
/** @var array */
private $memberships = [];
/**
* CirclesMemberships constructor.
*
* @param IUserManager $userManager
* @param MembershipRequest $membershipRequest
* @param MemberRequest $memberRequest
* @param CircleRequest $circleRequest
* @param FederatedUserService $federatedUserService
* @param CircleService $circleService
* @param MembershipService $membershipService
* @param ConfigService $configService
*/
public function __construct(
IUserManager $userManager,
MembershipRequest $membershipRequest,
MemberRequest $memberRequest,
CircleRequest $circleRequest,
FederatedUserService $federatedUserService,
CircleService $circleService,
MembershipService $membershipService,
ConfigService $configService
) {
parent::__construct();
$this->userManager = $userManager;
$this->memberRequest = $memberRequest;
$this->membershipRequest = $membershipRequest;
$this->circleRequest = $circleRequest;
$this->federatedUserService = $federatedUserService;
$this->circleService = $circleService;
$this->membershipService = $membershipService;
$this->configService = $configService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:memberships')
->setDescription('index and display memberships for local and federated users')
->addArgument('userId', InputArgument::OPTIONAL, 'userId to generate memberships', '')
->addOption('display-name', '', InputOption::VALUE_NONE, 'display the displayName')
// ->addOption('reset', '', InputOption::VALUE_NONE, 'reset memberships')
->addOption('all', '', InputOption::VALUE_NONE, 'refresh memberships for all entities')
->addOption(
'type', '', InputOption::VALUE_REQUIRED, 'type of the user',
Member::$TYPE[Member::TYPE_SINGLE]
);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
* @throws FederatedItemException
* @throws InvalidIdException
* @throws SingleCircleNotFoundException
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$this->input = $input;
if ($input->getOption('all')) {
$this->manageAllMemberships();
return 0;
}
$userId = $input->getArgument('userId');
if ($userId === '') {
throw new Exception('Not enough arguments (missing: "userId").');
}
$type = Member::parseTypeString($input->getOption('type'));
$federatedUser = $this->federatedUserService->getFederatedUser($userId, $type);
// if ($this->input->getOption('reset')) {
// $this->membershipsService->resetMemberships($federatedUser->getSingleId());
//
// return 0;
// }
$output->writeln('Id: <info>' . $federatedUser->getUserId() . '</info>');
$output->writeln('Instance: <info>' . $federatedUser->getInstance() . '</info>');
$output->writeln('Type: <info>' . Member::$TYPE[$federatedUser->getUserType()] . '</info>');
$output->writeln('SingleId: <info>' . $federatedUser->getSingleId() . '</info>');
$output->writeln('');
$output->writeln('Memberships:');
$count = $this->membershipService->manageMemberships($federatedUser->getSingleId());
if ($count === 0) {
$output->writeln('(database not updated)');
} else {
$output->writeln('(' . $count . ' entries generated/updated in the database)');
}
foreach ($federatedUser->getMemberships() as $membership) {
$this->memberships[$membership->getCircleId()] = $membership;
$output->writeln(
'- <info>' . $membership->getCircleId() . '</info> ('
. Member::$DEF_LEVEL[$membership->getLevel()] . ')'
);
}
$output->writeln('');
$tree = new TreeNode(null, new SimpleDataStore(['federatedUser' => $federatedUser]));
$this->generateTree($federatedUser->getSingleId(), $tree);
$this->drawTree(
$tree, [$this, 'displayLeaf'],
[
'height' => 3,
'node-spacing' => 0,
'item-spacing' => 1,
]
);
return 0;
}
/**
* @param string $id
* @param TreeNode $tree
* @param array $knownIds
*/
private function generateTree(string $id, TreeNode $tree, array $knownIds = []) {
if (in_array($id, $knownIds)) {
return;
}
$knownIds[] = $id;
$members = $this->memberRequest->getMembersBySingleId($id);
foreach ($members as $member) {
if ($member->getLevel() < Member::LEVEL_MEMBER
|| $member->getCircle()->isConfig(Circle::CFG_SINGLE)) {
continue;
}
$item = new TreeNode(
$tree, new SimpleDataStore(
[
'member' => $member,
'cycling' => in_array($member->getCircleId(), $knownIds)
]
)
);
$this->generateTree($member->getCircleId(), $item, $knownIds);
}
}
/**
* @param SimpleDataStore $data
* @param int $lineNumber
*
* @return string
* @throws OwnerNotFoundException
*/
public function displayLeaf(SimpleDataStore $data, int $lineNumber): string {
if ($lineNumber === 3) {
return ($data->gBool('cycling')) ? '<comment>(loop detected)</comment>' : '';
}
try {
$line = '';
if ($data->hasKey('federatedUser')) {
/** @var FederatedUser $federatedUser */
$federatedUser = $data->gObj('federatedUser', FederatedUser::class);
if ($lineNumber === 2) {
return '';
}
$line .= '<info>' . $federatedUser->getSingleId() . '</info>';
if (!$this->configService->isLocalInstance($federatedUser->getInstance())) {
$line .= '@' . $federatedUser->getInstance();
}
return $line;
}
if ($data->hasKey('member')) {
/** @var Member $member */
$member = $data->gObj('member', Member::class);
$circle = $member->getCircle();
if ($lineNumber === 1) {
$line .= '<info>' . $circle->getSingleId() . '</info>';
if (!$this->configService->isLocalInstance($circle->getInstance())) {
$line .= '@' . $circle->getInstance();
}
$line .= ' (' . ($this->input->getOption('display-name') ?
$circle->getDisplayName() : $circle->getName()) . ')';
$line .= ' <info>MemberId</info>: ' . $member->getId();
$line .= ' <info>Level</info>: ' . Member::$DEF_LEVEL[$member->getLevel()];
$knownMembership = $this->memberships[$member->getCircleId()];
if ($member->getLevel() !== $knownMembership->getLevel()) {
$line .= ' (' . Member::$DEF_LEVEL[$knownMembership->getLevel()] . ')';
}
}
if ($lineNumber === 2) {
$owner = $circle->getOwner();
$line .= '<info>Owner</info>: ' . $owner->getUserId() . '@' . $owner->getInstance() . ' ';
if ($owner->hasBasedOn()) {
$line .= '(' . Circle::$DEF_SOURCE[$owner->getBasedOn()->getSource()] . ') ';
}
$type = implode(", ", Circle::getCircleFlags($circle, Circle::FLAGS_LONG));
$line .= ($type === '') ? '' : '<info>Config</info>: ' . $type;
}
return $line;
}
} catch (InvalidItemException | ItemNotFoundException | UnknownTypeException $e) {
}
return '';
}
/**
* @throws FederatedUserNotFoundException
* @throws InitiatorNotFoundException
* @throws OwnerNotFoundException
* @throws RequestBuilderException
*/
private function manageAllMemberships() {
// if ($this->input->getOption('reset')) {
// $this->membershipsService->resetMemberships('', true);
//
// return;
// }
$this->federatedUserService->bypassCurrentUserCondition(true);
$probe = new CircleProbe();
$probe->includeSystemCircles()
->includeSingleCircles()
->includePersonalCircles();
$circles = $this->circleService->getCircles($probe);
$output = new ConsoleOutput();
$output = $output->section();
$table = new Table($output);
$table->setHeaders(['Circle Id', 'Name', 'Source', 'Owner', 'Instance', 'Updated', 'Memberships']);
$table->render();
$count = 0;
foreach ($circles as $circle) {
$owner = $circle->getOwner();
$updated = $this->membershipService->manageMemberships($circle->getSingleId());
$count += $updated;
$federatedUser = $this->circleRequest->getFederatedUserBySingleId($circle->getSingleId());
$table->appendRow(
[
$circle->getSingleId(),
$circle->getDisplayName(),
($circle->getSource() > 0) ? Circle::$DEF_SOURCE[$circle->getSource()] : '',
$owner->getUserId(),
$this->configService->displayInstance($owner->getInstance()),
$updated,
sizeof($federatedUser->getMemberships())
]
);
}
$output->writeln($count . ' memberships updated');
}
}
@@ -0,0 +1,534 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OCA\Circles\Tools\Exceptions\RequestNetworkException;
use OCA\Circles\Tools\Exceptions\SignatoryException;
use OCA\Circles\Tools\Exceptions\SignatureException;
use OCA\Circles\Tools\Model\NCRequest;
use OCA\Circles\Tools\Model\NCSignedRequest;
use OCA\Circles\Tools\Traits\TNCWellKnown;
use OCA\Circles\Tools\Traits\TStringTools;
use Exception;
use OC\Core\Command\Base;
use OCA\Circles\AppInfo\Application;
use OCA\Circles\Db\RemoteRequest;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteUidException;
use OCA\Circles\Model\Federated\RemoteInstance;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\GlobalScaleService;
use OCA\Circles\Service\InterfaceService;
use OCA\Circles\Service\RemoteStreamService;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
/**
* Class CirclesRemote
*
* @package OCA\Circles\Command
*/
class CirclesRemote extends Base {
use TNCWellKnown;
use TStringTools;
/** @var RemoteRequest */
private $remoteRequest;
/** @var GlobalScaleService */
private $globalScaleService;
/** @var RemoteStreamService */
private $remoteStreamService;
/** @var InterfaceService */
private $interfaceService;
/** @var ConfigService */
private $configService;
/** @var InputInterface */
private $input;
/** @var OutputInterface */
private $output;
/**
* CirclesRemote constructor.
*
* @param RemoteRequest $remoteRequest
* @param GlobalScaleService $globalScaleService
* @param RemoteStreamService $remoteStreamService
* @param InterfaceService $interfaceService
* @param ConfigService $configService
*/
public function __construct(
RemoteRequest $remoteRequest,
GlobalScaleService $globalScaleService,
RemoteStreamService $remoteStreamService,
InterfaceService $interfaceService,
ConfigService $configService
) {
parent::__construct();
$this->remoteRequest = $remoteRequest;
$this->globalScaleService = $globalScaleService;
$this->remoteStreamService = $remoteStreamService;
$this->interfaceService = $interfaceService;
$this->configService = $configService;
$this->setup('app', 'circles');
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:remote')
->setDescription('remote features')
->addArgument('host', InputArgument::OPTIONAL, 'host of the remote instance of Nextcloud')
->addOption(
'type', '', InputOption::VALUE_REQUIRED, 'set type of remote', RemoteInstance::TYPE_UNKNOWN
)
->addOption(
'iface', '', InputOption::VALUE_REQUIRED, 'set interface to use to contact remote',
InterfaceService::$LIST_IFACE[InterfaceService::IFACE_FRONTAL]
)
->addOption('yes', '', InputOption::VALUE_NONE, 'silently add the remote instance')
->addOption('all', '', InputOption::VALUE_NONE, 'display all information');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$host = $input->getArgument('host');
$this->input = $input;
$this->output = $output;
if ($host) {
$this->requestInstance($host);
} else {
$this->checkKnownInstance();
}
return 0;
}
/**
* @param string $host
*
* @throws Exception
*/
private function requestInstance(string $host): void {
$remoteType = $this->getRemoteType();
$remoteIface = $this->getRemoteInterface();
$this->interfaceService->setCurrentInterface($remoteIface);
$webfinger = $this->getWebfinger($host, Application::APP_SUBJECT);
if ($this->input->getOption('all')) {
$this->output->writeln('- Webfinger on <info>' . $host . '</info>');
$this->output->writeln(json_encode($webfinger, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->output->writeln('');
}
if ($this->input->getOption('all')) {
$circleLink = $this->extractLink(Application::APP_REL, $webfinger);
$this->output->writeln('- Information about Circles app on <info>' . $host . '</info>');
$this->output->writeln(json_encode($circleLink, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->output->writeln('');
}
$this->output->writeln('- Available services on <info>' . $host . '</info>');
foreach ($webfinger->getLinks() as $link) {
$app = $link->getProperty('name');
$ver = $link->getProperty('version');
if ($app !== '') {
$app .= ' ';
}
if ($ver !== '') {
$ver = 'v' . $ver;
}
$this->output->writeln(' * ' . $link->getRel() . ' ' . $app . $ver);
}
$this->output->writeln('');
$this->output->writeln('- Resources related to Circles on <info>' . $host . '</info>');
$resource = $this->getResourceData($host, Application::APP_SUBJECT, Application::APP_REL);
$this->output->writeln(json_encode($resource, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->output->writeln('');
$tempUid = $resource->g('uid');
$this->output->writeln(
'- Confirming UID=' . $tempUid . ' from parsed Signatory at <info>' . $host . '</info>'
);
try {
/** @var RemoteInstance $remoteSignatory */
$remoteSignatory = $this->remoteStreamService->retrieveSignatory($resource->g('id'), true);
$this->output->writeln(' * No SignatureException: <info>Identity authed</info>');
} catch (SignatureException $e) {
$this->output->writeln(
'<error>' . $host . ' cannot auth its identity: ' . $e->getMessage() . '</error>'
);
return;
}
$this->output->writeln(' * Found <info>' . $remoteSignatory->getUid() . '</info>');
if ($remoteSignatory->getUid(true) !== $tempUid) {
$this->output->writeln('<error>looks like ' . $host . ' is faking its identity');
return;
}
$this->output->writeln('');
$testUrl = $resource->g('test');
$this->output->writeln('- Testing signed payload on <info>' . $testUrl . '</info>');
try {
$localSignatory = $this->remoteStreamService->getAppSignatory();
} catch (SignatoryException $e) {
$this->output->writeln(
'<error>Federated Circles not enabled locally. Please run ./occ circles:remote:init</error>'
);
return;
}
$payload = [
'test' => 42,
'token' => $this->uuid()
];
$signedRequest = $this->outgoingTest($testUrl, $payload);
$this->output->writeln(' * Payload: ');
$this->output->writeln(json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->output->writeln('');
$this->output->writeln(' * Clear Signature: ');
$this->output->writeln('<comment>' . $signedRequest->getClearSignature() . '</comment>');
$this->output->writeln('');
$this->output->writeln(' * Signed Signature (base64 encoded): ');
$this->output->writeln(
'<comment>' . base64_encode($signedRequest->getSignedSignature()) . '</comment>'
);
$this->output->writeln('');
$result = $signedRequest->getOutgoingRequest()->getResult();
$code = $result->getStatusCode();
$this->output->writeln(' * Result: ' . (($code === 200) ? '<info>' . $code . '</info>' : $code));
$this->output->writeln(
json_encode(json_decode($result->getContent(), true), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
$this->output->writeln('');
if ($this->input->getOption('all')) {
$this->output->writeln('');
$this->output->writeln('<info>### Complete report ###</info>');
$this->output->writeln(json_encode($signedRequest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->output->writeln('');
}
if ($remoteSignatory->getUid() !== $localSignatory->getUid()) {
$remoteSignatory->setInstance($host)
->setType($remoteType)
->setInterface($remoteIface);
try {
$stored = new RemoteInstance();
$this->remoteStreamService->confirmValidRemote($remoteSignatory, $stored);
$this->output->writeln(
'<info>The remote instance ' . $host
. ' is already known with this current identity</info>'
);
$this->output->writeln('- updating item');
$this->remoteStreamService->update($remoteSignatory, RemoteStreamService::UPDATE_ITEM);
if ($remoteSignatory->getType() !== $stored->getType()) {
$this->output->writeln(
'- updating type from ' . $stored->getType() . ' to '
. $remoteSignatory->getType()
);
$this->remoteStreamService->update(
$remoteSignatory, RemoteStreamService::UPDATE_TYPE
);
}
if ($remoteSignatory->getInstance() !== $stored->getInstance()) {
$this->output->writeln(
'- updating host from ' . $stored->getInstance() . ' to '
. $remoteSignatory->getInstance()
);
$this->remoteStreamService->update(
$remoteSignatory, RemoteStreamService::UPDATE_INSTANCE
);
}
if ($remoteSignatory->getId() !== $stored->getId()) {
$this->output->writeln(
'- updating href/Id from ' . $stored->getId() . ' to '
. $remoteSignatory->getId()
);
$this->remoteStreamService->update($remoteSignatory, RemoteStreamService::UPDATE_HREF);
}
} catch (RemoteUidException $e) {
$this->updateRemote($remoteSignatory);
} catch (RemoteNotFoundException $e) {
$this->saveRemote($remoteSignatory);
}
}
}
/**
* @param RemoteInstance $remoteSignatory
*
* @throws RemoteUidException
*/
private function saveRemote(RemoteInstance $remoteSignatory) {
$this->output->writeln('');
$helper = $this->getHelper('question');
$this->output->writeln(
'The remote instance <info>' . $remoteSignatory->getInstance() . '</info> looks good.'
);
$question = new ConfirmationQuestion(
'Would you like to identify this remote instance as \'<comment>' . $remoteSignatory->getType()
. '</comment>\' using interface \'<comment>'
. InterfaceService::$LIST_IFACE[$remoteSignatory->getInterface()]
. '</comment>\' ? (y/N) ',
false,
'/^(y|Y)/i'
);
if ($this->input->getOption('yes') || $helper->ask($this->input, $this->output, $question)) {
if (!$this->interfaceService->isInterfaceInternal($remoteSignatory->getInterface())) {
$remoteSignatory->setAliases([]);
}
$this->remoteRequest->save($remoteSignatory);
$this->output->writeln('<info>remote instance saved</info>');
}
}
/**
* @param RemoteInstance $remoteSignatory
*
* @throws RemoteUidException
*/
private function updateRemote(RemoteInstance $remoteSignatory): void {
$this->output->writeln('');
$helper = $this->getHelper('question');
$this->output->writeln(
'The remote instance <info>' . $remoteSignatory->getInstance()
. '</info> is known but <error>its identity has changed.</error>'
);
$this->output->writeln(
'<comment>If you are not sure on why identity changed, please say No to the next question and contact the admin of the remote instance</comment>'
);
$question = new ConfirmationQuestion(
'Do you consider this new identity as valid and update the entry in the database? (y/N) ',
false,
'/^(y|Y)/i'
);
if ($helper->ask($this->input, $this->output, $question)) {
$this->remoteStreamService->update($remoteSignatory);
$this->output->writeln('remote instance updated');
}
}
/**
* @param string $remote
* @param array $payload
*
* @return NCSignedRequest
* @throws RequestNetworkException
* @throws SignatoryException
*/
private function outgoingTest(string $remote, array $payload): NCSignedRequest {
$request = new NCRequest();
$request->basedOnUrl($remote);
$request->setFollowLocation(true);
$request->setLocalAddressAllowed(true);
$request->setTimeout(5);
$request->setData($payload);
$app = $this->remoteStreamService->getAppSignatory();
$signedRequest = $this->remoteStreamService->signOutgoingRequest($request, $app);
$outgoingRequest = $signedRequest->getOutgoingRequest();
$outgoingRequest->setLocalAddressAllowed(true);
$outgoingRequest->setFollowLocation(true);
$this->doRequest($outgoingRequest);
return $signedRequest;
}
/**
*
*/
private function checkKnownInstance(): void {
$this->verifyGSInstances();
$this->checkRemoteInstances();
}
/**
*
*/
private function verifyGSInstances(): void {
$instances = $this->globalScaleService->getGlobalScaleInstances();
$known = array_map(
function (RemoteInstance $instance): string {
return $instance->getInstance();
}, $this->remoteRequest->getFromType(RemoteInstance::TYPE_GLOBALSCALE)
);
$missing = array_diff($instances, $known);
foreach ($missing as $instance) {
$this->syncGSInstance($instance);
}
}
/**
* @param string $instance
*/
private function syncGSInstance(string $instance): void {
$this->output->write('Adding <comment>' . $instance . '</comment>: ');
if ($this->configService->isLocalInstance($instance)) {
$this->output->writeln('<comment>instance is local</comment>');
return;
}
try {
$this->remoteStreamService->addRemoteInstance(
$instance,
RemoteInstance::TYPE_GLOBALSCALE,
InterfaceService::IFACE_INTERNAL,
true
);
$this->output->writeln('<info>ok</info>');
} catch (Exception $e) {
$msg = ($e->getMessage() === '') ? '' : ' (' . $e->getMessage() . ')';
$this->output->writeln('<error>' . get_class($e) . $msg . '</error>');
}
}
private function checkRemoteInstances(): void {
$instances = $this->remoteRequest->getAllInstances();
$output = new ConsoleOutput();
$output = $output->section();
$table = new Table($output);
$table->setHeaders(['Instance', 'Type', 'iface', 'UID', 'Authed', 'Aliases']);
$table->render();
foreach ($instances as $instance) {
try {
$current = $this->remoteStreamService->retrieveRemoteInstance($instance->getInstance());
if ($current->getUid(true) === $instance->getUid(true)) {
$currentUid = '<info>' . $current->getUid(true) . '</info>';
} else {
$currentUid = '<error>' . $current->getUid(true) . '</error>';
}
} catch (Exception $e) {
$currentUid = '<error>' . $e->getMessage() . '</error>';
}
$table->appendRow(
[
$instance->getInstance(),
$instance->getType(),
InterfaceService::$LIST_IFACE[$instance->getInterface()],
$instance->getUid(),
$currentUid,
json_encode($instance->getAliases())
]
);
}
}
/**
* @throws Exception
*/
private function getRemoteType(): string {
foreach (RemoteInstance::$LIST_TYPE as $type) {
if (strtolower($this->input->getOption('type')) === strtolower($type)) {
return $type;
}
}
throw new Exception('Unknown type: ' . implode(', ', RemoteInstance::$LIST_TYPE));
}
/**
* @throws Exception
*/
private function getRemoteInterface(): int {
foreach (InterfaceService::$LIST_IFACE as $iface => $def) {
if (strtolower($this->input->getOption('iface')) === strtolower($def)) {
return $iface;
}
}
throw new Exception('Unknown interface: ' . implode(', ', InterfaceService::$LIST_IFACE));
}
}
@@ -0,0 +1,433 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OCA\Circles\Tools\Exceptions\InvalidItemException;
use OCA\Circles\Tools\Traits\TDeserialize;
use OCA\Circles\Tools\Traits\TArrayTools;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\UnknownInterfaceException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Federated\RemoteInstance;
use OCA\Circles\Model\FederatedUser;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Membership;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Model\Report;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\InterfaceService;
use OCA\Circles\Service\MemberService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesReport
*
* @package OCA\Circles\Command
*/
class CirclesReport extends Base implements IInteractiveShellClient {
use TDeserialize;
use TArrayTools;
/** @var FederatedUserService */
private $federatedUserService;
/** @var CircleService */
private $circleService;
/** @var MemberService */
private $memberService;
/** @var InterfaceService */
private $interfaceService;
/** @var ConfigService */
private $configService;
/** @var OutputInterface */
private $output;
/** @var Report */
private $report;
/**
* CirclesReport constructor.
*
* @param FederatedUserService $federatedUserService
* @param CircleService $circleService
* @param MemberService $memberService
* @param ConfigService $configService
*/
public function __construct(
FederatedUserService $federatedUserService,
CircleService $circleService,
MemberService $memberService,
InterfaceService $interfaceService,
ConfigService $configService
) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->circleService = $circleService;
$this->memberService = $memberService;
$this->interfaceService = $interfaceService;
$this->configService = $configService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:report')
->setDescription('Read and write obfuscated report')
->addOption('local', '', InputOption::VALUE_NONE, 'Use local report')
->addOption('read', '', InputOption::VALUE_REQUIRED, 'File containing the report to read', '');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws InvalidItemException
* @throws InitiatorNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
throw new \Exception('not available');
$filename = $input->getOption('read');
$local = $input->getOption('local');
$this->output = $output;
$report = null;
if ($filename === '' || $local) {
$report = $this->generateReport();
if (!$local) {
$this->output->writeln(json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
return 0;
}
}
if ($filename !== '') {
/** @var Report $report */
$data = file_get_contents($filename);
$report = $this->deserialize(json_decode($data, true), Report::class);
}
if (!is_null($report)) {
$this->readReport($input, $report);
}
return 0;
}
/**
* @throws InitiatorNotFoundException
* @throws UnknownInterfaceException
*/
private function generateReport(): Report {
$report = new Report();
$report->setSource($this->interfaceService->getLocalInstance());
$this->federatedUserService->bypassCurrentUserCondition(true);
$probe = new CircleProbe();
$probe->includeSystemCircles();
$raw = $this->circleService->getCircles($probe);
$circles = [];
foreach ($raw as $circle) {
$circle->getMembers();
$circles[] = $this->obfuscateCircle($circle);
}
$report->setCircles($circles);
return $report;
}
/**
* @param InputInterface $input
* @param Report $report
*/
private function readReport(InputInterface $input, Report $report) {
$output = new ConsoleOutput();
$this->output = $output->section();
$this->report = $report;
$interactiveShell = new NC22InteractiveShell($this, $input, $this);
$commands = [
'circles.list',
'circles.delete.#circleId',
'members.list.#circleId',
'members.details.#memberId',
'remoteInstance.list'
];
$interactiveShell->setCommands($commands);
$interactiveShell->run();
}
/**
* @param string $source
* @param string $field
*
* @return string[]
*/
public function fillCommandList(string $source, string $field): array {
echo $source . ' ' . $field . "\n";
return ['abcd', 'abdde', 'erfg'];
}
/**
* @param string $command
*/
public function manageCommand(string $command): void {
// echo $command . "\n";
}
/**
* @param Circle $circle
*
* @return Circle
*/
private function obfuscateCircle(Circle $circle): Circle {
$singleId = $this->obfuscateId($circle->getSingleId());
$circle->setSingleId($singleId)
->setName($singleId)
->setDisplayName($singleId)
->setDescription('')
->setCreation(0);
if ($circle->hasOwner()) {
$circle->setOwner($this->obfuscateMember($circle->getOwner()));
}
if ($circle->hasInitiator()) {
$circle->setInitiator($this->obfuscateMember($circle->getInitiator()));
}
if ($circle->hasMembers()) {
$members = [];
foreach ($circle->getMembers() as $member) {
$members[] = $this->obfuscateMember($member);
}
$circle->setMembers($members);
}
if ($circle->hasMemberships()) {
$memberships = [];
foreach ($circle->getMemberships() as $membership) {
$memberships[] = $this->obfuscateMembership($membership);
}
$circle->setMemberships($memberships);
}
return $circle;
}
/**
* @param Member $member
*
* @return Member
*/
private function obfuscateMember(Member $member): Member {
$memberId = $this->obfuscateId($member->getId());
$singleId = $this->obfuscateId($member->getSingleId());
$circleId = $this->obfuscateId($member->getCircleId());
$member->setSingleId($singleId)
->setCircleId($circleId)
->setId($memberId)
->setUserId($singleId)
->setDisplayName($singleId)
->setDisplayUpdate(0)
->setNotes('')
->setContactId('')
->setContactMeta('')
->setJoined(0);
if ($member->hasCircle()) {
$member->setCircle($this->obfuscateCircle($member->getCircle()));
}
if ($member->hasBasedOn()) {
$member->setBasedOn($this->obfuscateCircle($member->getBasedOn()));
}
if ($member->hasInheritedBy()) {
$member->setInheritedBy($this->obfuscateFederatedUser($member->getInheritedBy()));
}
if ($member->hasInheritanceFrom()) {
$member->setInheritanceFrom($this->obfuscateMember($member->getInheritanceFrom()));
}
if ($member->hasRemoteInstance()) {
$member->setRemoteInstance($this->obfuscateRemoteInstance($member->getRemoteInstance()));
}
if ($member->hasMemberships()) {
$memberships = [];
foreach ($member->getMemberships() as $membership) {
$memberships[] = $this->obfuscateMembership($membership);
}
$member->setMemberships($memberships);
}
return $member;
}
/**
* @param FederatedUser $federatedUser
*
* @return FederatedUser
*/
private function obfuscateFederatedUser(FederatedUser $federatedUser): FederatedUser {
$singleId = $this->obfuscateId($federatedUser->getSingleId());
$federatedUser->setSingleId($singleId)
->setUserId($singleId);
if ($federatedUser->hasBasedOn()) {
$federatedUser->setBasedOn($this->obfuscateCircle($federatedUser->getBasedOn()));
}
// what was that for ?
// if ($federatedUser->hasLink()) {
// $federatedUser->setLink($this->obfuscateMembership($federatedUser->getLink()));
// }
if ($federatedUser->hasMemberships()) {
$memberships = [];
foreach ($federatedUser->getMemberships() as $membership) {
$memberships[] = $this->obfuscateMembership($membership);
}
$federatedUser->setMemberships($memberships);
}
return $federatedUser;
}
/**
* @param Membership $membership
*
* @return Membership
*/
private function obfuscateMembership(Membership $membership): Membership {
$membership->setSingleId($this->obfuscateId($membership->getSingleId()));
$membership->setCircleId($this->obfuscateId($membership->getCircleId()));
$membership->setInheritanceFirst($this->obfuscateId($membership->getInheritanceFirst()));
$membership->setInheritanceLast($this->obfuscateId($membership->getInheritanceLast()));
$path = [];
foreach ($membership->getInheritancePath() as $item) {
$path[] = $this->obfuscateId($item);
}
$membership->setInheritancePath($path);
return $membership;
}
/**
* @param RemoteInstance $remoteInstance
*
* @return RemoteInstance
*/
private function obfuscateRemoteInstance(RemoteInstance $remoteInstance): RemoteInstance {
return $remoteInstance;
}
/**
* @param string $id
*
* @return string
*/
private function obfuscateId(string $id): string {
return substr($id, 0, 5) . '.' . md5(substr($id, 5));
}
/**
* @param NC22InteractiveShellSession $session
*/
public function onNewPrompt(NC22InteractiveShellSession $session): void {
$prompt =
'Circles Report [<info>' . $this->report->getSource() . '</info>]:<comment>%PATH%</comment>';
$commands = [];
if ($session->getData()->g('currentStatus') === 'write') {
$commands[] = 'cancel';
$commands[] = 'write';
$prompt .= '<error>#</error> ';
} else {
$commands[] = 'edit';
$prompt .= '$ ';
}
$session->setGlobalCommands($commands)
->setPrompt($prompt);
}
/**
* @param NC22InteractiveShellSession $session
* @param $command
*/
public function onNewCommand(NC22InteractiveShellSession $session, $command): void {
echo $session->getPath();
}
}
@@ -0,0 +1,211 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedEventException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Helpers\MemberHelper;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCP\Security\IHasher;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CirclesSetting extends Base {
private IHasher $hasher;
private FederatedUserService $federatedUserService;
private CircleService $circleService;
private ConfigService $configService;
public function __construct(
IHasher $hasher,
FederatedUserService $federatedUserService,
CircleService $circlesService,
ConfigService $configService
) {
parent::__construct();
$this->hasher = $hasher;
$this->federatedUserService = $federatedUserService;
$this->circleService = $circlesService;
$this->configService = $configService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:manage:setting')
->setDescription('edit setting for a Circle')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addArgument('setting', InputArgument::OPTIONAL, 'setting to edit', '')
->addArgument('value', InputArgument::OPTIONAL, 'value', '')
->addOption('unset', '', InputOption::VALUE_NONE, 'unset the setting')
->addOption(
'test-password', '', InputOption::VALUE_REQUIRED,
'test and compare password with hash (in case of static password)'
)
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotFoundException
* @throws RequestBuilderException
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotConfirmedException
* @throws InvalidIdException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = (string)$input->getArgument('circle_id');
$setting = (string)$input->getArgument('setting');
$value = (string)$input->getArgument('value');
try {
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
false
);
if ($setting === '') {
$circle = $this->circleService->getCircle($circleId);
$initiatorHelper = new MemberHelper($circle->getInitiator());
$initiatorHelper->mustBeAdmin();
$output->writeln(json_encode($circle->getSettings(), JSON_PRETTY_PRINT));
$testPassword = $input->getOption('test-password');
if ($testPassword !== null) {
$this->testPassword($output, $circle, $testPassword);
}
return 0;
}
if (!$input->getOption('unset') && $value === '') {
throw new InvalidArgumentException('you need to specify a value');
}
$outcome = $this->circleService->updateSetting(
$circleId,
$setting,
($input->getOption('unset')) ? null : $value,
);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
private function testPassword(OutputInterface $output, Circle $circle, string $testPassword): void {
$output->writeln('');
$output->write('Password enforced for this Circle: ');
if (!$this->configService->enforcePasswordOnSharedFile($circle)) {
$output->writeln('<error>no</error>');
return;
}
$output->writeln('<info>yes</info>');
$output->write('Single password is configured for this Circle: ');
if (!$this->configService->isSinglePasswordAvailable($circle)) {
$output->writeln('<error>no</error>');
return;
}
$output->writeln('<info>yes</info>');
$output->write('Comparing password with hashed version in database: ');
if ($this->hasher->verify($testPassword, $circle->getSettings()['password_single'])) {
$output->writeln('<info>ok</info>');
} else {
$output->writeln('<error>fail</error>');
}
}
}
@@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\ContactAddressBookNotFoundException;
use OCA\Circles\Exceptions\ContactFormatException;
use OCA\Circles\Exceptions\ContactNotFoundException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MigrationException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\MigrationService;
use OCA\Circles\Service\OutputService;
use OCA\Circles\Service\SyncService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class CirclesSync
*
* @package OCA\Circles\Command
*/
class CirclesSync extends Base {
/** @var SyncService */
private $syncService;
/** @var MigrationService */
private $migrationService;
/** @var OutputService */
private $outputService;
/** @var ConfigService */
private $configService;
/**
* CirclesSync constructor.
*
* @param SyncService $syncService
* @param OutputService $outputService
* @param ConfigService $configService
*/
public function __construct(
SyncService $syncService,
MigrationService $migrationService,
OutputService $outputService,
ConfigService $configService
) {
parent::__construct();
$this->syncService = $syncService;
$this->migrationService = $migrationService;
$this->outputService = $outputService;
$this->configService = $configService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:sync')
->setDescription('Sync Circles and Members')
->addOption('migration', '', InputOption::VALUE_NONE, 'Migrate from Circles 0.21.0')
->addOption('force', '', InputOption::VALUE_NONE, 'Force migration')
->addOption('force-run', '', InputOption::VALUE_NONE, 'Force migration run')
->addOption('apps', '', InputOption::VALUE_NONE, 'Sync Apps')
->addOption('users', '', InputOption::VALUE_NONE, 'Sync Nextcloud Account')
->addOption('groups', '', InputOption::VALUE_NONE, 'Sync Nextcloud Groups')
->addOption('contacts', '', InputOption::VALUE_NONE, 'Sync Contacts')
->addOption('remotes', '', InputOption::VALUE_NONE, 'Sync Remotes')
->addOption('global-scale', '', InputOption::VALUE_NONE, 'Sync GlobalScale');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws ContactAddressBookNotFoundException
* @throws ContactFormatException
* @throws ContactNotFoundException
* @throws FederatedUserException
* @throws InvalidIdException
* @throws MigrationException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$this->outputService->setOccOutput($output);
if ($input->getOption('migration')) {
if ($input->getOption('force-run')) {
$this->configService->setAppValue(ConfigService::MIGRATION_RUN, '0');
}
$this->migrationService->migration($input->getOption('force'));
$output->writeln('');
$output->writeln('Migration done');
return 0;
}
$output->writeln('<comment>This process requires a lot of memory.</comment>');
$output->writeln('<comment>If it crash, please restart it and it will continue where it stopped.</comment>');
$output->writeln('');
$sync = $this->filterSync($input);
$this->syncService->sync($sync);
$output->writeln('');
$output->writeln('Sync done');
return 0;
}
private function filterSync(InputInterface $input): int {
$sync = 0;
if ($input->getOption('apps')) {
$sync += SyncService::SYNC_APPS;
}
if ($input->getOption('users')) {
$sync += SyncService::SYNC_USERS;
}
if ($input->getOption('groups')) {
$sync += SyncService::SYNC_GROUPS;
}
if ($input->getOption('global-scale')) {
$sync += SyncService::SYNC_GLOBALSCALE;
}
if ($input->getOption('remotes')) {
$sync += SyncService::SYNC_REMOTES;
}
if ($input->getOption('contacts')) {
$sync += SyncService::SYNC_CONTACTS;
}
if ($sync === 0) {
$sync = SyncService::SYNC_ALL;
}
return $sync;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,148 @@
<?php
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use Exception;
use OC\Core\Command\Base;
use OCA\Circles\Db\DeprecatedCirclesRequest;
use OCA\Circles\Exceptions\CommandMissingArgumentException;
use OCA\Circles\Exceptions\FakeException;
use OCP\IL10N;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Groups extends Base {
/** @var IL10N */
private $l10n;
/** @var DeprecatedCirclesRequest */
private $circlesRequest;
/**
* Groups constructor.
*
* @param IL10N $l10n
* @param DeprecatedCirclesRequest $circlesRequest
*/
public function __construct(IL10N $l10n, DeprecatedCirclesRequest $circlesRequest) {
parent::__construct();
$this->l10n = $l10n;
$this->circlesRequest = $circlesRequest;
}
protected function configure() {
parent::configure();
$this->setName('circles:groups')
->setDescription('manage the linked groups')
->addOption('list', 'l', InputOption::VALUE_NONE, 'list all linked group')
->addOption('link', 'a', InputOption::VALUE_NONE, 'link a group to a circle')
->addOption('unlink', 'd', InputOption::VALUE_NONE, 'unlink a group from a circle')
->addArgument('circle_id', InputArgument::OPTIONAL, 'id of the circle')
->addArgument('group', InputArgument::OPTIONAL, 'name of the group');
}
protected function execute(InputInterface $input, OutputInterface $output) {
try {
$this->listLinkedGroups($input, $output);
$this->addLinkedGroups($input, $output);
$this->delLinkedGroups($input, $output);
} catch (FakeException $e) {
$output->writeln('done');
} catch (Exception $e) {
$output->writeln($e->getMessage());
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @throws FakeException
*/
private function listLinkedGroups(InputInterface $input, OutputInterface $output) {
if ($input->getOption('list') !== true) {
return ;
}
throw new FakeException();
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @throws FakeException
* @throws CommandMissingArgumentException
*/
private function addLinkedGroups(InputInterface $input, OutputInterface $output) {
if ($input->getOption('link') !== true) {
return;
}
[$circleId, $group] = $this->getCircleIdAndGroupFromArguments($input);
throw new FakeException();
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @throws FakeException
*/
private function delLinkedGroups(InputInterface $input, OutputInterface $output) {
if ($input->getOption('unlink') !== true) {
return;
}
[$circleId, $group] = $this->getCircleIdAndGroupFromArguments($input);
throw new FakeException();
}
private function getCircleIdAndGroupFromArguments(InputInterface $input) {
if ($input->getArgument('circle_id') === null
|| $input->getArgument('group') === null) {
throw new CommandMissingArgumentException(
);
// $this->l10n->t(
// 'Missing argument: {cmd} circle_id group', ['cmd' => './occ circles:link']
// )
}
return [$input->getArgument('circle_id'), $input->getArgument('group')];
}
}
@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OCA\Circles\Tools\Exceptions\RequestContentException;
use OCA\Circles\Tools\Exceptions\RequestNetworkException;
use OCA\Circles\Tools\Exceptions\RequestResultNotJsonException;
use OCA\Circles\Tools\Exceptions\RequestResultSizeException;
use OCA\Circles\Tools\Exceptions\RequestServerException;
use OCA\Circles\Tools\Model\NCRequest;
use OCA\Circles\Tools\Model\Request;
use Exception;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\GSStatusException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MemberService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class MembersAdd
*
* @package OCA\Circles\Command
*/
class MembersAdd extends Base {
/** @var FederatedUserService */
private $federatedUserService;
/** @var MemberService */
private $memberService;
/** @var ConfigService */
private $configService;
/**
* MembersAdd constructor.
*
* @param FederatedUserService $federatedUserService
* @param MemberService $memberService
* @param ConfigService $configService
*/
public function __construct(
FederatedUserService $federatedUserService,
MemberService $memberService,
ConfigService $configService
) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->memberService = $memberService;
$this->configService = $configService;
}
protected function configure() {
parent::configure();
$this->setName('circles:members:add')
->setDescription('Add a member to a Circle')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addArgument('user', InputArgument::REQUIRED, 'username of the member')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception')
->addOption('type', '', InputOption::VALUE_REQUIRED, 'type of the user', '0');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$circleId = $input->getArgument('circle_id');
$userId = $input->getArgument('user');
$type = $input->getOption('type');
if ($type !== '0') {
$type = Member::parseTypeString($type);
}
try {
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
false
);
$federatedUser = $this->federatedUserService->generateFederatedUser($userId, (int)$type);
$outcome = $this->memberService->addMember($circleId, $federatedUser);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
/**
* @param string $search
* @param string $instance
*
* @return string
*/
private function findUserFromLookup(string $search, string &$instance = ''): string {
$userId = '';
/** @var string $lookup */
try {
$lookup = $this->configService->getGSLookup();
} catch (GSStatusException $e) {
return '';
}
$request = new NCRequest(ConfigService::GS_LOOKUP_USERS, Request::TYPE_GET);
$this->configService->configureRequest($request);
$request->basedOnUrl($lookup);
$request->addParam('search', $search);
try {
$users = $this->retrieveJson($request);
} catch (
RequestContentException |
RequestNetworkException |
RequestResultSizeException |
RequestServerException |
RequestResultNotJsonException $e
) {
return '';
}
$result = [];
foreach ($users as $user) {
if (!array_key_exists('userid', $user)) {
continue;
}
[, $host] = explode('@', $user['federationId']);
if (strtolower($user['userid']['value']) === strtolower($search)) {
$userId = $user['userid']['value'];
$instance = $host;
}
$result[] = $user['userid']['value'] . ' <info>@' . $host . '</info>';
}
// if ($userId === '') {
// foreach($result as $item) {
// $output->writeln($item);
// }
// }
return $userId;
}
}
@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Db\MemberRequest;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MemberService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class MembersDetails
*
* @package OCA\Circles\Command
*/
class MembersDetails extends Base {
/** @var MemberRequest */
private $memberRequest;
/** @var FederatedUserService */
private $federatedUserService;
/** @var MemberService */
private $memberService;
/**
* MembersDetails constructor.
*
* @param MemberRequest $memberRequest
* @param FederatedUserService $federatedUserService
* @param MemberService $memberService
*/
public function __construct(
MemberRequest $memberRequest, FederatedUserService $federatedUserService, MemberService $memberService
) {
parent::__construct();
$this->memberRequest = $memberRequest;
$this->federatedUserService = $federatedUserService;
$this->memberService = $memberService;
}
protected function configure() {
parent::configure();
$this->setName('circles:members:details')
->setDescription('get details about a member by its ID')
->addArgument('member_id', InputArgument::REQUIRED, 'ID of the member')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotFoundException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
* @throws FederatedItemException
* @throws InvalidIdException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$memberId = $input->getArgument('member_id');
$circleId = $this->memberRequest->getMemberById($memberId)->getCircleId();
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
false
);
$member = $this->memberService->getMemberById($memberId);
$output->writeln(json_encode($member, JSON_PRETTY_PRINT));
return 0;
}
}
@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Db\MemberRequest;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedEventException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\ParseMemberLevelException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MemberService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class MembersLevel
*
* @package OCA\Circles\Command
*/
class MembersLevel extends Base {
/** @var MemberRequest */
private $memberRequest;
/** @var FederatedUserService */
private $federatedUserService;
/** @var MemberService */
private $memberService;
/**
* MembersLevel constructor.
*
* @param MemberRequest $memberRequest
* @param FederatedUserService $federatedUserService
* @param MemberService $memberService
*/
public function __construct(
MemberRequest $memberRequest, FederatedUserService $federatedUserService, MemberService $memberService
) {
parent::__construct();
$this->memberRequest = $memberRequest;
$this->federatedUserService = $federatedUserService;
$this->memberService = $memberService;
}
protected function configure() {
parent::configure();
$this->setName('circles:members:level')
->setDescription('Change the level of a member from a Circle')
->addArgument('member_id', InputArgument::REQUIRED, 'ID of the member from the Circle')
->addOption('circle', '', InputOption::VALUE_REQUIRED, 'ID of the circle', '')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception')
->addArgument('level', InputArgument::REQUIRED, 'new level');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotFoundException
* @throws RequestBuilderException
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotConfirmedException
* @throws InvalidIdException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws ParseMemberLevelException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$memberId = $input->getArgument('member_id');
$circleId = $input->getOption('circle');
try {
if ($circleId === '') {
$circleId = $this->memberRequest->getMemberById($memberId)->getCircleId();
}
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$circleId,
false
);
$this->memberService->getMemberById($memberId, $circleId);
$level = Member::parseLevelString($input->getArgument('level'));
$outcome = $this->memberService->memberLevel($memberId, $level);
} catch (FederatedItemException $e) {
if ($input->getOption('status-code')) {
throw new FederatedItemException(
' [' . get_class($e) . ', ' . $e->getStatus() . ']' . "\n" . $e->getMessage()
);
}
throw $e;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
}
@@ -0,0 +1,473 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use Exception;
use OC\Core\Command\Base;
use OCA\Circles\Db\MemberRequest;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\CircleService;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MemberService;
use OCA\Circles\Service\RemoteService;
use OCA\Circles\Tools\Exceptions\InvalidItemException;
use OCA\Circles\Tools\Exceptions\ItemNotFoundException;
use OCA\Circles\Tools\Exceptions\RequestNetworkException;
use OCA\Circles\Tools\Exceptions\SignatoryException;
use OCA\Circles\Tools\Exceptions\UnknownTypeException;
use OCA\Circles\Tools\Model\SimpleDataStore;
use OCA\Circles\Tools\Model\TreeNode;
use OCA\Circles\Tools\Traits\TConsoleTree;
use OCA\Circles\Tools\Traits\TStringTools;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class MembersList
*
* @package OCA\Circles\Command
*/
class MembersList extends Base {
use TConsoleTree;
use TStringTools;
/** @var MemberRequest */
private $memberRequest;
/** @var FederatedUserService */
private $federatedUserService;
/** @var RemoteService */
private $remoteService;
/** @var CircleService */
private $circleService;
/** @var MemberService */
private $memberService;
/** @var ConfigService */
private $configService;
/** @var InputInterface */
private $input;
/** @var string */
private $treeType = '';
/**
* MembersList constructor.
*
* @param MemberRequest $memberRequest
* @param FederatedUserService $federatedUserService
* @param RemoteService $remoteService
* @param CircleService $circleService
* @param MemberService $memberService
* @param ConfigService $configService
*/
public function __construct(
MemberRequest $memberRequest, FederatedUserService $federatedUserService,
RemoteService $remoteService, CircleService $circleService, MemberService $memberService,
ConfigService $configService
) {
parent::__construct();
$this->memberRequest = $memberRequest;
$this->federatedUserService = $federatedUserService;
$this->remoteService = $remoteService;
$this->circleService = $circleService;
$this->memberService = $memberService;
$this->configService = $configService;
}
protected function configure() {
parent::configure();
$this->setName('circles:members:list')
->setDescription('listing Members from a Circle')
->addArgument('circle_id', InputArgument::REQUIRED, 'ID of the circle')
->addOption('instance', '', InputOption::VALUE_REQUIRED, 'Instance of the circle', '')
->addOption('inherited', '', InputOption::VALUE_NONE, 'Display all inherited members')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0')
->addOption('display-name', '', InputOption::VALUE_NONE, 'display the displayName')
->addOption('tree', '', InputOption::VALUE_OPTIONAL, 'display members as a tree', false);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws CircleNotFoundException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotFoundException
* @throws InvalidIdException
* @throws InvalidItemException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestNetworkException
* @throws SignatoryException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
* @throws FederatedItemException
* @throws SingleCircleNotFoundException
* @throws RequestBuilderException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$this->input = $input;
$circleId = $input->getArgument('circle_id');
$instance = $input->getOption('instance');
$initiator = $input->getOption('initiator');
$initiatorType = Member::parseTypeString($input->getOption('initiator-type'));
$inherited = $input->getOption('inherited');
$tree = null;
if ($input->getOption('tree') !== false) {
$this->treeType = (is_null($input->getOption('tree'))) ? 'all' : $input->getOption('tree');
$this->federatedUserService->commandLineInitiator($initiator, $initiatorType, $circleId, true);
$circle = $this->circleService->getCircle($circleId);
$output->writeln('<info>Name</info>: ' . $circle->getName());
$owner = $circle->getOwner();
$output->writeln('<info>Owner</info>: ' . $owner->getUserId() . '@' . $owner->getInstance());
$type = implode(", ", Circle::getCircleFlags($circle, Circle::FLAGS_LONG));
$output->writeln('<info>Config</info>: ' . $type);
$output->writeln(' ');
$tree = new TreeNode(null, new SimpleDataStore(['circle' => $circle]));
$inherited = false;
}
if ($inherited) {
$this->federatedUserService->commandLineInitiator($initiator, $initiatorType, $circleId, true);
$circle = $this->circleService->getCircle($circleId);
$members = $circle->getInheritedMembers(true);
} else {
$members = $this->getMembers($circleId, $instance, $initiator, $initiatorType, $tree);
}
if (!is_null($tree)) {
$this->drawTree(
$tree, [$this, 'displayLeaf'],
[
'height' => 3,
'node-spacing' => 1,
'item-spacing' => 0,
]
);
return 0;
}
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($members, JSON_PRETTY_PRINT));
return 0;
}
$output = new ConsoleOutput();
$output = $output->section();
$table = new Table($output);
$table->setHeaders(
[
'Circle Id', 'Circle Name', 'Member Id', 'Single Id', 'Type', 'Source',
'Username', 'Level', 'Invited By'
]
);
$table->render();
foreach ($members as $member) {
if ($member->getCircleId() === $circleId) {
$level = $member->getLevel();
} else {
$level = $member->getInheritanceFrom()->getLevel();
}
$table->appendRow(
[
$member->getCircleId(),
$member->getCircle()->getDisplayName(),
$member->getId(),
$member->getSingleId(),
Member::$TYPE[$member->getUserType()],
$member->hasBasedOn() ? Circle::$DEF_SOURCE[$member->getBasedOn()->getSource()] : '',
$this->configService->displayFederatedUser(
$member,
$this->input->getOption('display-name')
),
($level > 0) ? Member::$DEF_LEVEL[$level] :
'(' . strtolower($member->getStatus()) . ')',
($member->hasInvitedBy()) ? $this->configService->displayFederatedUser(
$member->getInvitedBy(),
$this->input->getOption('display-name')
) : 'Unknown'
]
);
}
return 0;
}
/**
* @param string $circleId
* @param string $instance
* @param string $initiator
* @param int $initiatorType
* @param TreeNode|null $tree
* @param array $knownIds
*
* @return array
* @throws CircleNotFoundException
* @throws FederatedItemException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotFoundException
* @throws InvalidIdException
* @throws InvalidItemException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestBuilderException
* @throws RequestNetworkException
* @throws SignatoryException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
*/
private function getMembers(
string $circleId,
string $instance,
string $initiator,
int $initiatorType,
?TreeNode $tree,
array $knownIds = []
): array {
if (in_array($circleId, $knownIds)) {
return [];
}
$knownIds[] = $circleId;
if (!$this->configService->isLocalInstance($instance)) {
$data = [];
if ($initiator) {
$data['initiator'] = $this->federatedUserService->getFederatedUser(
$initiator,
Member::TYPE_USER
);
}
try {
$members = $this->remoteService->getMembersFromInstance($circleId, $instance, $data);
} catch (RemoteInstanceException $e) {
return [];
}
} else {
$this->federatedUserService->commandLineInitiator($initiator, $initiatorType, $circleId, true);
$members = $this->memberService->getMembers($circleId);
}
if (!is_null($tree)) {
foreach ($members as $member) {
if ($member->getUserType() === Member::TYPE_CIRCLE) {
if (!$this->configService->isLocalInstance($member->getInstance())) {
$data = [];
if ($initiator) {
$data['initiator'] = $this->federatedUserService->getFederatedUser(
$initiator,
Member::TYPE_USER
);
}
$circle = null;
try {
$circle = $this->remoteService->getCircleFromInstance(
$member->getSingleId(), $member->getInstance(), $data
);
} catch (CircleNotFoundException | RemoteInstanceException $e) {
}
} else {
$this->federatedUserService->commandLineInitiator(
$initiator,
$initiatorType,
$member->getSingleId(),
true
);
$circle = $this->circleService->getCircle($member->getSingleId());
}
$node = new TreeNode(
$tree, new SimpleDataStore(
[
'circle' => $circle,
'member' => $member,
'cycling' => in_array($member->getSingleId(), $knownIds),
]
)
);
$this->getMembers(
$member->getSingleId(),
$member->getInstance(),
$initiator,
$initiatorType,
$node,
$knownIds
);
} else {
if ($this->treeType !== 'circles-only') {
new TreeNode(
$tree, new SimpleDataStore(
[
'member' => $member,
'cycling' => in_array($member->getSingleId(), $knownIds)
]
)
);
}
}
}
}
return $members;
}
/**
* @param SimpleDataStore $data
* @param int $lineNumber
*
* @return string
* @throws OwnerNotFoundException
*/
public function displayLeaf(SimpleDataStore $data, int $lineNumber): string {
if ($lineNumber === 3) {
return ($data->gBool('cycling')) ? '<comment>(loop detected)</comment>' : '';
}
try {
$line = '';
$circle = null;
if ($data->hasKey('circle')) {
/** @var Circle $circle */
try {
$circle = $data->gObj('circle', Circle::class);
} catch (Exception $e) {
}
}
if ($data->hasKey('member')) {
/** @var Member $member */
$member = $data->gObj('member', Member::class);
if ($lineNumber === 1) {
$line .= '<info>' . $member->getSingleId() . '</info>';
if (!$this->configService->isLocalInstance($member->getInstance())) {
$line .= '@' . $member->getInstance();
}
$line .= ' (' . Member::$DEF_LEVEL[$member->getLevel()] . ')';
$line .= ' <info>MemberId</info>: ' . $member->getId();
$line .= ' <info>Name</info>: ' . $this->configService->displayFederatedUser(
$member,
$this->input->getOption('display-name')
);
if ($member->hasBasedOn()) {
$line .= ' <info>Source</info>: '
. Circle::$DEF_SOURCE[$member->getBasedOn()->getSource()];
} else {
$line .= ' <info>Type</info>: ' . Member::$TYPE[$member->getUserType()];
}
}
if ($lineNumber === 2) {
if (is_null($circle)) {
if ($member->getUserType() === Member::TYPE_CIRCLE) {
$line .= '<comment>(out of bounds)</comment>';
}
return $line;
}
$owner = $circle->getOwner();
$line .= '<info>Owner</info>: ' . $owner->getUserId() . '@' . $owner->getInstance();
$type = implode(", ", Circle::getCircleFlags($circle, Circle::FLAGS_LONG));
$line .= ($type === '') ? '' : ' <info>Config</info>: ' . $type;
}
} else {
if ($lineNumber === 1 && !is_null($circle)) {
$line .= '<info>' . $circle->getSingleId() . '</info>';
if (!$this->configService->isLocalInstance($circle->getInstance())) {
$line .= '@' . $circle->getInstance();
}
}
}
return $line;
} catch (InvalidItemException | ItemNotFoundException | UnknownTypeException $e) {
}
return '';
}
}
@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\Db\MemberRequest;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedEventException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Exceptions\UnknownRemoteException;
use OCA\Circles\Exceptions\UserTypeNotFoundException;
use OCA\Circles\Model\Member;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MemberService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class MembersRemove
*
* @package OCA\Circles\Command
*/
class MembersRemove extends Base {
/** @var MemberRequest */
private $memberRequest;
/** @var FederatedUserService */
private $federatedUserService;
/** @var MemberService */
private $memberService;
/**
* MembersRemove constructor.
*
* @param MemberRequest $memberRequest
* @param FederatedUserService $federatedUserService
* @param MemberService $memberService
*/
public function __construct(
MemberRequest $memberRequest, FederatedUserService $federatedUserService, MemberService $memberService
) {
parent::__construct();
$this->memberRequest = $memberRequest;
$this->federatedUserService = $federatedUserService;
$this->memberService = $memberService;
}
protected function configure() {
parent::configure();
$this->setName('circles:members:remove')
->setDescription('remove a member from a circle')
->addArgument('member_id', InputArgument::REQUIRED, 'ID of the member from the Circle')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('initiator-type', '', InputOption::VALUE_REQUIRED, 'set initiator type', '0');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws CircleNotFoundException
* @throws FederatedEventException
* @throws FederatedItemException
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InitiatorNotConfirmedException
* @throws InitiatorNotFoundException
* @throws InvalidIdException
* @throws MemberNotFoundException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
* @throws UnknownRemoteException
* @throws UserTypeNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$memberId = $input->getArgument('member_id');
$member = $this->memberRequest->getMemberById($memberId);
$this->federatedUserService->commandLineInitiator(
$input->getOption('initiator'),
Member::parseTypeString($input->getOption('initiator-type')),
$member->getCircleId(),
false
);
$outcome = $this->memberService->removeMember($memberId);
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
}
return 0;
}
}
@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OC\Core\Command\Base;
use OCA\Circles\IFederatedUser;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\SearchResult;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\SearchService;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class MembersSearch
*
* @package OCA\Circles\Command
*/
class MembersSearch extends Base {
/** @var SearchService */
private $searchService;
/** @var ConfigService */
private $configService;
/**
* MembersSearch constructor.
*
* @param SearchService $searchService
* @param ConfigService $configService
*/
public function __construct(SearchService $searchService, ConfigService $configService) {
parent::__construct();
$this->searchService = $searchService;
$this->configService = $configService;
}
protected function configure() {
parent::configure();
$this->setName('circles:members:search')
->setDescription('Change the level of a member from a Circle')
->addArgument('term', InputArgument::REQUIRED, 'term to search')
->addOption('initiator', '', InputOption::VALUE_REQUIRED, 'set an initiator to the request', '')
->addOption('status-code', '', InputOption::VALUE_NONE, 'display status code on exception');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$result = $this->searchService->search($input->getArgument('needle'));
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($result, JSON_PRETTY_PRINT));
}
$this->displaySearchResult($result);
return 0;
}
/**
* @param list<IFederatedUser|SearchResult> $result
*/
private function displaySearchResult(array $result) {
$output = new ConsoleOutput();
$output = $output->section();
$table = new Table($output);
$table->setHeaders(['SingleId', 'UserId', 'UserType', 'Instance']);
$table->render();
foreach ($result as $entry) {
if (!$result instanceof IFederatedUser) {
continue;
}
$table->appendRow(
[
$entry->getSingleId(),
$entry->getUserId(),
Member::$TYPE[$entry->getUserType()],
$this->configService->displayInstance($entry->getInstance())
]
);
}
}
}
@@ -0,0 +1,320 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use OCA\Circles\Tools\Traits\TArrayTools;
use OC\Core\Command\Base;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\InvalidIdException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Model\ShareWrapper;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\ShareWrapperService;
use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class SharesFilesList
*
* @package OCA\Circles\Command
*/
class SharesFiles extends Base {
use TArrayTools;
/** @var FederatedUserService */
private $federatedUserService;
/** @var ShareWrapperService */
private $shareWrapperService;
/** @var ConfigService */
private $configService;
/** @var int */
private $fileId = 0;
/**
* SharesFilesList constructor.
*
* @param FederatedUserService $federatedUserService
* @param ShareWrapperService $shareWrapperService
* @param ConfigService $configService
*/
public function __construct(
FederatedUserService $federatedUserService, ShareWrapperService $shareWrapperService,
ConfigService $configService
) {
parent::__construct();
$this->federatedUserService = $federatedUserService;
$this->shareWrapperService = $shareWrapperService;
$this->configService = $configService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('circles:shares:files')
->setDescription('listing shares files')
->addArgument('file_id', InputArgument::OPTIONAL, 'filter on a File Id', '0')
->addOption('to', '', InputOption::VALUE_REQUIRED, 'get files shared TO CIRCLEID', '')
->addOption('with', '', InputOption::VALUE_REQUIRED, 'get files shared WITH USERID', '')
->addOption('by', '', InputOption::VALUE_REQUIRED, 'get files shared BY USERID', '')
->addOption('all', '', InputOption::VALUE_NONE, 'get all data about the shares');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InvalidIdException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$this->fileId = (int)$input->getArgument('file_id');
$json = (strtolower($input->getOption('output')) === 'json');
$this->displayShares(
(int)$input->getArgument('file_id'),
$input->getOption('to'),
$input->getOption('with'),
$input->getOption('by'),
$input->getOption('all'),
$json
);
return 0;
//
// if ($input->getOption('to')) {
// $this->sharedToCircle(
// $input->getOption('to'),
// $input->getOption('with'),
// $input->getOption('by'),
// $input->getOption('all'),
// $json
// );
//
// return 0;
// }
//
// if ($input->getOption('with')) {
// $this->sharedWith($input->getOption('with'), $json);
//
// return 0;
// }
//
// if ($input->getOption('by')) {
// $this->sharesBy($input->getOption('by'), $json);
//
// return 0;
// }
//
// if ($this->fileId > 0) {
// $this->sharedFile($json);
//
// return 0;
// }
}
/**
* @param int $fileId
* @param string $to
* @param string $with
* @param string $by
* @param bool $all
* @param bool $json
*
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InvalidIdException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
*/
private function displayShares(
int $fileId,
string $to,
string $with,
string $by,
bool $all,
bool $json
): void {
$shareWrappers = $this->getShares($fileId, $to, $with, $by, $all, $filterRecipient);
$output = new ConsoleOutput();
if ($json) {
$output->writeln(json_encode($shareWrappers, JSON_PRETTY_PRINT));
return;
}
$output = $output->section();
$table = new Table($output);
$headers = [
'Share Id',
'File Id',
'File Owner',
'Original Filename',
'Shared By',
'Shared To'
];
if (!$filterRecipient) {
$headers = array_merge($headers, ['Recipient', 'Target Name']);
}
$table->setHeaders($headers);
$table->render();
foreach ($shareWrappers as $share) {
if (!$filterRecipient) {
$recipient = $share->getInitiator();
$sharedTo = $recipient->getDisplayName();
if (!$this->configService->isLocalInstance($recipient->getInstance())) {
$sharedTo .= '@' . $recipient->getInstance();
}
}
$circle = $share->getCircle();
$row = [
$share->getId(),
$share->getFileSource(),
$share->getShareOwner(),
$share->getFileTarget(),
$share->getSharedBy(),
$circle->getDisplayName() . ' (' . $share->getSharedWith()
. ', ' . Circle::$DEF_SOURCE[$circle->getSource()] . ')'
];
if (!$filterRecipient) {
$row = array_merge(
$row, [
$sharedTo . ' (' . $recipient->getSingleId() . ', '
. Circle::$DEF_SOURCE[$recipient->getBasedOn()->getSource()] . ')',
(($share->getChildId() > 0) ? $share->getChildFileTarget(
) : $share->getFileTarget()),
]
);
}
$table->appendRow($row);
}
}
/**
* @param int $fileId
* @param string $to
* @param string $with
* @param string $by
* @param bool $all
* @param bool|null $filterRecipient
*
* @return ShareWrapper[]
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InvalidIdException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
*/
private function getShares(
int $fileId,
string $to,
string $with,
string $by,
bool $all,
?bool &$filterRecipient = false
): array {
if ($fileId > 0) {
return $this->shareWrapperService->getSharesByFileId($this->fileId, true);
}
if ($to !== '') {
$filterRecipient = !$all;
return $this->shareWrapperService->getSharesToCircle(
$to,
($with === '') ? null : $this->federatedUserService->getLocalFederatedUser($with),
($by === '') ? null : $this->federatedUserService->getLocalFederatedUser($by),
$all
);
}
if ($by !== '') {
$filterRecipient = !$all;
return $this->shareWrapperService->getSharesBy(
$this->federatedUserService->getLocalFederatedUser($by),
$fileId,
true,
-1,
0,
true,
$all
);
}
if ($with !== '') {
$probe = new CircleProbe();
$probe->includePersonalCircles();
return $this->shareWrapperService->getSharedWith(
$this->federatedUserService->getLocalFederatedUser($with),
$fileId,
$probe
);
}
throw new MissingInputException(
'Specify a FileId or an option: --with (USER), --by (USER), --to (CIRCLE)'
);
}
}
@@ -0,0 +1,161 @@
<?php
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Command;
use Exception;
use OC\Core\Command\Base;
use OCA\Circles\Service\DavService;
use OCA\DAV\CardDAV\CardDavBackend;
use OCP\IUserManager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class SyncContact
*
* @package OCA\Circles\Command
*/
class SyncContact extends Base {
/** @var IUserManager */
private $userManager;
/** @var CardDavBackend */
private $cardDavBackend;
/** @var DavService */
private $davService;
/**
* Groups constructor.
*
* @param IUserManager $userManager
* @param CardDavBackend $cardDavBackend
* @param DavService $davService
*/
public function __construct(
IUserManager $userManager, CardDavBackend $cardDavBackend, DavService $davService
) {
parent::__construct();
$this->userManager = $userManager;
$this->cardDavBackend = $cardDavBackend;
$this->davService = $davService;
}
protected function configure() {
parent::configure();
$this->setName('circles:contacts:sync')
->addOption('info', '', InputOption::VALUE_NONE, 'get info about contacts')
->addOption('status', '', InputOption::VALUE_NONE, 'get info about sync')
->setDescription('sync contacts, when using the Circles app as a backend of the Contact app');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|void|null
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output) {
if ($input->getOption('info')) {
$this->displayInfo($output);
return;
}
if ($input->getOption('status')) {
$this->displayStatus($output);
return;
}
$this->davService->migration();
$output->writeln('migration done');
}
/**
* @param OutputInterface $output
*/
private function displayInfo(OutputInterface $output) {
$users = $this->userManager->search('');
$tCards = $tBooks = 0;
$knownBooks = [];
foreach ($users as $user) {
$books = $this->cardDavBackend->getAddressBooksForUser('principals/users/' . $user->getUID());
$output->writeln(
'- User <info>' . $user->getUID() . '</info> have ' . sizeof($books) . ' address books:'
);
$tBooks += sizeof($books);
foreach ($books as $book) {
$bookId = $book['id'];
$owner = $this->davService->getOwnerFromAddressBook($bookId);
$cards = $this->cardDavBackend->getCards($bookId);
if (!in_array($bookId, $knownBooks)) {
$tCards += sizeof($cards);
}
$shared = '';
if ($owner !== $user->getUID()) {
$shared = ' (shared by <info>' . $owner . '</info>)';
}
$output->writeln(
' <comment>*</comment> book #' . $bookId . $shared . ' contains '
. sizeof($cards)
. ' entries'
);
$knownBooks[] = $bookId;
}
}
$output->writeln('');
$output->writeln('with a total of ' . $tBooks . ' address books and ' . $tCards . ' contact entries');
}
/**
* @param OutputInterface $output
*/
private function displayStatus(OutputInterface $output) {
$output->writeln('not yet available');
}
}