Skip to content

Commit 58f2551

Browse files
committed
feat: global internal link
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
1 parent ee39c92 commit 58f2551

File tree

10 files changed

+324
-12
lines changed

10 files changed

+324
-12
lines changed

appinfo/info.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
</background-jobs>
2929

3030
<commands>
31+
<command>OCA\GlobalSiteSelector\Command\GlobalScaleDiscovery</command>
3132
<command>OCA\GlobalSiteSelector\Command\UsersUpdate</command>
3233
</commands>
3334
</info>

appinfo/routes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
return [
1010
'ocs' => [
1111
['name' => 'Slave#createAppToken', 'url' => '/v1/createapptoken', 'verb' => 'GET'],
12+
['name' => 'Slave#discovery', 'url' => '/discovery', 'verb' => 'GET'],
1213
],
1314
'routes' => [
1415
[

lib/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OCA\GlobalSiteSelector\GlobalSiteSelector;
1616
use OCA\GlobalSiteSelector\Listeners\AddContentSecurityPolicyListener;
1717
use OCA\GlobalSiteSelector\Listeners\DeletingUser;
18+
use OCA\GlobalSiteSelector\Listeners\InternalLinkRequested;
1819
use OCA\GlobalSiteSelector\Listeners\UserCreated;
1920
use OCA\GlobalSiteSelector\Listeners\UserDeleted;
2021
use OCA\GlobalSiteSelector\Listeners\UserLoggedOut;
@@ -27,6 +28,7 @@
2728
use OCP\AppFramework\Bootstrap\IBootstrap;
2829
use OCP\AppFramework\Bootstrap\IRegistrationContext;
2930
use OCP\EventDispatcher\IEventDispatcher;
31+
use OCP\Files\Events\InternalLinkRequestEvent;
3032
use OCP\IRequest;
3133
use OCP\IUser;
3234
use OCP\IUserManager;
@@ -79,6 +81,7 @@ public function register(IRegistrationContext $context): void {
7981
$context->registerEventListener(BeforeUserDeletedEvent::class, DeletingUser::class);
8082
$context->registerEventListener(UserDeletedEvent::class, UserDeleted::class);
8183
$context->registerEventListener(UserLoggedOutEvent::class, UserLoggedOut::class);
84+
$context->registerEventListener(InternalLinkRequestEvent::class, InternalLinkRequested::class);
8285

8386
// It seems that AccountManager use deprecated dispatcher, let's use a deprecated listener
8487
/** @var IEventDispatcher $eventDispatcher */

lib/BackgroundJobs/UpdateLookupServer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ protected function run($argument) {
3636
return;
3737
}
3838

39+
$this->slave->updateDiscoveryData();
3940
$this->slave->batchUpdate();
4041
}
4142
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCA\GlobalSiteSelector\Command;
10+
11+
use OC\Core\Command\Base;
12+
use OCA\GlobalSiteSelector\AppInfo\Application;
13+
use OCA\GlobalSiteSelector\ConfigLexicon;
14+
use OCA\GlobalSiteSelector\Service\GlobalScaleService;
15+
use OCP\IAppConfig;
16+
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Input\InputOption;
18+
use Symfony\Component\Console\Output\OutputInterface;
19+
20+
class GlobalScaleDiscovery extends Base {
21+
public function __construct(
22+
private readonly IAppConfig $appConfig,
23+
private readonly GlobalScaleService $globalScaleService,
24+
) {
25+
parent::__construct();
26+
}
27+
28+
29+
/**
30+
*
31+
*/
32+
protected function configure() {
33+
parent::configure();
34+
$this->setName('globalsiteselector:discovery')
35+
->addOption('current', '', InputOption::VALUE_NONE, 'display current data')
36+
->setDescription('run a discovery request over Global Scale');
37+
}
38+
39+
40+
/**
41+
* @param InputInterface $input
42+
* @param OutputInterface $output
43+
*
44+
* @return int
45+
*/
46+
protected function execute(InputInterface $input, OutputInterface $output): int {
47+
if ($input->getOption('current')) {
48+
$output->writeln(json_encode($this->appConfig->getValueArray(Application::APP_ID, ConfigLexicon::GS_TOKENS), JSON_PRETTY_PRINT));
49+
return 0;
50+
}
51+
52+
$this->globalScaleService->refreshTokenFromGlobalScale();
53+
return 0;
54+
}
55+
}

lib/ConfigLexicon.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCA\GlobalSiteSelector;
10+
11+
use NCU\Config\Lexicon\ConfigLexiconEntry;
12+
use NCU\Config\Lexicon\ConfigLexiconStrictness;
13+
use NCU\Config\Lexicon\IConfigLexicon;
14+
use NCU\Config\ValueType;
15+
16+
class ConfigLexicon implements IConfigLexicon {
17+
public const GS_TOKENS = 'globalScaleTokens';
18+
public const LOCAL_TOKEN = 'localToken';
19+
20+
public function getStrictness(): ConfigLexiconStrictness {
21+
return ConfigLexiconStrictness::IGNORE;
22+
}
23+
24+
/**
25+
* @inheritDoc
26+
* @return ConfigLexiconEntry[]
27+
*/
28+
public function getAppConfigs(): array {
29+
return [
30+
new ConfigLexiconEntry(self::GS_TOKENS, ValueType::ARRAY, [], 'list of token+host to navigate throw GlobalScale', true),
31+
new ConfigLexiconEntry(self::LOCAL_TOKEN, ValueType::STRING, '', 'local token to id instance of GlobalScale ', true),
32+
];
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
* @return ConfigLexiconEntry[]
38+
*/
39+
public function getUserConfigs(): array {
40+
return [
41+
];
42+
}
43+
}

lib/Controller/SlaveController.php

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCA\GlobalSiteSelector\AppInfo\Application;
1313
use OCA\GlobalSiteSelector\Exceptions\MasterUrlException;
1414
use OCA\GlobalSiteSelector\GlobalSiteSelector;
15+
use OCA\GlobalSiteSelector\Service\GlobalScaleService;
1516
use OCA\GlobalSiteSelector\Service\SlaveService;
1617
use OCA\GlobalSiteSelector\Slave;
1718
use OCA\GlobalSiteSelector\TokenHandler;
@@ -41,25 +42,35 @@
4142
* @package OCA\GlobalSiteSelector\Controller
4243
*/
4344
class SlaveController extends OCSController {
44-
4545
public function __construct(
4646
$appName,
4747
IRequest $request,
48-
private GlobalSiteSelector $gss,
49-
private IUserSession $userSession,
50-
private IURLGenerator $urlGenerator,
51-
private ICrypto $crypto,
52-
private TokenHandler $tokenHandler,
53-
private IUserManager $userManager,
54-
private UserBackend $userBackend,
55-
private ISession $session,
56-
private SlaveService $slaveService,
57-
private IConfig $config,
58-
private LoggerInterface $logger,
48+
private readonly GlobalSiteSelector $gss,
49+
private readonly IUserSession $userSession,
50+
private readonly IURLGenerator $urlGenerator,
51+
private readonly ICrypto $crypto,
52+
private readonly TokenHandler $tokenHandler,
53+
private readonly IUserManager $userManager,
54+
private readonly UserBackend $userBackend,
55+
private readonly ISession $session,
56+
private readonly SlaveService $slaveService,
57+
private readonly GlobalScaleService $globalScaleService,
58+
private readonly IConfig $config,
59+
private readonly LoggerInterface $logger,
5960
) {
6061
parent::__construct($appName, $request);
6162
}
6263

64+
65+
/**
66+
* @PublicPage
67+
* @NoCSRFRequired
68+
*/
69+
public function discovery(): DataResponse {
70+
// public data, reachable from outside.
71+
return new DataResponse(['token' => $this->globalScaleService->getLocalToken()]);
72+
}
73+
6374
/**
6475
* @PublicPage
6576
* @NoCSRFRequired
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCA\GlobalSiteSelector\Listeners;
10+
11+
use OCA\GlobalSiteSelector\AppInfo\Application;
12+
use OCA\GlobalSiteSelector\ConfigLexicon;
13+
use OCA\GlobalSiteSelector\GlobalSiteSelector;
14+
use OCA\GlobalSiteSelector\Service\GlobalScaleService;
15+
use OCP\EventDispatcher\Event;
16+
use OCP\EventDispatcher\IEventListener;
17+
use OCP\Files\Events\InternalLinkRequestEvent;
18+
use OCP\IAppConfig;
19+
use OCP\IUserSession;
20+
use Psr\Log\LoggerInterface;
21+
22+
/**
23+
* @template-implements IEventListener<InternalLinkRequestEvent>
24+
*/
25+
class InternalLinkRequested implements IEventListener {
26+
27+
public function __construct(
28+
private readonly IAppConfig $appConfig,
29+
private readonly IUserSession $userSession,
30+
private readonly GlobalSiteSelector $gss,
31+
private readonly GlobalScaleService $globalScaleService,
32+
private readonly LoggerInterface $logger,
33+
) {
34+
}
35+
36+
37+
/**
38+
* @param Event $event
39+
*/
40+
public function handle(Event $event): void {
41+
if (!$event instanceof InternalLinkRequestEvent) {
42+
return;
43+
}
44+
45+
if ($this->gss->isMaster()) {
46+
return;
47+
}
48+
49+
$fileId = $event->getFileId();
50+
if (!str_contains($fileId, '.')) {
51+
return;
52+
}
53+
54+
[$token, $fileId] = explode('.', $fileId);
55+
$event->setFileId($fileId);
56+
57+
if ($this->appConfig->getValueString(Application::APP_ID, ConfigLexicon::LOCAL_TOKEN) === $token) {
58+
return;
59+
}
60+
61+
$instance = $this->globalScaleService->getAddressFromToken($token);
62+
63+
// unknown instance, make it file not found
64+
if ($instance === null) {
65+
$event->setFileId('1');
66+
return;
67+
}
68+
69+
$this->logger->warning('=> remote instance ' . $instance . ' - remote file id ' . $fileId . ' - local user: ' . $this->userSession->getUser()->getUID());
70+
71+
// - get local fileid using remote instance, remote fileid and local user (using share_external ?)
72+
73+
// - setResponse using a REdirectResponse the same way ViewController::redirectToFile()
74+
// $event->setResponse('');
75+
// $fileId = 1234;
76+
}
77+
}

lib/Lookup.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace OCA\GlobalSiteSelector;
1111

12+
use JsonException;
1213
use OCP\Federation\ICloudIdManager;
1314
use OCP\Http\Client\IClientService;
1415
use OCP\IConfig;
@@ -22,6 +23,7 @@ public function __construct(
2223
private IClientService $clientService,
2324
private LoggerInterface $logger,
2425
private ICloudIdManager $cloudIdManager,
26+
private GlobalSiteSelector $gss,
2527
private IConfig $config,
2628
) {
2729
$this->lookupServerUrl = $this->config->getSystemValueString('lookup_server', '');
@@ -148,6 +150,16 @@ private function getUserLocation_Sanitize(string $address, string &$uid): string
148150
return $address;
149151
}
150152

153+
public function getInstances(): array {
154+
$client = $this->clientService->newClient();
155+
$response = $client->get($this->lookupServerUrl . '/gs/instances', $this->configureClient(['body' => json_encode(['authKey' => $this->gss->getJwtKey()])]));
156+
157+
try {
158+
return json_decode($response->getBody(), true, flags: JSON_THROW_ON_ERROR);
159+
} catch (JsonException) {
160+
return [];
161+
}
162+
}
151163

152164
public function sanitizeUid(string &$uid = ''): void {
153165
if ($this->config->getSystemValueString('gss.username_format', '') !== 'sanitize') {

0 commit comments

Comments
 (0)