Skip to content

Commit

Permalink
feat: Saving file
Browse files Browse the repository at this point in the history
Signed-off-by: Hoang Pham <[email protected]>

feat: Saving file

Signed-off-by: Hoang Pham <[email protected]>

feat: Saving file

Signed-off-by: Hoang Pham <[email protected]>

feat: Saving file

Signed-off-by: Hoang Pham <[email protected]>
  • Loading branch information
hweihwang authored and juliusknorr committed Jul 23, 2024
1 parent 71539b5 commit 408f158
Show file tree
Hide file tree
Showing 16 changed files with 559 additions and 247 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ TLS=false
TLS_KEY=
TLS_CERT=

# Turn off SSL certificate validation in development mode for easier testing
IS_DEV=false

# Prometheus metrics endpoint
# Set this to access the monitoring endpoint at /metrics
# either providing it as Bearer token or as ?token= query parameter
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"autoloader-suffix": "Whiteboard",
"optimize-autoloader": true,
"platform": {
"php": "8.1"
"php": "8.0"
},
"sort-packages": true
},
"license": "AGPL",
"require": {
"php": "^8.1",
"php": "^8.0",
"firebase/php-jwt": "^6.10"
},
"require-dev": {
Expand Down
15 changes: 15 additions & 0 deletions lib/Consts/JWTConsts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Whiteboard\Consts;

final class JWTConsts {
public const JWT_ALGORITHM = 'HS256';
public const EXPIRATION_TIME = 15 * 60;
}
92 changes: 16 additions & 76 deletions lib/Controller/JWTController.php
Original file line number Diff line number Diff line change
@@ -1,40 +1,33 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Whiteboard\Controller;

use Firebase\JWT\JWT;
use OC\User\NoUserException;
use OCA\Whiteboard\Service\ConfigService;
use OCA\Whiteboard\Service\AuthenticationService;
use OCA\Whiteboard\Service\ExceptionService;
use OCA\Whiteboard\Service\FileService;
use OCA\Whiteboard\Service\JWTService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\Files\InvalidPathException;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\IRequest;
use OCP\IUserSession;

/**
* @psalm-suppress UndefinedClass
* @psalm-suppress MissingDependency
*/
final class JWTController extends Controller {
private const EXPIRATION_TIME = 15 * 60;

public const JWT_ALGORITHM = 'HS256';

public function __construct(
IRequest $request,
private IUserSession $userSession,
private ConfigService $configService,
private IRootFolder $rootFolder
IRequest $request,
private AuthenticationService $authService,
private FileService $fileService,
private JWTService $jwtService,
private ExceptionService $exceptionService
) {
parent::__construct('whiteboard', $request);
}
Expand All @@ -44,66 +37,13 @@ public function __construct(
* @NoAdminRequired
*/
public function getJWT(int $fileId): DataResponse {
if (!$this->userSession->isLoggedIn()) {
return new DataResponse(['message' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED);
}

$user = $this->userSession->getUser();

if ($user === null) {
return new DataResponse(['message' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED);
}

$userId = $user->getUID();
try {
$folder = $this->rootFolder->getUserFolder($userId);
} catch (NotPermittedException $e) {
return new DataResponse(['message' => 'Access denied'], Http::STATUS_FORBIDDEN);
} catch (NoUserException $e) {
return new DataResponse(['message' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED);
}

$file = $folder->getById($fileId)[0] ?? null;

if ($file === null) {
return new DataResponse(['message' => 'File not found or access denied'], Http::STATUS_FORBIDDEN);
}

try {
$readable = $file->isReadable();
} catch (InvalidPathException|NotFoundException $e) {
return new DataResponse(['message' => 'Access denied'], Http::STATUS_FORBIDDEN);
$user = $this->authService->getAuthenticatedUser();
$file = $this->fileService->getUserFileById($user->getUID(), $fileId);
$jwt = $this->jwtService->generateJWT($user, $file, $fileId);
return new DataResponse(['token' => $jwt]);
} catch (\Exception $e) {
return $this->exceptionService->handleException($e);
}

if (!$readable) {
return new DataResponse(['message' => 'Access denied'], Http::STATUS_FORBIDDEN);
}

try {
$permissions = $file->getPermissions();
} catch (InvalidPathException $e) {
return new DataResponse(['message' => 'Access denied'], Http::STATUS_FORBIDDEN);
} catch (NotFoundException $e) {
return new DataResponse(['message' => 'File not found'], Http::STATUS_NOT_FOUND);
}

$key = $this->configService->getJwtSecretKey();
$issuedAt = time();
$expirationTime = $issuedAt + self::EXPIRATION_TIME;
$payload = [
'userid' => $userId,
'fileId' => $fileId,
'permissions' => $permissions,
'user' => [
'id' => $userId,
'name' => $user->getDisplayName()
],
'iat' => $issuedAt,
'exp' => $expirationTime
];

$jwt = JWT::encode($payload, $key, self::JWT_ALGORITHM);

return new DataResponse(['token' => $jwt]);
}
}
93 changes: 26 additions & 67 deletions lib/Controller/WhiteboardController.php
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Whiteboard\Controller;

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use OC\User\NoUserException;
use OCA\Whiteboard\Service\ConfigService;
use Exception;
use OCA\Whiteboard\Service\AuthenticationService;
use OCA\Whiteboard\Service\ExceptionService;
use OCA\Whiteboard\Service\FileService;
use OCA\Whiteboard\Service\WhiteboardContentService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\Files\IRootFolder;
use OCP\Files\NotPermittedException;
use OCP\IRequest;
use OCP\IUserSession;

/**
* @psalm-suppress UndefinedClass
Expand All @@ -31,79 +29,40 @@ final class WhiteboardController extends ApiController {
public function __construct(
$appName,
IRequest $request,
private IUserSession $userSession,
private IRootFolder $rootFolder,
private ConfigService $configService
private AuthenticationService $authService,
private FileService $fileService,
private WhiteboardContentService $contentService,
private ExceptionService $exceptionService
) {
parent::__construct($appName, $request);
}

/**
* @throws NotPermittedException
* @throws NoUserException
* @throws \JsonException
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[PublicPage]
public function update(int $fileId, array $data): DataResponse {
$user = $this->userSession->getUser();
$userFolder = $this->rootFolder->getUserFolder($user?->getUID());
$file = $userFolder->getById($fileId)[0];

if (empty($data)) {
$data = ['elements' => [], 'scrollToContent' => true];
public function show(int $fileId): DataResponse {
try {
$userId = $this->authService->authenticateJWT($this->request);
$file = $this->fileService->getUserFileById($userId, $fileId);
$data = $this->contentService->getContent($file);
return new DataResponse(['data' => $data]);
} catch (Exception $e) {
return $this->exceptionService->handleException($e);
}

$file->putContent(json_encode($data, JSON_THROW_ON_ERROR));

return new DataResponse(['status' => 'success']);
}

/**
* @throws NotPermittedException
* @throws NoUserException
* @throws \JsonException
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[PublicPage]
public function show(int $fileId): DataResponse {
$authHeader = $this->request->getHeader('Authorization');

if (!$authHeader) {
return new DataResponse(['message' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED);
}

$assignedValues = sscanf($authHeader, 'Bearer %s', $jwt);

if (!$assignedValues) {
return new DataResponse(['message' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED);
}

if (!$jwt || !is_string($jwt)) {
return new DataResponse(['message' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED);
}

public function update(int $fileId, array $data): DataResponse {
try {
$key = $this->configService->getJwtSecretKey();
$decoded = JWT::decode($jwt, new Key($key, JWTController::JWT_ALGORITHM));
$userId = $decoded->userid;
} catch (\Exception $e) {
return new DataResponse(['message' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED);
$this->authService->authenticateSharedToken($this->request, $fileId);
$user = $this->authService->getAndSetUser($this->request);
$file = $this->fileService->getUserFileById($user->getUID(), $fileId);
$this->contentService->updateContent($file, $data);
return new DataResponse(['status' => 'success']);
} catch (Exception $e) {
return $this->exceptionService->handleException($e);
}

$userFolder = $this->rootFolder->getUserFolder($userId);
$file = $userFolder->getById($fileId)[0];

$fileContent = $file->getContent();
if ($fileContent === '') {
$fileContent = '{"elements":[],"scrollToContent":true}';
}
$data = json_decode($fileContent, true, 512, JSON_THROW_ON_ERROR);

return new DataResponse([
'data' => $data,
]);
}
}
Loading

0 comments on commit 408f158

Please sign in to comment.