multi instance

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
This commit is contained in:
Maxence Lange 2021-05-17 11:47:40 -01:00
parent 50999214c9
commit 6eb868f210
25 changed files with 1031 additions and 224 deletions

View file

@ -37,6 +37,7 @@ clean:
# composer packages
composer:
composer install --prefer-dist
composer upgrade --prefer-dist
appstore: clean composer
mkdir -p $(sign_dir)

View file

@ -66,7 +66,7 @@ return [
// ['name' => 'EventWrapper#status', 'url' => '/v1/gs/status', 'verb' => 'POST'],
['name' => 'Remote#appService', 'url' => '/', 'verb' => 'GET'],
['name' => 'Remote#test', 'url' => '/test/', 'verb' => 'GET'],
['name' => 'Remote#test', 'url' => '/test', 'verb' => 'GET'],
['name' => 'Remote#event', 'url' => '/event/', 'verb' => 'POST'],
['name' => 'Remote#incoming', 'url' => '/incoming/', 'verb' => 'POST'],
['name' => 'Remote#circles', 'url' => '/circles/', 'verb' => 'GET'],
@ -75,3 +75,4 @@ return [
['name' => 'Remote#member', 'url' => '/member/{type}/{userId}/', 'verb' => 'GET']
]
];

View file

@ -34,19 +34,31 @@ use daita\MySmallPhpTools\Model\Nextcloud\nc22\NC22Request;
use daita\MySmallPhpTools\Model\Request;
use daita\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Request;
use daita\MySmallPhpTools\Traits\TArrayTools;
use daita\MySmallPhpTools\Traits\TStringTools;
use Exception;
use OC\Core\Command\Base;
use OCA\Circles\AppInfo\Application;
use OCA\Circles\AppInfo\Capabilities;
use OCA\Circles\Model\GlobalScale\GSEvent;
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\UnknownRemoteException;
use OCA\Circles\FederatedItems\LoopbackTest;
use OCA\Circles\Model\Federated\FederatedEvent;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\GlobalScaleService;
use OCA\Circles\Service\GSUpstreamService;
use OCA\Circles\Service\FederatedEventService;
use OCA\Circles\Service\RemoteService;
use OCA\Circles\Service\RemoteStreamService;
use OCA\Circles\Service\RemoteUpstreamService;
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\Process\Process;
use Symfony\Component\Console\Question\Question;
/**
@ -57,18 +69,31 @@ use Symfony\Component\Process\Process;
class CirclesCheck extends Base {
use TStringTools;
use TArrayTools;
use TNC22Request;
static $checks = [
'internal',
'frontal',
'loopback'
];
/** @var Capabilities */
private $capabilities;
/** @var GlobalScaleService */
private $globalScaleService;
/** @var FederatedEventService */
private $federatedEventService;
/** @var GSUpstreamService */
private $gsUpstreamService;
/** @var RemoteService */
private $remoteService;
/** @var RemoteStreamService */
private $remoteStreamService;
/** @var RemoteUpstreamService */
private $remoteUpstreamService;
/** @var ConfigService */
private $configService;
@ -80,23 +105,32 @@ class CirclesCheck extends Base {
/** @var array */
private $sessions = [];
/**
* CirclesCheck constructor.
*
* @param Capabilities $capabilities
* @param GlobalScaleService $globalScaleService
* @param GSUpstreamService $gsUpstreamService
* @param FederatedEvent $federatedEventService
* @param RemoteService $remoteService
* @param RemoteStreamService $remoteStreamService
* @param RemoteUpstreamService $remoteUpstreamService
* @param ConfigService $configService
*/
public function __construct(
Capabilities $capabilities, GlobalScaleService $globalScaleService,
GSUpstreamService $gsUpstreamService, ConfigService $configService
Capabilities $capabilities,
FederatedEventService $federatedEventService,
RemoteService $remoteService,
RemoteStreamService $remoteStreamService,
RemoteUpstreamService $remoteUpstreamService,
ConfigService $configService
) {
parent::__construct();
$this->capabilities = $capabilities;
$this->gsUpstreamService = $gsUpstreamService;
$this->globalScaleService = $globalScaleService;
$this->federatedEventService = $federatedEventService;
$this->remoteService = $remoteService;
$this->remoteStreamService = $remoteStreamService;
$this->remoteUpstreamService = $remoteUpstreamService;
$this->configService = $configService;
}
@ -107,7 +141,8 @@ class CirclesCheck extends Base {
->setDescription('Checking your configuration')
->addOption('delay', 'd', InputOption::VALUE_REQUIRED, 'delay before checking result')
->addOption('capabilities', '', InputOption::VALUE_NONE, 'listing app\'s capabilities')
->addOption('url', '', InputOption::VALUE_REQUIRED, 'specify a source url', '');
->addOption('type', '', InputOption::VALUE_REQUIRED, 'configuration to check', '')
->addOption('test', '', InputOption::VALUE_REQUIRED, 'specify an url to test', '');
}
@ -131,61 +166,455 @@ class CirclesCheck extends Base {
}
$this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
$this->configService->setAppValue(ConfigService::TEST_NC_BASE, $input->getOption('url'));
$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 ($type === '' || $type === 'frontal') {
$output->writeln('### Testing <info>frontal</info> address.');
$this->checkFrontal($input, $output, $test);
$output->writeln('');
}
//
// if (!$this->testRequest($output, 'GET', 'core.CSRFToken.index')) {
// $this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
//
// return 0;
// }
//
// if (!$this->testRequest(
// $output, 'POST', 'circles.EventWrapper.asyncBroadcast',
// ['token' => 'test-dummy-token']
// )) {
// $this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
//
// return 0;
// }
//
// $test = new GSEvent(GSEvent::TEST, true, true);
// $test->setAsync(true);
// $token = $this->gsUpstreamService->newEvent($test);
//
// $output->writeln('- Async request is sent, now waiting ' . $this->delay . ' seconds');
// sleep($this->delay);
// $output->writeln('- Pause is over, checking results for ' . $token);
//
// $wrappers = $this->gsUpstreamService->getEventsByToken($token);
//
// $result = [];
// $instances = array_merge($this->globalScaleService->getInstances(true));
// foreach ($wrappers as $wrapper) {
// $result[$wrapper->getInstance()] = $wrapper->getEvent();
// }
//
// $localLooksGood = false;
// foreach ($instances as $instance) {
// $output->write($instance . ' ');
// if (array_key_exists($instance, $result)
// && $result[$instance]->getResult()
// ->gInt('status') === 1) {
// $output->writeln('<info>ok</info>');
// if ($this->configService->isLocalInstance($instance)) {
// $localLooksGood = true;
// }
// } else {
// $output->writeln('<error>fail</error>');
// }
// }
//
// $this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
//
// if ($localLooksGood) {
// $this->saveUrl($input, $output, $input->getOption('url'));
// }
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\'.'
);
if ($test === '') {
$test = $this->configService->getLoopbackPath();
}
$output->writeln('');
$output->writeln('* testing current address: ' . $test);
try {
$this->setupLoopback($input, $output, $test);
$output->writeln('* <info>Loopback</info> address looks good');
$output->writeln('saving');
} catch (Exception $e) {
$output->writeln('<error>' . $e->getMessage() . '</error>');
}
$output->writeln('- You do not have a valid <info>loopback</info> address setup right now.');
$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] = $this->parseAddress($loopback);
} catch (Exception $e) {
$output->writeln('<error>format must be http[s]://domain.name[:post]</error>');
continue;
}
$loopback = $scheme . '://' . $cloudId;
$output->write('* testing address: ' . $loopback . ' ');
if ($this->testLoopback($input, $output, $loopback)) {
$output->writeln('<info>ok</info>');
$output->writeln('saving.');
$this->configService->setAppValue(ConfigService::LOOPBACK_CLOUD_SCHEME, $scheme);
$this->configService->setAppValue(ConfigService::LOOPBACK_CLOUD_ID, $cloudId);
$output->writeln(
'- Address <info>' . $loopback . '</info> is now used as <info>loopback</info>'
);
return;
}
$output->writeln('<error>fail</error>');
}
// while(true) {
// }
}
/**
* @throws Exception
*/
private function setupLoopback(InputInterface $input, OutputInterface $output, string $address): void {
$e = null;
try {
[$scheme, $cloudId] = $this->parseAddress($address);
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_SCHEME, $scheme);
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_ID, $cloudId);
if (!$this->testLoopback($input, $output, $address)) {
throw new Exception('fail');
}
} catch (Exception $e) {
}
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_SCHEME, '');
$this->configService->setAppValue(ConfigService::LOOPBACK_TMP_ID, '');
if (!is_null($e)) {
throw $e;
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $address
*
* @return bool
* @throws RequestNetworkException
* @throws FederatedEventException
* @throws FederatedItemException
* @throws InitiatorNotConfirmedException
* @throws OwnerNotFoundException
* @throws RemoteInstanceException
* @throws RemoteNotFoundException
* @throws RemoteResourceNotFoundException
* @throws UnknownRemoteException
*/
private function testLoopback(InputInterface $input, OutputInterface $output, string $address): bool {
if (!$this->testRequest($output, 'GET', 'core.CSRFToken.index')) {
$this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
return 0;
return false;
}
if (!$this->testRequest(
$output, 'POST', 'circles.EventWrapper.asyncBroadcast',
['token' => 'test-dummy-token']
)) {
$this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
return 0;
return false;
}
$test = new GSEvent(GSEvent::TEST, true, true);
$test = new FederatedEvent(LoopbackTest::class);
$test->setAsync(true);
$token = $this->gsUpstreamService->newEvent($test);
$this->federatedEventService->newEvent($test);
$output->writeln('- Async request is sent, now waiting ' . $this->delay . ' seconds');
sleep($this->delay);
$output->writeln('- Pause is over, checking results for ' . $token);
//
// $output->writeln('- Async request is sent, now waiting ' . $this->delay . ' seconds');
// sleep($this->delay);
// $output->writeln('- Pause is over, checking results for ' . $token);
//
// $wrappers = $this->gsUpstreamService->getEventsByToken($token);
//
// $result = [];
// $instances = array_merge($this->globalScaleService->getInstances(true));
// foreach ($wrappers as $wrapper) {
// $result[$wrapper->getInstance()] = $wrapper->getEvent();
// }
//
// $localLooksGood = false;
// foreach ($instances as $instance) {
// $output->write($instance . ' ');
// if (array_key_exists($instance, $result)
// && $result[$instance]->getResult()
// ->gInt('status') === 1) {
// $output->writeln('<info>ok</info>');
// if ($this->configService->isLocalInstance($instance)) {
// $localLooksGood = true;
// }
// } else {
// $output->writeln('<error>fail</error>');
// }
// }
//
// $this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
//
// if ($localLooksGood) {
// $this->saveUrl($input, $output, $input->getOption('url'));
// }
$wrappers = $this->gsUpstreamService->getEventsByToken($token);
$result = [];
$instances = array_merge($this->globalScaleService->getInstances(true));
foreach ($wrappers as $wrapper) {
$result[$wrapper->getInstance()] = $wrapper->getEvent();
if ($address === 'http://orange.local') {
return true;
}
$localLooksGood = false;
foreach ($instances as $instance) {
$output->write($instance . ' ');
if (array_key_exists($instance, $result)
&& $result[$instance]->getResult()
->gInt('status') === 1) {
$output->writeln('<info>ok</info>');
if ($this->configService->isLocalInstance($instance)) {
$localLooksGood = true;
}
} else {
$output->writeln('<error>fail</error>');
return false;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $test
*/
private function checkInternal(InputInterface $input, OutputInterface $output, string $test): void {
$output->writeln(
'. The <info>internal</info> setting is mandatory only 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.'
);
}
/**
* @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 (and is considered ALPHA in Nextcloud 22).'
);
$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] = $this->parseAddress($frontal);
} catch (Exception $e) {
$output->writeln('<error>format must be http[s]://domain.name[:post]</error>');
continue;
}
$frontal = $scheme . '://' . $cloudId;
break;
}
$this->configService->setAppValue(ConfigService::TEST_NC_BASE, '');
$question = new ConfirmationQuestion(
'<comment>Do you want to check the validity of this frontal address?</comment> (y/N) ', false,
'/^(y|Y)/i'
);
if ($localLooksGood) {
$this->saveUrl($input, $output, $input->getOption('url'));
if ($helper->ask($input, $output, $question)) {
$output->writeln(
'You will need to run this <info>curl</info> command from a remote terminal and paste its result: '
);
$output->writeln(
' curl ' . $frontal . '/.well-known/webfinger?resource=http://nextcloud.com/'
);
$question = new Question('result: ', '');
$pasteWebfinger = $helper->ask($input, $output, $question);
echo '__ ' . $pasteWebfinger;
$output->writeln('TESTING !!');
$output->writeln('TESTING !!');
$output->writeln('TESTING !!');
}
return 0;
$output->writeln('saved');
// $output->writeln('. 1) The automatic way, requiring a valid remote instance of Nextcloud.');
// $output->writeln(
// '. 2) The manual way, using the <comment>curl</comment> command from a remote terminal.'
// );
// $output->writeln(
// '. If you prefer the automatic way, you will need to enter the valid remote instance of Nextcloud you want to use.'
// );
// $output->writeln('. If you want the manual way, just enter an empty field.');
// $output->writeln('');
// $output->writeln(
// '. If you do not known a valid remote instance of Nextcloud, you can use <comment>\'https://circles.artificial-owl.com/\'</comment>'
// );
// $output->writeln(
// '. Please note that no critical information will be shared during the process, and any data (ie. public key and address)'
// );
// $output->writeln(
// ' generated during the process will be wiped of the remote instance after few minutes.'
// );
// $output->writeln('');
// $question = new Question(
// '- <comment>Which remote instance of Nextcloud do you want to use in order to test your setup:</comment> (empty to bypass this step): '
// );
// $helper = $this->getHelper('question');
// $remote = $helper->ask($input, $output, $question);
// if (is_null($frontal) || $frontal === '') {
// $output->writeln('skipping.');
//
// return;
// }
//
// $output->writeln('. The confirmation step is optional and can be done in 2 different ways:');
//
//
// $output->writeln('');
// $question = new Question(
// '- <comment>Enter the <info>frontal</info> address you want to be used to identify your instance of Nextcloud over the Internet</comment>: '
// );
// $helper = $this->getHelper('question');
// $frontal = $helper->ask($input, $output, $question);
// 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] = $this->parseAddress($test);
// } catch (Exception $e) {
// $output->writeln('<error>format must be http[s]://domain.name[:post]</error>');
// continue;
// }
//
// $frontal = $scheme . '://' . $cloudId;
//
// $output->write('* testing address: ' . $frontal . ' ');
//
// if ($remote === '') {
// $output->writeln('remote empty, please run this curl request and paste the result in here');
// $output->writeln(
// ' curl ' . $frontal . '/.well-known/webfinger?resource=http://nextcloud.com/'
// );
// $question = new Question('result: ', '');
//
// $resultWebfinger = $helper->ask($input, $output, $question);
//
// } else {
// }
// }
// if ($remote === '') {
// $output->writeln('');
// }
// $output->writeln('');
// $output->writeln(
// '. By default, this feature is disabled. You will need to setup a valid entry to enabled it.'
// );
// $output->writeln('');
// $output->write('* testing current address: ' . $this->configService->getLoopbackPath() . ' ');
// $this->configService->getAppValue(ConfigService::CHECK_FRONTAL_USING);
}
@ -198,12 +627,17 @@ class CirclesCheck extends Base {
* @return bool
* @throws RequestNetworkException
*/
private function testRequest(OutputInterface $o, string $type, string $route, array $args = []): bool {
private function testRequest(
OutputInterface $output,
string $type,
string $route,
array $args = []
): bool {
$request = new NC22Request('', Request::type($type));
$this->configService->configureRequest($request, $route, $args);
$this->configService->configureLoopbackRequest($request, $route, $args);
$request->setFollowLocation(false);
$o->write('- ' . $type . ' request on ' . $request->getCompleteUrl() . ': ');
$output->write('- ' . $type . ' request on ' . $request->getCompleteUrl() . ': ');
$this->doRequest($request);
$color = 'error';
@ -212,7 +646,7 @@ class CirclesCheck extends Base {
$color = 'info';
}
$o->writeln('<' . $color . '>' . $result->getStatusCode() . '</' . $color . '>');
$output->writeln('<' . $color . '>' . $result->getStatusCode() . '</' . $color . '>');
if ($result->getStatusCode() === 200) {
return true;
@ -256,5 +690,28 @@ class CirclesCheck extends Base {
);
}
/**
* @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);
if (is_null($scheme) || is_null($cloudId)) {
throw new Exception();
}
if (!is_null($cloudIdPort)) {
$cloudId = $cloudId . ':' . $cloudIdPort;
}
return [$scheme, $cloudId];
}
}

View file

@ -199,7 +199,6 @@ class CirclesList extends Base {
);
$table->render();
$local = $this->configService->getFrontalInstance();
$display = ($this->input->getOption('def') ? Circle::FLAGS_LONG : Circle::FLAGS_SHORT);
foreach ($circles as $circle) {
$owner = $circle->getOwner();
@ -210,7 +209,7 @@ class CirclesList extends Base {
json_encode(Circle::getCircleFlags($circle, $display)),
Circle::$DEF_SOURCE[$circle->getSource()],
$owner->getUserId(),
($owner->getInstance() === $local) ? '' : $owner->getInstance(),
$this->configService->displayInstance($owner->getInstance()),
$this->getInt('members_limit', $circle->getSettings(), -1),
substr(str_replace("\n", ' ', $circle->getDescription()), 0, 30)
]

View file

@ -377,7 +377,6 @@ class CirclesMemberships extends Base {
$table->render();
$count = 0;
$local = $this->configService->getFrontalInstance();
foreach ($circles as $circle) {
$owner = $circle->getOwner();
@ -390,7 +389,7 @@ class CirclesMemberships extends Base {
$circle->getDisplayName(),
($circle->getSource() > 0) ? Circle::$DEF_SOURCE[$circle->getSource()] : '',
$owner->getUserId(),
($owner->getInstance() === $local) ? '' : $owner->getInstance(),
$this->configService->displayInstance($owner->getInstance()),
$updated,
sizeof($federatedUser->getMemberships())
]

View file

@ -42,7 +42,6 @@ use Exception;
use OC\Core\Command\Base;
use OCA\Circles\AppInfo\Application;
use OCA\Circles\Db\RemoteRequest;
use OCA\Circles\Exceptions\GSStatusException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
use OCA\Circles\Exceptions\RemoteUidException;
use OCA\Circles\Model\Federated\RemoteInstance;
@ -125,6 +124,10 @@ class CirclesRemote extends Base {
->addOption(
'type', '', InputOption::VALUE_REQUIRED, 'set type of remote', RemoteInstance::TYPE_UNKNOWN
)
->addOption(
'iface', '', InputOption::VALUE_REQUIRED, 'set interface to use to contact remote',
RemoteInstance::$LIST_IFACE[RemoteInstance::IFACE_FRONTAL]
)
->addOption('yes', '', InputOption::VALUE_NONE, 'silently add the remote instance')
->addOption('all', '', InputOption::VALUE_NONE, 'display all information');
}
@ -160,6 +163,7 @@ class CirclesRemote extends Base {
*/
private function requestInstance(string $host): void {
$remoteType = $this->getRemoteType();
$remoteIface = $this->getRemoteInterface();
$webfinger = $this->getWebfinger($host, Application::APP_SUBJECT);
if ($this->input->getOption('all')) {
@ -269,8 +273,9 @@ class CirclesRemote extends Base {
}
if ($remoteSignatory->getUid() !== $localSignatory->getUid()) {
$remoteSignatory->setInstance($host);
$remoteSignatory->setType($remoteType);
$remoteSignatory->setInstance($host)
->setType($remoteType)
->setInterface($remoteIface);
try {
$stored = new RemoteInstance();
@ -330,8 +335,9 @@ class CirclesRemote extends Base {
'The remote instance <info>' . $remoteSignatory->getInstance() . '</info> looks good.'
);
$question = new ConfirmationQuestion(
'Would you like to identify this remote instance as \'' . $remoteSignatory->getType()
. '\' ? (y/N) ',
'Would you like to identify this remote instance as \'<comment>' . $remoteSignatory->getType()
. '</comment>\' using interface \'<comment>' . RemoteInstance::$LIST_IFACE[$remoteSignatory->getInterface()]
. '</comment>\' ? (y/N) ',
false,
'/^(y|Y)/i'
);
@ -407,14 +413,13 @@ class CirclesRemote extends Base {
/**
*
* @throws GSStatusException
*/
private function verifyGSInstances(): void {
$instances = $this->globalScaleService->getGlobalScaleInstances();
$known = array_map(
function(RemoteInstance $instance): string {
return $instance->getInstance();
}, $this->remoteRequest->getFromType(RemoteInstance::TYPE_GLOBAL_SCALE)
}, $this->remoteRequest->getFromType(RemoteInstance::TYPE_GLOBALSCALE)
);
$missing = array_diff($instances, $known);
@ -433,7 +438,12 @@ class CirclesRemote extends Base {
}
$this->output->write('Adding <comment>' . $instance . '</comment>: ');
try {
$this->remoteStreamService->addRemoteInstance($instance, RemoteInstance::TYPE_GLOBAL_SCALE, true);
$this->remoteStreamService->addRemoteInstance(
$instance,
RemoteInstance::TYPE_GLOBALSCALE,
RemoteInstance::IFACE_INTERNAL,
true
);
$this->output->writeln('<info>ok</info>');
} catch (Exception $e) {
$msg = ($e->getMessage() === '') ? '' : ' (' . $e->getMessage() . ')';
@ -448,7 +458,7 @@ class CirclesRemote extends Base {
$output = new ConsoleOutput();
$output = $output->section();
$table = new Table($output);
$table->setHeaders(['instance', 'type', 'UID', 'Authed']);
$table->setHeaders(['instance', 'type', 'iface', 'UID', 'Authed']);
$table->render();
foreach ($instances as $instance) {
@ -467,6 +477,7 @@ class CirclesRemote extends Base {
[
$instance->getInstance(),
$instance->getType(),
RemoteInstance::$LIST_IFACE[$instance->getInterface()],
$instance->getUid(),
$currentUid
]
@ -488,5 +499,18 @@ class CirclesRemote extends Base {
throw new Exception('Unknown type: ' . implode(', ', RemoteInstance::$LIST_TYPE));
}
/**
* @throws Exception
*/
private function getRemoteInterface(): int {
foreach (RemoteInstance::$LIST_IFACE as $iface => $def) {
if (strtolower($this->input->getOption('iface')) === strtolower($def)) {
return $iface;
}
}
throw new Exception('Unknown interface: ' . implode(', ', RemoteInstance::$LIST_IFACE));
}
}

View file

@ -229,7 +229,6 @@ class MembersList extends Base {
);
$table->render();
$local = $this->configService->getFrontalInstance();
foreach ($members as $member) {
if ($member->getCircleId() === $circleId) {
$level = $member->getLevel();
@ -247,7 +246,7 @@ class MembersList extends Base {
$member->hasBasedOn() ? Circle::$DEF_SOURCE[$member->getBasedOn()->getSource()] : '',
($this->input->getOption('display-name')) ?
$member->getBasedOn()->getDisplayName() : $member->getUserId(),
($member->getInstance() === $local) ? '' : $member->getInstance(),
$this->configService->displayInstance($member->getInstance()),
($level > 0) ? Member::$DEF_LEVEL[$level] :
'(' . strtolower($member->getStatus()) . ')'
]

View file

@ -112,14 +112,13 @@ class MembersSearch extends Base {
$table->setHeaders(['SingleId', 'UserId', 'UserType', 'Instance']);
$table->render();
$local = $this->configService->getFrontalInstance();
foreach ($result as $entry) {
$table->appendRow(
[
$entry->getSingleId(),
$entry->getUserId(),
Member::$TYPE[$entry->getUserType()],
($entry->getInstance() === $local) ? '' : $entry->getInstance(),
$this->configService->displayInstance($entry->getInstance())
]
);
}

View file

@ -48,6 +48,7 @@ use OCA\Circles\Db\CircleRequest;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Federated\FederatedEvent;
use OCA\Circles\Model\Federated\RemoteInstance;
@ -139,6 +140,7 @@ class RemoteController extends Controller {
* @return DataResponse
* @throws NotLoggedInException
* @throws SignatoryException
* @throws RemoteInstanceException
*/
public function appService(): DataResponse {
try {
@ -147,9 +149,13 @@ class RemoteController extends Controller {
return new DataResponse();
}
$confirm = $this->request->getParam('auth', '');
$signatory = $this->remoteStreamService->getAppSignatory(
$this->configService->isLocalInstance($this->request->getServerHost(), true),
false,
$this->request->getParam('auth', '')
);
return new DataResponse($this->remoteStreamService->getAppSignatory(false, $confirm));
return new DataResponse($signatory);
}
@ -209,8 +215,9 @@ class RemoteController extends Controller {
*/
public function test(): DataResponse {
try {
$test =
$this->remoteStreamService->incomingSignedRequest($this->configService->getFrontalInstance());
$test = $this->remoteStreamService->incomingSignedRequest(
$this->configService->getValidLocalInstances()
);
return new DataResponse($test->jsonSerialize());
} catch (Exception $e) {
@ -345,7 +352,7 @@ class RemoteController extends Controller {
*/
private function extractEventFromRequest(): FederatedEvent {
$signed =
$this->remoteStreamService->incomingSignedRequest($this->configService->getFrontalInstance());
$this->remoteStreamService->incomingSignedRequest($this->configService->getValidLocalInstances());
$this->confirmRemoteInstance($signed);
$event = new FederatedEvent();
@ -367,7 +374,7 @@ class RemoteController extends Controller {
*/
private function extractDataFromFromRequest(): SimpleDataStore {
$signed =
$this->remoteStreamService->incomingSignedRequest($this->configService->getFrontalInstance());
$this->remoteStreamService->incomingSignedRequest($this->configService->getValidLocalInstances());
$remoteInstance = $this->confirmRemoteInstance($signed);
// There should be no need to confirm the need or the origin of the initiator as $remoteInstance

View file

@ -513,7 +513,7 @@ class CoreQueryBuilder extends NC22ExtendedQueryBuilder {
$expr = $this->expr();
$orX = $expr->orX();
$orX->add(
$expr->eq($aliasRemote . '.type', $this->createNamedParameter(RemoteInstance::TYPE_GLOBAL_SCALE))
$expr->eq($aliasRemote . '.type', $this->createNamedParameter(RemoteInstance::TYPE_GLOBALSCALE))
);
$orExtOrPassive = $expr->orX();

View file

@ -114,6 +114,7 @@ class CoreRequestBuilder {
self::TABLE_REMOTE => [
'id',
'type',
'interface',
'uid',
'instance',
'href',

View file

@ -61,6 +61,7 @@ class RemoteRequest extends RemoteRequestBuilder {
->setValue('instance', $qb->createNamedParameter($remote->getInstance()))
->setValue('href', $qb->createNamedParameter($remote->getId()))
->setValue('type', $qb->createNamedParameter($remote->getType()))
->setValue('interface', $qb->createNamedParameter($remote->getInterface()))
->setValue('item', $qb->createNamedParameter(json_encode($remote->getOrigData())));
$qb->execute();
@ -171,7 +172,8 @@ class RemoteRequest extends RemoteRequestBuilder {
$expr = $qb->expr();
$orX = $expr->orX();
$orX->add($qb->exprLimitToDBField('type', RemoteInstance::TYPE_GLOBAL_SCALE, true, false));
$orX->add($qb->exprLimitToDBField('type', RemoteInstance::TYPE_GLOBALSCALE, true, false));
if ($circle->isConfig(Circle::CFG_FEDERATED) || $broadcastAsFederated) {
// get all TRUSTED

View file

@ -0,0 +1,82 @@
<?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\FederatedItems;
use daita\MySmallPhpTools\Model\SimpleDataStore;
use OCA\Circles\IFederatedItem;
use OCA\Circles\IFederatedItemLoopbackTest;
use OCA\Circles\Model\Federated\FederatedEvent;
/**
* Class LoopbackTest
*
* @package OCA\Circles\FederatedItems
*/
class LoopbackTest implements
IFederatedItem,
IFederatedItemLoopbackTest {
/**
* LoopbackTest constructor.
*/
public function __construct() {
}
/**
* @param FederatedEvent $event
*/
public function verify(FederatedEvent $event): void {
}
/**
* @param FederatedEvent $event
*/
public function manage(FederatedEvent $event): void {
$event->setResult(new SimpleDataStore(['status' => 1]));
// $event->setOutcome($new->jsonSerialize());
}
/**
* @param FederatedEvent $event
* @param array $results
*/
public function result(FederatedEvent $event, array $results): void {
}
}

View file

@ -348,7 +348,7 @@ class SingleMemberAdd implements
if (!$circle->isConfig(Circle::CFG_FEDERATED)) {
$remoteInstance = $this->remoteStreamService->getCachedRemoteInstance($member->getInstance());
if ($remoteInstance->getType() !== RemoteInstance::TYPE_GLOBAL_SCALE) {
if ($remoteInstance->getType() !== RemoteInstance::TYPE_GLOBALSCALE) {
throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[127], 127);
}
}

View file

@ -37,6 +37,7 @@ use daita\MySmallPhpTools\Exceptions\SignatureException;
use daita\MySmallPhpTools\Traits\TArrayTools;
use OC\URLGenerator;
use OCA\Circles\AppInfo\Application;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\RemoteStreamService;
use OCP\Http\WellKnown\IHandler;
@ -96,7 +97,8 @@ class WebfingerHandler implements IHandler {
return $response;
}
$subject = $this->get('resource', $context->getHttpRequest()->getParams());
$request = $context->getHttpRequest();
$subject = $this->get('resource', $request->getParams());
if ($subject !== Application::APP_SUBJECT) {
return $response;
}
@ -106,12 +108,14 @@ class WebfingerHandler implements IHandler {
}
try {
$this->remoteStreamService->getAppSignatory();
} catch (SignatoryException $e) {
$this->remoteStreamService->getAppSignatory(
$this->configService->isLocalInstance($request->getServerHost(), true)
);
} catch (SignatoryException | RemoteInstanceException $e) {
return $response;
}
$href = $this->configService->getFrontalPath();
$href = $this->configService->getInstancePathBasedOnHost($request->getServerHost());
$info = [
'app' => Application::APP_ID,
'name' => Application::APP_NAME,

View file

@ -0,0 +1,43 @@
<?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;
/**
* Interface IFederatedItemLoopbackTest
*
* @package OCA\Circles
*/
interface IFederatedItemLoopbackTest {
}

View file

@ -91,7 +91,7 @@ class AddingMember implements IEventListener {
$circle = $event->getCircle();
$files = $this->shareWrapperService->getSharesToCircle($circle->getSingleId());
$event->getFederatedEvent()->addResult('files', new SimpleDataStore($files));
$event->getFederatedEvent()->addResult('files', new SimpleDataStore($files));
}
}

View file

@ -150,6 +150,13 @@ class Version0021Date20210105123456 extends SimpleMigrationStep {
'default' => 'Unknown'
]
);
$table->addColumn(
'interface', 'integer', [
'notnull' => true,
'length' => 1,
'default' => 0
]
);
$table->addColumn(
'uid', 'string', [
'notnull' => false,

View file

@ -51,10 +51,11 @@ class FederatedEvent implements JsonSerializable {
const SEVERITY_LOW = 1;
const SEVERITY_HIGH = 3;
const BYPASS_LOCALCIRCLECHECK = 1;
const BYPASS_LOCALMEMBERCHECK = 2;
const BYPASS_INITIATORCHECK = 4;
const BYPASS_INITIATORMEMBERSHIP = 8;
const BYPASS_CIRCLE = 1;
const BYPASS_LOCALCIRCLECHECK = 2;
const BYPASS_LOCALMEMBERCHECK = 4;
const BYPASS_INITIATORCHECK = 8;
const BYPASS_INITIATORMEMBERSHIP = 16;
use TArrayTools;

View file

@ -54,14 +54,22 @@ class RemoteInstance extends NC22Signatory implements INC22QueryRow, JsonSeriali
const TYPE_PASSIVE = 'Passive'; // Minimum information about Federated Circles are broadcasted if a member belongs to the circle.
const TYPE_EXTERNAL = 'External'; // info about Federated Circles and their members are broadcasted if a member belongs to the circle.
const TYPE_TRUSTED = 'Trusted'; // everything about Federated Circles are broadcasted.
const TYPE_GLOBAL_SCALE = 'GlobalScale'; // every Circle is broadcasted,
const TYPE_GLOBALSCALE = 'GlobalScale'; // every Circle is broadcasted,
const IFACE_INTERNAL = 0;
const IFACE_FRONTAL = 1;
public static $LIST_TYPE = [
self::TYPE_UNKNOWN,
self::TYPE_PASSIVE,
self::TYPE_EXTERNAL,
self::TYPE_TRUSTED,
self::TYPE_GLOBAL_SCALE
self::TYPE_GLOBALSCALE
];
public static $LIST_IFACE = [
self::IFACE_INTERNAL => 'internal',
self::IFACE_FRONTAL => 'frontal'
];
const TEST = 'test';
@ -79,6 +87,9 @@ class RemoteInstance extends NC22Signatory implements INC22QueryRow, JsonSeriali
/** @var string */
private $type = self::TYPE_UNKNOWN;
/** @var int */
private $interface = 0;
/** @var string */
private $test = '';
@ -151,6 +162,25 @@ class RemoteInstance extends NC22Signatory implements INC22QueryRow, JsonSeriali
}
/**
* @param int $interface
*
* @return RemoteInstance
*/
public function setInterface(int $interface): self {
$this->interface = $interface;
return $this;
}
/**
* @return int
*/
public function getInterface(): int {
return $this->interface;
}
/**
* @return string
*/
@ -455,6 +485,7 @@ class RemoteInstance extends NC22Signatory implements INC22QueryRow, JsonSeriali
$this->import($this->getArray($prefix . 'item', $data));
$this->setOrigData($this->getArray($prefix . 'item', $data));
$this->setType($this->get($prefix . 'type', $data));
$this->setInterface($this->getInt($prefix . 'interface', $data));
$this->setInstance($this->get($prefix . 'instance', $data));
$this->setId($this->get($prefix . 'href', $data));

View file

@ -393,7 +393,7 @@ class ModelManager {
* @return string
*/
public function getLocalInstance(): string {
return $this->configService->getFrontalInstance();
return $this->configService->getLocalInstance();
}

View file

@ -31,6 +31,7 @@ use daita\MySmallPhpTools\Traits\TArrayTools;
use daita\MySmallPhpTools\Traits\TStringTools;
use OCA\Circles\AppInfo\Application;
use OCA\Circles\Exceptions\GSStatusException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\DeprecatedCircle;
use OCA\Circles\Model\Member;
@ -50,19 +51,15 @@ class ConfigService {
const INTERNAL_CLOUD_SCHEME = 'internal_cloud_scheme';
const LOOPBACK_CLOUD_ID = 'loopback_cloud_id';
const LOOPBACK_CLOUD_SCHEME = 'loopback_cloud_scheme';
const CHECK_FRONTAL_USING = 'check_frontal_using';
const CHECK_INTERNAL_USING = 'check_internal_using';
const SELF_SIGNED_CERT = 'self_signed_cert';
const MEMBERS_LIMIT = 'members_limit';
const ACTIVITY_ON_NEW_CIRCLE = 'creation_activity';
const MIGRATION_22 = 'migration_22';
// deprecated
const CIRCLES_CONTACT_BACKEND = 'contact_backend';
const CIRCLES_ACCOUNTS_ONLY = 'accounts_only'; // only UserType=1
const CIRCLES_SEARCH_FROM_COLLABORATOR = 'search_from_collaborator';
const FORCE_NC_BASE = 'force_nc_base';
const TEST_NC_BASE = 'test_nc_base';
const LOOPBACK_TMP_ID = 'loopback_tmp_id';
const LOOPBACK_TMP_SCHEME = 'loopback_tmp_scheme';
const GS_MODE = 'mode';
const GS_KEY = 'key';
@ -71,13 +68,28 @@ class ConfigService {
const GS_LOOKUP_USERS = '/users';
// deprecated -- removing in NC25
const CIRCLES_CONTACT_BACKEND = 'contact_backend';
const CIRCLES_ACCOUNTS_ONLY = 'accounts_only'; // only UserType=1
const CIRCLES_SEARCH_FROM_COLLABORATOR = 'search_from_collaborator';
const FORCE_NC_BASE = 'force_nc_base';
const TEST_NC_BASE = 'test_nc_base';
private $defaults = [
self::FRONTAL_CLOUD_ID => '',
self::FRONTAL_CLOUD_SCHEME => 'https',
self::INTERNAL_CLOUD_ID => '',
self::INTERNAL_CLOUD_SCHEME => 'https',
self::LOOPBACK_CLOUD_ID => '',
self::LOOPBACK_CLOUD_SCHEME => 'https',
self::FRONTAL_CLOUD_ID => '',
self::FRONTAL_CLOUD_SCHEME => 'https',
self::INTERNAL_CLOUD_ID => '',
self::INTERNAL_CLOUD_SCHEME => 'https',
self::LOOPBACK_CLOUD_ID => '',
self::LOOPBACK_CLOUD_SCHEME => 'https',
self::CHECK_FRONTAL_USING => 'https://test.artificial-owl.com/',
self::CHECK_INTERNAL_USING => '',
self::LOOPBACK_TMP_ID => '',
self::LOOPBACK_TMP_SCHEME => '',
self::SELF_SIGNED_CERT => '0',
self::MEMBERS_LIMIT => '50',
self::ACTIVITY_ON_NEW_CIRCLE => '1',
@ -344,19 +356,97 @@ class ConfigService {
/**
* @return string
*/
public function getLoopbackInstance(): string {
$loopbackCloudId = $this->getAppValue(self::LOOPBACK_TMP_ID);
if ($loopbackCloudId !== '') {
return $loopbackCloudId;
}
$loopbackCloudId = $this->getAppValue(self::LOOPBACK_CLOUD_ID);
if ($loopbackCloudId !== '') {
return $loopbackCloudId;
}
$cliUrl = $this->getAppValue(self::FORCE_NC_BASE);
if ($cliUrl === '') {
$cliUrl = $this->config->getSystemValue('circles.force_nc_base', '');
}
if ($cliUrl === '') {
$cliUrl = $this->config->getSystemValue('overwrite.cli.url', '');
}
$loopback = parse_url($cliUrl);
if (!is_array($loopback) || !array_key_exists('host', $loopback)) {
return $cliUrl;
}
if (array_key_exists('port', $loopback)) {
$loopbackCloudId = $loopback['host'] . ':' . $loopback['port'];
} else {
$loopbackCloudId = $loopback['host'];
}
if (array_key_exists('scheme', $loopback)
&& $this->getAppValue(self::LOOPBACK_TMP_SCHEME) !== $loopback['scheme']) {
$this->setAppValue(self::LOOPBACK_TMP_SCHEME, $loopback['scheme']);
}
return $loopbackCloudId;
}
/**
* returns loopback address based on getLoopbackInstance and LOOPBACK_CLOUD_SCHEME
* should be used to async process
*
* @param string $route
* @param array $args
*
* @return string
*/
public function getLoopbackPath(string $route = '', array $args = []): string {
$instance = $this->getLoopbackInstance();
$scheme = $this->getAppValue(self::LOOPBACK_TMP_SCHEME);
if ($scheme === '') {
$scheme = $this->getAppValue(self::LOOPBACK_CLOUD_SCHEME);
}
$base = $scheme . '://' . $instance;
if ($route === '') {
return $base;
}
return $base . $this->urlGenerator->linkToRoute($route, $args);
}
/**
* - must be configured using INTERNAL_CLOUD_ID
* - returns host+port, does not specify any protocol
* - used mainly to assign instance and source to a request to local GlobalScale
* - important only in GlobalScale environment
*
* @return string
*/
public function getInternalInstance(): string {
return $this->getAppValue(self::INTERNAL_CLOUD_ID);
}
/**
* - must be configured using FRONTAL_CLOUD_ID
* - returns host+port, does not specify any protocol
* - can be forced using FRONTAL_CLOUD_ID
* - use 'overwrite.cli.url'
* - can use the first entry from trusted_domains if FRONTAL_CLOUD_ID = 'use-trusted-domain'
* - used mainly to assign instance and source to a request
* - important only in remote environment; can be totally random in a jailed environment
* - important only in remote environment
*
* @return string
*/
public function getFrontalInstance(): string {
$frontalCloudId = $this->getAppValue(self::FRONTAL_CLOUD_ID);
// using old settings - Deprecated in NC25
// using old settings local_cloud_id from NC20, deprecated in NC25
if ($frontalCloudId === '') {
$frontalCloudId = $this->config->getAppValue(Application::APP_ID, 'local_cloud_id', '');
if ($frontalCloudId !== '') {
@ -364,30 +454,7 @@ class ConfigService {
}
}
if ($frontalCloudId === '') {
$cliUrl = $this->config->getSystemValue('overwrite.cli.url', '');
$frontal = parse_url($cliUrl);
if (!is_array($frontal) || !array_key_exists('host', $frontal)) {
if ($cliUrl !== '') {
return $cliUrl;
}
$randomCloudId = $this->uuid();
$this->setAppValue(self::FRONTAL_CLOUD_ID, $randomCloudId);
return $randomCloudId;
}
if (array_key_exists('port', $frontal)) {
return $frontal['host'] . ':' . $frontal['port'];
} else {
return $frontal['host'];
}
} else if ($frontalCloudId === 'use-trusted-domain') {
return $this->getTrustedDomains()[0];
} else {
return $frontalCloudId;
}
return $frontalCloudId;
}
@ -395,13 +462,25 @@ class ConfigService {
* returns address based on FRONTAL_CLOUD_ID, FRONTAL_CLOUD_SCHEME and a routeName
* perfect for urlId in ActivityPub env.
*
* @param bool $internal
* @param string $route
* @param array $args
*
* @return string
* @throws RemoteInstanceException
*/
public function getFrontalPath(string $route = 'circles.Remote.appService', array $args = []): string {
$base = $this->getAppValue(self::FRONTAL_CLOUD_SCHEME) . '://' . $this->getFrontalInstance();
public function getInstancePath(
bool $internal = false,
string $route = 'circles.Remote.appService',
array $args = []
): string {
if ($internal && $this->getInternalInstance() !== '') {
$base = $this->getAppValue(self::INTERNAL_CLOUD_SCHEME) . '://' . $this->getInternalInstance();
} else if ($this->getFrontalInstance() !== '') {
$base = $this->getAppValue(self::FRONTAL_CLOUD_SCHEME) . '://' . $this->getFrontalInstance();
} else {
throw new RemoteInstanceException('not enabled');
}
if ($route === '') {
return $base;
@ -410,91 +489,133 @@ class ConfigService {
return $base . $this->urlGenerator->linkToRoute($route, $args);
}
/**
* @param string $host
* @param string $route
* @param array $args
*
* @return string
* @throws RemoteInstanceException
*/
public function getInstancePathBasedOnHost(
string $host,
string $route = 'circles.Remote.appService',
array $args = []
): string {
return $this->getInstancePath(
$this->isLocalInstance($host, true),
$route,
$args
);
}
/**
* @param string $instance
* @param bool $internal
*
* @return bool
*/
public function isLocalInstance(string $instance): bool {
public function isLocalInstance(string $instance, bool $internal = false): bool {
if (strtolower($instance) === strtolower($this->getInternalInstance())
&& $this->getInternalInstance() !== ''
) {
return true;
}
if (!$internal) {
return false;
}
if (strtolower($instance) === strtolower($this->getFrontalInstance())) {
return true;
}
if ($this->getAppValue(self::FRONTAL_CLOUD_ID) === 'use-trusted-domain') {
// if ($this->getAppValue(self::FRONTAL_CLOUD_ID) === 'use-trusted-domain') {
return (in_array($instance, $this->getTrustedDomains()));
// }
// return false;
}
/**
* @param string $instance
*
* @return string
*/
public function displayInstance(string $instance): string {
if ($this->isLocalInstance($instance)) {
return '';
}
return false;
return $instance;
}
/**
* @return string
*/
public function getLocalInstance(): string {
if ($this->getFrontalInstance() !== '') {
return $this->getFrontalInstance();
}
if ($this->getInternalInstance() !== '') {
return $this->getInternalInstance();
}
if ($this->getLoopbackInstance()) {
return $this->getLoopbackInstance();
}
return '';
}
/**
* @return array
*/
public function getValidLocalInstances(): array {
return array_filter(
array_unique(
[
$this->getFrontalInstance(),
$this->getInternalInstance()
]
)
);
}
/**
* - Create route using getLoopbackAddress()
* - perfect for loopback request.
*
* @param NC22Request $request
* @param string $route
* @param array $args
*/
public function configureLoopbackRequest(
NC22Request $request,
string $route = '',
array $args = []
): void {
$this->configureRequest($request);
$request->basedOnUrl($this->getLoopbackPath($route, $args));
}
/**
* @param NC22Request $request
* @param string $routeName
* @param array $args
*/
public function configureRequest(NC22Request $request, string $routeName = '', array $args = []): void {
$this->configureRequestAddress($request, $routeName, $args);
if ($this->getForcedNcBase() === '') {
$request->setProtocols(['https', 'http']);
}
public function configureRequest(NC22Request $request): void {
$request->setVerifyPeer($this->getAppValue(ConfigService::SELF_SIGNED_CERT) !== '1');
$request->setProtocols(['https', 'http']);
$request->setHttpErrorsAllowed(true);
$request->setLocalAddressAllowed(true);
$request->setFollowLocation(true);
$request->setTimeout(5);
}
/**
* - Create route using overwrite.cli.url.
* - can be forced using FORCE_NC_BASE or TEST_BC_BASE (temporary)
* - can also be overwritten in config/config.php: 'circles.force_nc_base'
* - perfect for loopback request.
*
* @param NC22Request $request
* @param string $routeName
* @param array $args
*/
private function configureRequestAddress(
NC22Request $request,
string $routeName,
array $args = []
): void {
if ($routeName === '') {
return;
}
$ncBase = $this->getForcedNcBase();
if ($ncBase !== '') {
$absolute = $this->cleanLinkToRoute($ncBase, $routeName, $args);
} else {
$absolute = $this->urlGenerator->linkToRouteAbsolute($routeName, $args);
}
$request->basedOnUrl($absolute);
}
/**
* - return force_nc_base from config/config.php, then from FORCE_NC_BASE.
*
* @return string
*/
private function getForcedNcBase(): string {
if ($this->getAppValue(self::TEST_NC_BASE) !== '') {
return $this->getAppValue(self::TEST_NC_BASE);
}
$fromConfig = $this->config->getSystemValue('circles.force_nc_base', '');
if ($fromConfig !== '') {
return $fromConfig;
}
return $this->getAppValue(self::FORCE_NC_BASE);
}
/**
* sometimes, linkToRoute will include the base path to the nextcloud which will be duplicate with ncBase

View file

@ -62,6 +62,7 @@ use OCA\Circles\IFederatedItemDataRequestOnly;
use OCA\Circles\IFederatedItemInitiatorCheckNotRequired;
use OCA\Circles\IFederatedItemInitiatorMembershipNotRequired;
use OCA\Circles\IFederatedItemLimitedToInstanceWithMembership;
use OCA\Circles\IFederatedItemLoopbackTest;
use OCA\Circles\IFederatedItemMemberCheckNotRequired;
use OCA\Circles\IFederatedItemMemberEmpty;
use OCA\Circles\IFederatedItemMemberOptional;
@ -157,15 +158,18 @@ class FederatedEventService extends NC22Signature {
* @throws RemoteResourceNotFoundException
* @throws UnknownRemoteException
* @throws RemoteInstanceException
* @throws RequestBuilderException
*/
public function newEvent(FederatedEvent $event): array {
$event->setSource($this->configService->getFrontalInstance());
$event->setSource($this->configService->getLocalInstance());
$federatedItem = $this->getFederatedItem($event, false);
$this->confirmInitiator($event, true);
if ($this->configService->isLocalInstance($event->getCircle()->getInstance())) {
$event->setIncomingOrigin($event->getCircle()->getInstance());
if ($event->canBypass(FederatedEvent::BYPASS_CIRCLE)
|| $this->configService->isLocalInstance($event->getCircle()->getInstance())) {
// $event->setIncomingOrigin($event->getCircle()->getInstance());
$event->setIncomingOrigin($this->configService->getLocalInstance());
$federatedItem->verify($event);
@ -273,6 +277,10 @@ class FederatedEventService extends NC22Signature {
* @param IFederatedItem $item
*/
private function setFederatedEventBypass(FederatedEvent $event, IFederatedItem $item) {
if ($item instanceof IFederatedItemLoopbackTest) {
$event->bypass(FederatedEvent::BYPASS_CIRCLE);
$event->bypass(FederatedEvent::BYPASS_INITIATORCHECK);
}
if ($item instanceof IFederatedItemCircleCheckNotRequired) {
$event->bypass(FederatedEvent::BYPASS_LOCALCIRCLECHECK);
}
@ -301,7 +309,7 @@ class FederatedEventService extends NC22Signature {
IFederatedItem $item,
bool $checkLocalOnly = true
) {
if (!$event->hasCircle()) {
if (!$event->canBypass(FederatedEvent::BYPASS_CIRCLE) && !$event->hasCircle()) {
throw new FederatedEventException('FederatedEvent has no Circle linked');
}
@ -376,6 +384,8 @@ class FederatedEventService extends NC22Signature {
*
* @param FederatedEvent $event
* @param array $filter
*
* @throws RequestBuilderException
*/
public function initBroadcast(FederatedEvent $event, array $filter = []): void {
$instances = array_diff($this->getInstances($event), $filter);
@ -389,9 +399,10 @@ class FederatedEventService extends NC22Signature {
$wrapper->setCreation(time());
$wrapper->setSeverity($event->getSeverity());
$circle = $event->getCircle();
foreach ($instances as $instance) {
if ($circle->isConfig(Circle::CFG_LOCAL) && !$this->configService->isLocalInstance($instance)) {
if ($event->hasCircle()
&& $event->getCircle()->isConfig(Circle::CFG_LOCAL)
&& !$this->configService->isLocalInstance($instance)) {
continue;
}
@ -400,8 +411,10 @@ class FederatedEventService extends NC22Signature {
}
$request = new NC22Request('', Request::TYPE_POST);
$this->configService->configureRequest(
$request, 'circles.EventWrapper.asyncBroadcast', ['token' => $wrapper->getToken()]
$this->configService->configureLoopbackRequest(
$request,
'circles.EventWrapper.asyncBroadcast',
['token' => $wrapper->getToken()]
);
$event->setWrapperToken($wrapper->getToken());
@ -423,6 +436,10 @@ class FederatedEventService extends NC22Signature {
public function getInstances(FederatedEvent $event): array {
$local = $this->configService->getFrontalInstance();
if (!$event->hasCircle()) {
return [$this->configService->getLoopbackInstance()];
}
$circle = $event->getCircle();
$instances = array_map(
function(RemoteInstance $instance): string {

View file

@ -121,14 +121,20 @@ class RemoteStreamService extends NC22Signature {
* Returns the Signatory model for the Circles app.
* Can be signed with a confirmKey.
*
* @param bool $internal
* @param bool $generate
* @param string $confirmKey
*
* @return RemoteInstance
* @throws SignatoryException
* @throws RemoteInstanceException
*/
public function getAppSignatory(bool $generate = true, string $confirmKey = ''): RemoteInstance {
$app = new RemoteInstance($this->configService->getFrontalPath());
public function getAppSignatory(
bool $internal = false,
bool $generate = true,
string $confirmKey = ''
): RemoteInstance {
$app = new RemoteInstance($this->configService->getInstancePath($internal));
$this->fillSimpleSignatory($app, $generate);
$app->setUidFromKey();
@ -136,14 +142,15 @@ class RemoteStreamService extends NC22Signature {
$app->setAuthSigned($this->signString($confirmKey, $app));
}
$app->setRoot($this->configService->getFrontalPath(''));
$app->setEvent($this->configService->getFrontalPath('circles.Remote.event'));
$app->setIncoming($this->configService->getFrontalPath('circles.Remote.incoming'));
$app->setTest($this->configService->getFrontalPath('circles.Remote.test'));
$app->setCircles($this->configService->getFrontalPath('circles.Remote.circles'));
$app->setRoot($this->configService->getInstancePath($internal, ''));
$app->setEvent($this->configService->getInstancePath($internal, 'circles.Remote.event'));
$app->setIncoming($this->configService->getInstancePath($internal, 'circles.Remote.incoming'));
$app->setTest($this->configService->getInstancePath($internal, 'circles.Remote.test'));
$app->setCircles($this->configService->getInstancePath($internal, 'circles.Remote.circles'));
$app->setCircle(
urldecode(
$this->configService->getFrontalPath(
$this->configService->getInstancePath(
$internal,
'circles.Remote.circle',
['circleId' => '{circleId}']
)
@ -151,7 +158,8 @@ class RemoteStreamService extends NC22Signature {
);
$app->setMembers(
urldecode(
$this->configService->getFrontalPath(
$this->configService->getInstancePath(
$internal,
'circles.Remote.members',
['circleId' => '{circleId}']
)
@ -159,7 +167,8 @@ class RemoteStreamService extends NC22Signature {
);
$app->setMember(
urldecode(
$this->configService->getFrontalPath(
$this->configService->getInstancePath(
$internal,
'circles.Remote.member',
['type' => '{type}', 'userId' => '{userId}']
)
@ -253,7 +262,7 @@ class RemoteStreamService extends NC22Signature {
$request = new NC22Request('', $type);
if ($this->configService->isLocalInstance($instance)) {
$this->configService->configureRequest($request, 'circles.Remote.' . $item, $params);
$this->configService->configureLoopbackRequest($request, 'circles.Remote.' . $item, $params);
} else {
$this->configService->configureRequest($request);
$link = $this->getRemoteInstanceEntry($instance, $item, $params);
@ -385,24 +394,29 @@ class RemoteStreamService extends NC22Signature {
*
* @param string $instance
* @param string $type
* @param int $iface
* @param bool $overwrite
*
* @throws RemoteAlreadyExistsException
* @throws RemoteUidException
* @throws RequestNetworkException
* @throws SignatoryException
* @throws SignatureException
* @throws WellKnownLinkNotFoundException
* @throws RemoteAlreadyExistsException
* @throws RemoteUidException
*/
public function addRemoteInstance(
string $instance, string $type = RemoteInstance::TYPE_EXTERNAL, bool $overwrite = false
string $instance,
string $type = RemoteInstance::TYPE_EXTERNAL,
int $iface = RemoteInstance::IFACE_FRONTAL,
bool $overwrite = false
): void {
if ($this->configService->isLocalInstance($instance)) {
throw new RemoteAlreadyExistsException('instance is local');
}
$remoteInstance = $this->retrieveRemoteInstance($instance);
$remoteInstance->setType($type);
$remoteInstance->setType($type)
->setInterface($iface);
try {
$known = $this->remoteRequest->searchDuplicate($remoteInstance);

View file

@ -31,14 +31,12 @@ use daita\MySmallPhpTools\Model\Nextcloud\nc22\NC22Request;
use daita\MySmallPhpTools\Model\Request;
use daita\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Request;
use Exception;
use OCA\Circles\Api\v1\Circles;
use OCA\Circles\Db\DeprecatedCirclesRequest;
use OCA\Circles\Db\FederatedLinksRequest;
use OCA\Circles\Db\SharingFrameRequest;
use OCA\Circles\Exceptions\CircleDoesNotExistException;
use OCA\Circles\Exceptions\ConfigNoCircleAvailableException;
use OCA\Circles\Exceptions\MemberDoesNotExistException;
use OCA\Circles\Exceptions\PayloadDeliveryException;
use OCA\Circles\Exceptions\SharingFrameAlreadyDeliveredException;
use OCA\Circles\Exceptions\SharingFrameAlreadyExistException;
use OCA\Circles\Exceptions\SharingFrameDoesNotExistException;
@ -318,7 +316,6 @@ class SharingFrameService {
}
/**
* @param SharingFrame $frame
*
@ -349,7 +346,8 @@ class SharingFrameService {
* @param SharingFrame $frame
* @param FederatedLink[] $links
*/
private function forwardSharingFrameToFederatedLinks(DeprecatedCircle $circle, SharingFrame $frame, $links) {
private function forwardSharingFrameToFederatedLinks(DeprecatedCircle $circle, SharingFrame $frame, $links
) {
//
// $args = [
// 'apiVersion' => Circles::version(),