Skip to content

Commit d0537e5

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

File tree

10 files changed

+647
-12
lines changed

10 files changed

+647
-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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
return [
1010
'ocs' => [
1111
['name' => 'Slave#createAppToken', 'url' => '/v1/createapptoken', 'verb' => 'GET'],
12+
['name' => 'Slave#discovery', 'url' => '/discovery', 'verb' => 'GET'],
13+
['name' => 'Slave#sharedFile', 'url' => '/sharedfile', 'verb' => 'GET'],
1214
],
1315
'routes' => [
1416
[

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: 48 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;
@@ -21,6 +22,7 @@
2122
use OCA\GlobalSiteSelector\Vendor\Firebase\JWT\Key;
2223
use OCP\AppFramework\Http;
2324
use OCP\AppFramework\Http\DataResponse;
25+
use OCP\AppFramework\Http\JSONResponse;
2426
use OCP\AppFramework\Http\RedirectResponse;
2527
use OCP\AppFramework\OCSController;
2628
use OCP\IConfig;
@@ -41,25 +43,59 @@
4143
* @package OCA\GlobalSiteSelector\Controller
4244
*/
4345
class SlaveController extends OCSController {
44-
4546
public function __construct(
4647
$appName,
4748
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,
49+
private readonly GlobalSiteSelector $gss,
50+
private readonly IUserSession $userSession,
51+
private readonly IURLGenerator $urlGenerator,
52+
private readonly ICrypto $crypto,
53+
private readonly TokenHandler $tokenHandler,
54+
private readonly IUserManager $userManager,
55+
private readonly UserBackend $userBackend,
56+
private readonly ISession $session,
57+
private readonly SlaveService $slaveService,
58+
private readonly GlobalScaleService $globalScaleService,
59+
private readonly IConfig $config,
60+
private readonly LoggerInterface $logger,
5961
) {
6062
parent::__construct($appName, $request);
6163
}
6264

65+
66+
/**
67+
* @PublicPage
68+
* @NoCSRFRequired
69+
*/
70+
public function discovery(): DataResponse {
71+
// public data, reachable from outside.
72+
return new DataResponse(['token' => $this->globalScaleService->getLocalToken()]);
73+
}
74+
75+
/**
76+
* @PublicPage
77+
* @NoCSRFRequired
78+
*/
79+
public function sharedFile(?string $jwt): DataResponse {
80+
if ($jwt === null) {
81+
return new DataResponse(['message' => 'missing jwt'], Http::STATUS_NOT_FOUND);
82+
}
83+
$key = $this->gss->getJwtKey();
84+
$decoded = (array)JWT::decode($jwt, new Key($key, Application::JWT_ALGORITHM));
85+
$fileId = $decoded['fileId'] ?? 0;
86+
$instance = $decoded['instance'] ?? '';
87+
88+
if ($fileId === 0 || $instance === '') {
89+
return new DataResponse(['message' => 'missing argument'], Http::STATUS_NOT_FOUND);
90+
}
91+
92+
$files = $this->globalScaleService->getRelatedFilesDetails((int)$fileId);
93+
$shares = $this->globalScaleService->getFederatedShareIdFromRemote($files, $instance);
94+
95+
return new DataResponse($shares);
96+
}
97+
98+
6399
/**
64100
* @PublicPage
65101
* @NoCSRFRequired

0 commit comments

Comments
 (0)