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 F7cloud')
->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 ' . $host . '');
$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 ' . $host . '');
$this->output->writeln(json_encode($circleLink, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->output->writeln('');
}
$this->output->writeln('- Available services on ' . $host . '');
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 ' . $host . '');
$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 ' . $host . ''
);
try {
/** @var RemoteInstance $remoteSignatory */
$remoteSignatory = $this->remoteStreamService->retrieveSignatory($resource->g('id'), true);
$this->output->writeln(' * No SignatureException: Identity authed');
} catch (SignatureException $e) {
$this->output->writeln(
'' . $host . ' cannot auth its identity: ' . $e->getMessage() . ''
);
return;
}
$this->output->writeln(' * Found ' . $remoteSignatory->getUid() . '');
if ($remoteSignatory->getUid(true) !== $tempUid) {
$this->output->writeln('looks like ' . $host . ' is faking its identity');
return;
}
$this->output->writeln('');
$testUrl = $resource->g('test');
$this->output->writeln('- Testing signed payload on ' . $testUrl . '');
try {
$localSignatory = $this->remoteStreamService->getAppSignatory();
} catch (SignatoryException $e) {
$this->output->writeln(
'Federated Circles not enabled locally. Please run ./occ circles:remote:init'
);
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('' . $signedRequest->getClearSignature() . '');
$this->output->writeln('');
$this->output->writeln(' * Signed Signature (base64 encoded): ');
$this->output->writeln(
'' . base64_encode($signedRequest->getSignedSignature()) . ''
);
$this->output->writeln('');
$result = $signedRequest->getOutgoingRequest()->getResult();
$code = $result->getStatusCode();
$this->output->writeln(' * Result: ' . (($code === 200) ? '' . ((string)$code) . '' : $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('### Complete report ###');
$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(
'The remote instance ' . $host
. ' is already known with this current identity'
);
$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('');
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$this->output->writeln(
'The remote instance ' . $remoteSignatory->getInstance() . ' looks good.'
);
$question = new ConfirmationQuestion(
'Would you like to identify this remote instance as \'' . $remoteSignatory->getType()
. '\' using interface \''
. InterfaceService::$LIST_IFACE[$remoteSignatory->getInterface()]
. '\' ? (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('remote instance saved');
}
}
/**
* @param RemoteInstance $remoteSignatory
*
* @throws RemoteUidException
*/
private function updateRemote(RemoteInstance $remoteSignatory): void {
$this->output->writeln('');
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$this->output->writeln(
'The remote instance ' . $remoteSignatory->getInstance()
. ' is known but its identity has changed.'
);
$this->output->writeln(
'If you are not sure on why identity changed, please say No to the next question and contact the admin of the remote instance'
);
$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 ' . $instance . ': ');
if ($this->configService->isLocalInstance($instance)) {
$this->output->writeln('instance is local');
return;
}
try {
$this->remoteStreamService->addRemoteInstance(
$instance,
RemoteInstance::TYPE_GLOBALSCALE,
InterfaceService::IFACE_INTERNAL,
true
);
$this->output->writeln('ok');
} catch (Exception $e) {
$msg = ($e->getMessage() === '') ? '' : ' (' . $e->getMessage() . ')';
$this->output->writeln('' . get_class($e) . $msg . '');
}
}
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']);
$rows = [];
foreach ($instances as $instance) {
try {
$current = $this->remoteStreamService->retrieveRemoteInstance($instance->getInstance());
if ($current->getUid(true) === $instance->getUid(true)) {
$currentUid = '' . $current->getUid(true) . '';
} else {
$currentUid = '' . $current->getUid(true) . '';
}
} catch (Exception $e) {
$currentUid = '' . $e->getMessage() . '';
}
$rows[] = [
$instance->getInstance(),
$instance->getType(),
InterfaceService::$LIST_IFACE[$instance->getInterface()],
$instance->getUid(),
$currentUid,
json_encode($instance->getAliases())
];
}
$table->setRows($rows);
$table->render();
}
/**
* @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));
}
}