Skip to content

Commit f08e3ce

Browse files
committed
feat: global internal link
Signed-off-by: Maxence Lange <[email protected]>
1 parent ee39c92 commit f08e3ce

18 files changed

Lines changed: 1491 additions & 24 deletions

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: 8 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
[
@@ -21,5 +23,11 @@
2123
'url' => '/autologout',
2224
'verb' => 'GET'
2325
],
26+
[
27+
'name' => 'Slave#findFile',
28+
'url' => '/gf/{token}/{fileId}',
29+
'verb' => 'GET',
30+
'root' => '',
31+
],
2432
],
2533
];

lib/BackgroundJobs/UpdateLookupServer.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,23 @@
66
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
77
* SPDX-License-Identifier: AGPL-3.0-or-later
88
*/
9-
109
namespace OCA\GlobalSiteSelector\BackgroundJobs;
1110

1211
use OCA\GlobalSiteSelector\GlobalSiteSelector;
12+
use OCA\GlobalSiteSelector\Service\GlobalScaleService;
1313
use OCA\GlobalSiteSelector\Slave;
1414
use OCP\AppFramework\Utility\ITimeFactory;
1515
use OCP\BackgroundJob\IJob;
1616
use OCP\BackgroundJob\TimedJob;
1717
use OCP\IConfig;
1818

1919
class UpdateLookupServer extends TimedJob {
20-
21-
2220
public function __construct(
2321
ITimeFactory $time,
2422
IConfig $config,
25-
private GlobalSiteSelector $globalSiteSelector,
26-
private Slave $slave,
23+
private readonly GlobalScaleService $globalScaleService,
24+
private readonly GlobalSiteSelector $globalSiteSelector,
25+
private readonly Slave $slave,
2726
) {
2827
parent::__construct($time);
2928

@@ -36,6 +35,7 @@ protected function run($argument) {
3635
return;
3736
}
3837

38+
$this->globalScaleService->refreshTokenFromGlobalScale();
3939
$this->slave->batchUpdate();
4040
}
4141
}
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 to get details about each instances');
37+
}
38+
39+
/**
40+
* @param InputInterface $input
41+
* @param OutputInterface $output
42+
*
43+
* @return int
44+
*/
45+
protected function execute(InputInterface $input, OutputInterface $output): int {
46+
if ($input->getOption('current')) {
47+
$output->writeln(json_encode($this->appConfig->getValueArray(Application::APP_ID, ConfigLexicon::GS_TOKENS), JSON_PRETTY_PRINT));
48+
return 0;
49+
}
50+
51+
// currently, the only available data is a unique token that helps identify each instance
52+
$this->globalScaleService->refreshTokenFromGlobalScale();
53+
return 0;
54+
}
55+
}

lib/ConfigLexicon.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 OCP\Config\Lexicon\Entry;
12+
use OCP\Config\Lexicon\ILexicon;
13+
use OCP\Config\Lexicon\Strictness;
14+
use OCP\Config\ValueType;
15+
16+
class ConfigLexicon implements ILexicon {
17+
public const GS_TOKENS = 'globalScaleTokens';
18+
public const LOCAL_TOKEN = 'localToken';
19+
20+
public function getStrictness(): Strictness {
21+
return Strictness::IGNORE;
22+
}
23+
24+
/**
25+
* @inheritDoc
26+
*/
27+
public function getAppConfigs(): array {
28+
return [
29+
new Entry(key: self::GS_TOKENS, type: ValueType::ARRAY, defaultRaw: [], definition: 'list of token+host to navigate through GlobalScale', lazy: true),
30+
new Entry(key: self::LOCAL_TOKEN, type: ValueType::STRING, defaultRaw: '', definition: 'local token to id instance within GlobalScale', lazy: true),
31+
];
32+
}
33+
34+
/**
35+
* @inheritDoc
36+
*/
37+
public function getUserConfigs(): array {
38+
return [
39+
];
40+
}
41+
}

lib/Controller/SlaveController.php

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010

