Skip to content

Commit bd1fcf6

Browse files
committed
public sharing
Signed-off-by: Hoang Pham <[email protected]>
1 parent 79c1d87 commit bd1fcf6

15 files changed

+433
-163
lines changed

lib/AppInfo/Application.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
use OCP\AppFramework\Bootstrap\IRegistrationContext;
2121
use OCP\Files\Template\RegisterTemplateCreatorEvent;
2222
use OCP\Security\CSP\AddContentSecurityPolicyEvent;
23+
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
24+
use OCA\Whiteboard\Listener\BeforeTemplateRenderedListener;
25+
2326

2427
class Application extends App implements IBootstrap {
2528
public const APP_ID = 'whiteboard';
@@ -34,6 +37,7 @@ public function register(IRegistrationContext $context): void {
3437
$context->registerEventListener(AddContentSecurityPolicyEvent::class, AddContentSecurityPolicyListener::class);
3538
$context->registerEventListener(LoadViewer::class, LoadViewerListener::class);
3639
$context->registerEventListener(RegisterTemplateCreatorEvent::class, RegisterTemplateCreatorListener::class);
40+
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
3741
}
3842

3943
public function boot(IBootContext $context): void {

lib/Controller/JWTController.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ public function __construct(
3535
/**
3636
* @NoCSRFRequired
3737
* @NoAdminRequired
38+
* @PublicPage
3839
*/
3940
public function getJWT(int $fileId): DataResponse {
4041
try {
41-
$user = $this->authService->getAuthenticatedUser();
42-
$file = $this->fileService->getUserFileById($user->getUID(), $fileId);
43-
$jwt = $this->jwtService->generateJWT($user, $file, $fileId);
42+
$publicSharingToken = $this->request->getParam('publicSharingToken');
43+
$user = $this->authService->getAuthenticatedUser($publicSharingToken);
44+
$file = $this->fileService->getFile($user, $fileId);
45+
$jwt = $this->jwtService->generateJWT($user, $file);
4446
return new DataResponse(['token' => $jwt]);
4547
} catch (\Exception $e) {
4648
return $this->exceptionService->handleException($e);

lib/Controller/WhiteboardController.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,27 @@
1313
use OCA\Whiteboard\Service\AuthenticationService;
1414
use OCA\Whiteboard\Service\ExceptionService;
1515
use OCA\Whiteboard\Service\FileService;
16+
use OCA\Whiteboard\Service\JWTService;
1617
use OCA\Whiteboard\Service\WhiteboardContentService;
1718
use OCP\AppFramework\ApiController;
1819
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
1920
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
2021
use OCP\AppFramework\Http\Attribute\PublicPage;
2122
use OCP\AppFramework\Http\DataResponse;
2223
use OCP\IRequest;
24+
use RuntimeException;
25+
use OCP\AppFramework\Http;
2326

2427
/**
2528
* @psalm-suppress UndefinedClass
2629
* @psalm-suppress UndefinedDocblockClass
2730
*/
28-
final class WhiteboardController extends ApiController {
31+
final class WhiteboardController extends ApiController
32+
{
2933
public function __construct(
3034
$appName,
3135
IRequest $request,
36+
private JWTService $jwtService,
3237
private AuthenticationService $authService,
3338
private FileService $fileService,
3439
private WhiteboardContentService $contentService,
@@ -40,10 +45,16 @@ public function __construct(
4045
#[NoAdminRequired]
4146
#[NoCSRFRequired]
4247
#[PublicPage]
43-
public function show(int $fileId): DataResponse {
48+
public function show(int $fileId): DataResponse
49+
{
4450
try {
45-
$userId = $this->authService->authenticateJWT($this->request);
46-
$file = $this->fileService->getUserFileById($userId, $fileId);
51+
$authHeader = $this->request->getHeader('Authorization');
52+
if (sscanf($authHeader, 'Bearer %s', $jwt) !== 1) {
53+
throw new RuntimeException('Unauthorized', Http::STATUS_UNAUTHORIZED);
54+
}
55+
$userId = $this->jwtService->getUserIdFromJWT($jwt);
56+
$user = $this->authService->getWhiteboardUser($userId);
57+
$file = $this->fileService->getFile($user, $fileId);
4758
$data = $this->contentService->getContent($file);
4859
return new DataResponse(['data' => $data]);
4960
} catch (Exception $e) {
@@ -54,11 +65,14 @@ public function show(int $fileId): DataResponse {
5465
#[NoAdminRequired]
5566
#[NoCSRFRequired]
5667
#[PublicPage]
57-
public function update(int $fileId, array $data): DataResponse {
68+
public function update(int $fileId, array $data): DataResponse
69+
{
5870
try {
59-
$this->authService->authenticateSharedToken($this->request, $fileId);
60-
$user = $this->authService->getAndSetUser($this->request);
61-
$file = $this->fileService->getUserFileById($user->getUID(), $fileId);
71+
$backendSharedToken = $this->request->getHeader('X-Whiteboard-Auth');
72+
$this->authService->validateBackendSharedToken($backendSharedToken, $fileId);
73+
$userId = $this->request->getHeader('X-Whiteboard-User');
74+
$user = $this->authService->getWhiteboardUser($userId);
75+
$file = $this->fileService->getFile($user, $fileId);
6276
$this->contentService->updateContent($file, $data);
6377
return new DataResponse(['status' => 'success']);
6478
} catch (Exception $e) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
11+
namespace OCA\Whiteboard\Listener;
12+
13+
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
14+
use OCP\AppFramework\Services\IInitialState;
15+
use OCP\EventDispatcher\Event;
16+
use OCP\EventDispatcher\IEventListener;
17+
18+
/** @template-implements IEventListener<BeforeTemplateRenderedEvent|Event> */
19+
class BeforeTemplateRenderedListener implements IEventListener {
20+
public function __construct(
21+
private IInitialState $initialState,
22+
) {
23+
}
24+
25+
public function handle(Event $event): void {
26+
if (!($event instanceof BeforeTemplateRenderedEvent)) {
27+
return;
28+
}
29+
30+
$this->initialState->provideInitialState(
31+
'file_id',
32+
$event->getShare()->getNodeId()
33+
);
34+
}
35+
}

lib/Listener/RegisterTemplateCreatorListener.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function handle(Event $event): void {
2525
if (!($event instanceof RegisterTemplateCreatorEvent)) {
2626
return;
2727
}
28+
2829

2930
$event->getTemplateManager()->registerTemplateFileCreator(function () {
3031
$whiteboard = new TemplateFileCreator(Application::APP_ID, $this->l10n->t('New whiteboard'), '.whiteboard');

lib/Model/AuthenticatedUser.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Whiteboard\Model;
10+
11+
use OCP\IUser;
12+
13+
class AuthenticatedUser implements User {
14+
public function __construct(private IUser $user) {
15+
}
16+
17+
public function getUID(): string {
18+
return $this->user->getUID();
19+
}
20+
21+
public function getDisplayName(): string {
22+
return $this->user->getDisplayName();
23+
}
24+
}

lib/Model/PublicSharingUser.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Whiteboard\Model;
10+
11+
class PublicSharingUser implements User {
12+
public function __construct(
13+
private string $publicSharingToken
14+
) {
15+
}
16+
17+
public function getUID(): string {
18+
return $this->generateRandomUID();
19+
}
20+
21+
public function getDisplayName(): string {
22+
return $this->generateRandomDisplayName();
23+
}
24+
25+
public function getPublicSharingToken(): string {
26+
return $this->publicSharingToken;
27+
}
28+
29+
private function generateRandomUID(): string {
30+
return 'shared_' . $this->publicSharingToken . '_' . bin2hex(random_bytes(8));
31+
}
32+
33+
private function generateRandomDisplayName(): string {
34+
$adjectives = ['Anonymous', 'Mysterious', 'Incognito', 'Unknown', 'Unnamed'];
35+
$nouns = ['User', 'Visitor', 'Guest', 'Collaborator', 'Participant'];
36+
37+
$adjective = $adjectives[array_rand($adjectives)];
38+
$noun = $nouns[array_rand($nouns)];
39+
40+
return $adjective . ' ' . $noun;
41+
}
42+
}

lib/Model/User.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Whiteboard\Model;
10+
11+
interface User
12+
{
13+
public function getUID(): string;
14+
public function getDisplayName(): string;
15+
}

lib/Service/AuthenticationService.php

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@
1010
namespace OCA\Whiteboard\Service;
1111

1212
use Exception;
13-
use Firebase\JWT\JWT;
14-
use Firebase\JWT\Key;
15-
use OCA\Whiteboard\Consts\JWTConsts;
13+
use OCA\Whiteboard\Model\AuthenticatedUser;
14+
use OCA\Whiteboard\Model\PublicSharingUser;
15+
use OCA\Whiteboard\Model\User;
1616
use OCP\AppFramework\Http;
17-
use OCP\IRequest;
18-
use OCP\IUser;
1917
use OCP\IUserManager;
2018
use OCP\IUserSession;
19+
use OCP\Share\IManager as ShareManager;
2120
use RuntimeException;
2221

2322
/**
@@ -27,54 +26,49 @@ final class AuthenticationService {
2726
public function __construct(
2827
private ConfigService $configService,
2928
private IUserManager $userManager,
30-
private IUserSession $userSession
29+
private IUserSession $userSession,
30+
private ShareManager $shareManager
3131
) {
3232
}
3333

3434
/**
3535
* @throws Exception
3636
*/
37-
public function authenticateJWT(IRequest $request): string {
38-
$authHeader = $request->getHeader('Authorization');
39-
if (!$authHeader || sscanf($authHeader, 'Bearer %s', $jwt) !== 1) {
40-
throw new RuntimeException('Unauthorized', Http::STATUS_UNAUTHORIZED);
37+
public function getAuthenticatedUser(?string $publicSharingToken = null): User {
38+
if ($publicSharingToken) {
39+
return $this->getPublicSharingUser($publicSharingToken);
4140
}
4241

43-
if (!is_string($jwt)) {
44-
throw new RuntimeException('JWT token must be a string', Http::STATUS_BAD_REQUEST);
42+
if (!$this->userSession->isLoggedIn()) {
43+
throw new RuntimeException('Unauthorized', Http::STATUS_UNAUTHORIZED);
4544
}
4645

47-
try {
48-
$key = $this->configService->getJwtSecretKey();
49-
50-
return JWT::decode($jwt, new Key($key, JWTConsts::JWT_ALGORITHM))->userid;
51-
} catch (Exception) {
46+
$user = $this->userSession->getUser();
47+
if ($user === null) {
5248
throw new RuntimeException('Unauthorized', Http::STATUS_UNAUTHORIZED);
5349
}
50+
51+
return new AuthenticatedUser($user);
5452
}
5553

5654
/**
5755
* @throws Exception
5856
*/
59-
public function getAuthenticatedUser(): IUser {
60-
if (!$this->userSession->isLoggedIn()) {
61-
throw new RuntimeException('Unauthorized', Http::STATUS_UNAUTHORIZED);
62-
}
57+
private function getPublicSharingUser(string $publicSharingToken): User {
58+
try {
59+
$this->shareManager->getShareByToken($publicSharingToken);
6360

64-
$user = $this->userSession->getUser();
65-
if ($user === null) {
66-
throw new RuntimeException('Unauthorized', Http::STATUS_UNAUTHORIZED);
61+
return new PublicSharingUser($publicSharingToken);
62+
} catch (Exception $e) {
63+
throw new RuntimeException('Invalid sharing token', Http::STATUS_UNAUTHORIZED);
6764
}
68-
69-
return $user;
7065
}
7166

7267
/**
7368
* @throws Exception
7469
*/
75-
public function authenticateSharedToken(IRequest $request, int $fileId): void {
76-
$whiteboardAuth = $request->getHeader('X-Whiteboard-Auth');
77-
if (!$whiteboardAuth || !$this->verifySharedToken($whiteboardAuth, $fileId)) {
70+
public function validateBackendSharedToken(string $backendSharedToken, int $fileId): void {
71+
if (!$backendSharedToken || !$this->verifySharedToken($backendSharedToken, $fileId)) {
7872
throw new RuntimeException('Unauthorized', Http::STATUS_UNAUTHORIZED);
7973
}
8074
}
@@ -96,15 +90,22 @@ private function verifySharedToken(string $token, int $fileId): bool {
9690
/**
9791
* @throws Exception
9892
*/
99-
public function getAndSetUser(IRequest $request): IUser {
100-
$whiteboardUser = $request->getHeader('X-Whiteboard-User');
101-
$user = $this->userManager->get($whiteboardUser);
93+
public function getWhiteboardUser(string $userId): User {
94+
if (str_starts_with($userId, 'shared_')) {
95+
$parts = explode('_', $userId);
96+
if (count($parts) < 3) {
97+
throw new RuntimeException('Invalid user', Http::STATUS_BAD_REQUEST);
98+
}
99+
$sharingToken = $parts[1];
100+
return new PublicSharingUser($sharingToken);
101+
}
102+
103+
$user = $this->userManager->get($userId);
102104
if (!$user) {
103105
throw new RuntimeException('Invalid user', Http::STATUS_BAD_REQUEST);
104106
}
105-
106107
$this->userSession->setVolatileActiveUser($user);
107108

108-
return $user;
109+
return new AuthenticatedUser($user);
109110
}
110111
}

0 commit comments

Comments
 (0)