1111
use OC\Authentication\Token\IToken;
1212
use OCA\GlobalSiteSelector\AppInfo\Application;
13+
use OCA\GlobalSiteSelector\Exceptions\LocalFederatedShareException;
1314
use OCA\GlobalSiteSelector\Exceptions\MasterUrlException;
15+
use OCA\GlobalSiteSelector\Exceptions\SharedFileException;
1416
use OCA\GlobalSiteSelector\GlobalSiteSelector;
17+
use OCA\GlobalSiteSelector\Model\LocalFile;
18+
use OCA\GlobalSiteSelector\Service\GlobalScaleService;
19+
use OCA\GlobalSiteSelector\Service\GlobalShareService;
1520
use OCA\GlobalSiteSelector\Service\SlaveService;
1621
use OCA\GlobalSiteSelector\Slave;
1722
use OCA\GlobalSiteSelector\TokenHandler;
@@ -20,6 +25,9 @@
2025
use OCA\GlobalSiteSelector\Vendor\Firebase\JWT\JWT;
2126
use OCA\GlobalSiteSelector\Vendor\Firebase\JWT\Key;
2227
use OCP\AppFramework\Http;
28+
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
29+
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
30+
use OCP\AppFramework\Http\Attribute\PublicPage;
2331
use OCP\AppFramework\Http\DataResponse;
2432
use OCP\AppFramework\Http\RedirectResponse;
2533
use OCP\AppFramework\OCSController;
@@ -41,25 +49,77 @@
4149
* @package OCA\GlobalSiteSelector\Controller
4250
*/
4351
class SlaveController extends OCSController {
44-
4552
public function __construct(
4653
$appName,
4754
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,
55+
private readonly GlobalSiteSelector $gss,
56+
private readonly IUserSession $userSession,
57+
private readonly IURLGenerator $urlGenerator,
58+
private readonly ICrypto $crypto,
59+
private readonly TokenHandler $tokenHandler,
60+
private readonly IUserManager $userManager,
61+
private readonly UserBackend $userBackend,
62+
private readonly ISession $session,
63+
private readonly SlaveService $slaveService,
64+
private readonly GlobalScaleService $globalScaleService,
65+
private readonly GlobalShareService $globalShareService,
66+
private readonly IConfig $config,
67+
private readonly LoggerInterface $logger,
5968
) {
6069
parent::__construct($appName, $request);
6170
}
6271

72+
#[PublicPage]
73+
#[NoCSRFRequired]
74+
public function discovery(): DataResponse {
75+
// public data, reachable from outside.
76+
return new DataResponse(['token' => $this->globalScaleService->getLocalToken()]);
77+
}
78+
79+
/**
80+
* return sharing details about a file.
81+
* request must contain encoded jwt.
82+
*/
83+
#[NoAdminRequired]
84+
#[NoCSRFRequired]
85+
public function findFile(string $token, int $fileId): RedirectResponse {
86+
return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $this->globalShareService->getNewFileId($token, $fileId) ?? 1]));
87+
}
88+
89+
/**
90+
* return sharing details about a file.
91+
* request must contain encoded jwt.
92+
*/
93+
#[PublicPage]
94+
#[NoCSRFRequired]
95+
public function sharedFile(string $jwt): DataResponse {
96+
$key = $this->gss->getJwtKey();
97+
$decoded = (array)JWT::decode($jwt, new Key($key, Application::JWT_ALGORITHM));
98+
// JWT store data as stdClass, not array
99+
$decoded = json_decode(json_encode($decoded), true);
100+
101+
$this->logger->debug('decoded request', ['data' => $decoded]);
102+
103+
$fileId = (int)($decoded['fileId'] ?? 0);
104+
$shareId = (int)($decoded['shareId'] ?? 0);
105+
$instance = $decoded['instance'] ?? '';
106+
107+
$target = new LocalFile();
108+
$target->import($decoded['target'] ?? []);
109+
110+
try {
111+
// the file is local and returns shares related to it
112+
return new DataResponse($this->globalShareService->getSharedFiles($fileId, $shareId, $instance, $target));
113+
} catch (SharedFileException $e) {
114+
// file not found
115+
return new DataResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
116+
} catch (LocalFederatedShareException $e) {
117+
// the file is not local and returns the shared folder and the path to the file
118+
return new DataResponse($e->getFederatedShare(), Http::STATUS_MOVED_PERMANENTLY);
119+
}
120+
}
121+
122+
63123
/**
64124
* @PublicPage
65125
* @NoCSRFRequired

0 commit comments

Comments
 (0)