Skip to content

Commit

Permalink
feat(attachments): Allow to get attachments without document session
Browse files Browse the repository at this point in the history
For all read-only attachments API endpoints, add support to authorize
with user session or share token when no document session is available.

Allows to get the attachments list and attachment files from
MarkdownContentEditor.vue without a document session.

Signed-off-by: Jonas <jonas@freesources.org>
  • Loading branch information
mejo- committed Nov 27, 2023
1 parent 47c0284 commit 3a5dd3c
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 26 deletions.
37 changes: 23 additions & 14 deletions lib/Controller/AttachmentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
namespace OCA\Text\Controller;

use Exception;
use OCA\Text\Exception\InvalidSessionException;
use OCA\Text\Exception\UploadException;
use OCA\Text\Middleware\Attribute\RequireDocumentSession;
use OCA\Text\Middleware\Attribute\RequireDocumentSessionUserOrShareToken;
use OCA\Text\Service\AttachmentService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
Expand Down Expand Up @@ -83,15 +85,22 @@ public function __construct(

#[NoAdminRequired]
#[PublicPage]
#[RequireDocumentSession]
#[RequireDocumentSessionUserOrShareToken]
public function getAttachmentList(?string $shareToken = null): DataResponse {
$documentId = $this->getSession()->getDocumentId();
$documentId = $this->getDocument()->getId();
try {
$session = $this->getSession();
} catch (InvalidSessionException) {
$session = null;
}

if ($shareToken) {
$attachments = $this->attachmentService->getAttachmentList($documentId, null, $this->getSession(), $shareToken);
$attachments = $this->attachmentService->getAttachmentList($documentId, null, $session, $shareToken);
} else {
$userId = $this->getSession()->getUserId();
$attachments = $this->attachmentService->getAttachmentList($documentId, $userId, $this->getSession(), null);
$userId = $this->getUserId();
$attachments = $this->attachmentService->getAttachmentList($documentId, $userId, $session, null);
}

return new DataResponse($attachments);
}

Expand Down Expand Up @@ -183,16 +192,16 @@ private function getUploadedFile(string $key): array {
#[NoAdminRequired]
#[PublicPage]
#[NoCSRFRequired]
#[RequireDocumentSession]
#[RequireDocumentSessionUserOrShareToken]
public function getImageFile(string $imageFileName, ?string $shareToken = null,
int $preferRawImage = 0): DataResponse|DataDownloadResponse {
$documentId = $this->getSession()->getDocumentId();
$documentId = $this->getDocument()->getId();

try {
if ($shareToken) {
$imageFile = $this->attachmentService->getImageFilePublic($documentId, $imageFileName, $shareToken, $preferRawImage === 1);
} else {
$userId = $this->getSession()->getUserId();
$userId = $this->getUserId();
$imageFile = $this->attachmentService->getImageFile($documentId, $imageFileName, $userId, $preferRawImage === 1);
}
return $imageFile !== null
Expand All @@ -218,15 +227,15 @@ public function getImageFile(string $imageFileName, ?string $shareToken = null,
#[NoAdminRequired]
#[PublicPage]
#[NoCSRFRequired]
#[RequireDocumentSession]
#[RequireDocumentSessionUserOrShareToken]
public function getMediaFile(string $mediaFileName, ?string $shareToken = null): DataResponse|DataDownloadResponse {
$documentId = $this->getSession()->getDocumentId();
$documentId = $this->getDocument()->getId();

try {
if ($shareToken) {
$mediaFile = $this->attachmentService->getMediaFilePublic($documentId, $mediaFileName, $shareToken);
} else {
$userId = $this->getSession()->getUserId();
$userId = $this->getUserId();
$mediaFile = $this->attachmentService->getMediaFile($documentId, $mediaFileName, $userId);
}
return $mediaFile !== null
Expand All @@ -249,15 +258,15 @@ public function getMediaFile(string $mediaFileName, ?string $shareToken = null):
#[NoAdminRequired]
#[PublicPage]
#[NoCSRFRequired]
#[RequireDocumentSession]
#[RequireDocumentSessionUserOrShareToken]
public function getMediaFilePreview(string $mediaFileName, ?string $shareToken = null) {
$documentId = $this->getSession()->getDocumentId();
$documentId = $this->getDocument()->getId();

try {
if ($shareToken) {
$preview = $this->attachmentService->getMediaFilePreviewPublic($documentId, $mediaFileName, $shareToken);
} else {
$userId = $this->getSession()->getUserId();
$userId = $this->getUserId();
$preview = $this->attachmentService->getMediaFilePreview($documentId, $mediaFileName, $userId);
}
if ($preview === null) {
Expand Down
2 changes: 2 additions & 0 deletions lib/Controller/ISessionAwareController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public function getSession(): Session;
public function setSession(Session $session): void;
public function getDocument(): Document;
public function setDocument(Document $document): void;
public function getUserId(): string;
public function setUserId(string $userId): void;
}
12 changes: 12 additions & 0 deletions lib/Controller/TSessionAwareController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
trait TSessionAwareController {
private ?Session $textSession = null;
private ?Document $document = null;
private ?string $userId = null;

public function setSession(?Session $session): void {
$this->textSession = $session;
Expand All @@ -20,6 +21,10 @@ public function setDocument(?Document $document): void {
$this->document = $document;
}

public function setUserId(?string $userId): void {
$this->userId = $userId;
}

public function getSession(): Session {
if ($this->textSession === null) {
throw new InvalidSessionException();
Expand All @@ -36,4 +41,11 @@ public function getDocument(): Document {
return $this->document;
}

public function getUserId(): string {
if ($this->userId === null) {
throw new InvalidSessionException();
}

return $this->userId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace OCA\Text\Middleware\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class RequireDocumentSessionUserOrShareToken {
}
39 changes: 37 additions & 2 deletions lib/Middleware/SessionMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
use OCA\Text\Controller\ISessionAwareController;
use OCA\Text\Exception\InvalidSessionException;
use OCA\Text\Middleware\Attribute\RequireDocumentSession;
use OCA\Text\Middleware\Attribute\RequireDocumentSessionUserOrShareToken;
use OCA\Text\Service\DocumentService;
use OCA\Text\Service\SessionService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
use OCP\IUserSession;
use ReflectionException;

class SessionMiddleware extends \OCP\AppFramework\Middleware {
Expand All @@ -18,20 +21,29 @@ public function __construct(
private IRequest $request,
private SessionService $sessionService,
private DocumentService $documentService,
private IUserSession $userSession,
) {
}

/**
* @throws ReflectionException
* @throws InvalidSessionException
*/
public function beforeController(Controller $controller, string $methodName) {
public function beforeController(Controller $controller, string $methodName): void {
if (!$controller instanceof ISessionAwareController) {
return;
}

$reflectionMethod = new \ReflectionMethod($controller, $methodName);

if (!empty($reflectionMethod->getAttributes(RequireDocumentSessionUserOrShareToken::class))) {
try {
$this->assertDocumentSession($controller);
} catch (InvalidSessionException) {
$this->assertUserOrShareToken($controller);
}
}

if (!empty($reflectionMethod->getAttributes(RequireDocumentSession::class))) {
$this->assertDocumentSession($controller);
}
Expand All @@ -41,6 +53,7 @@ private function assertDocumentSession(ISessionAwareController $controller): voi
$documentId = (int)$this->request->getParam('documentId');
$sessionId = (int)$this->request->getParam('sessionId');
$token = (string)$this->request->getParam('sessionToken');
$shareToken = (string)$this->request->getParam('token');

$session = $this->sessionService->getValidSession($documentId, $sessionId, $token);
if (!$session) {
Expand All @@ -54,9 +67,31 @@ private function assertDocumentSession(ISessionAwareController $controller): voi

$controller->setSession($session);
$controller->setDocument($document);
if (!$shareToken) {
$controller->setUserId($session->getUserId());
}
}

private function assertUserOrShareToken(ISessionAwareController $controller): void {
$documentId = (int)$this->request->getParam('documentId');
if (null !== $userId = $this->userSession->getUser()?->getUID()) {
$controller->setUserId($userId);
// TODO: check if user has access to document
} elseif (null !== $shareToken = (string)$this->request->getParam('shareToken')) {
// TODO: check if shareToken has access to document
} else {
throw new InvalidSessionException();
}

$document = $this->documentService->getDocument($documentId);
if (!$document) {
throw new InvalidSessionException();
}

$controller->setDocument($document);
}

public function afterException($controller, $methodName, \Exception $exception) {
public function afterException($controller, $methodName, \Exception $exception): DataResponse|Response {
if ($exception instanceof InvalidSessionException) {
return new DataResponse([], 403);
}
Expand Down
18 changes: 12 additions & 6 deletions lib/Service/AttachmentService.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,9 @@ public function getAttachmentList(int $documentId, ?string $userId = null, ?Sess
$shareTokenUrlString = $shareToken
? '&shareToken=' . urlencode($shareToken)
: '';
// TODO: session might be null
$sessionUrlParamsBase = '?documentId=' . $documentId . '&sessionId=' . $session->getId() . '&sessionToken=' . urlencode($session->getToken()) . $shareTokenUrlString;
$urlParamsBase = $session
? '?documentId=' . $documentId . '&sessionId=' . $session->getId() . '&sessionToken=' . urlencode($session->getToken()) . $shareTokenUrlString
: '?documentId=' . $documentId . $shareTokenUrlString;

$attachments = [];
foreach ($attachmentDir->getDirectoryListing() as $node) {
Expand All @@ -252,11 +253,16 @@ public function getAttachmentList(int $documentId, ?string $userId = null, ?Sess
'mtime' => $node->getMTime(),
'isImage' => $isImage,
'fullUrl' => $isImage
? $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getImageFile') . $sessionUrlParamsBase . '&imageFileName=' . urlencode($name) . '&preferRawImage=1'
: $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getMediaFile') . $sessionUrlParamsBase . '&mediaFileName=' . urlencode($name),
? $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getImageFile') . $urlParamsBase . '&imageFileName=' . urlencode($name) . '&preferRawImage=1'
: $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getMediaFile') . $urlParamsBase . '&mediaFileName=' . urlencode($name),
'previewUrl' => $isImage
? $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getImageFile') . $sessionUrlParamsBase . '&imageFileName=' . urlencode($name)
: $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getMediaFilePreview') . $sessionUrlParamsBase . '&mediaFileName=' . urlencode($name),
? $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getImageFile') . $urlParamsBase . '&imageFileName=' . urlencode($name)
: $this->urlGenerator->linkToRouteAbsolute('text.Attachment.getMediaFilePreview') . $urlParamsBase . '&mediaFileName=' . urlencode($name),
/*
: ($isImage
? $this->urlGenerator->linkTo('', 'remote.php') . '/dav/files/' . $userId . '/' . implode('/', array_map('rawurlencode', array_slice(explode('/', $node->getPath()), 3)))
: ''),
*/
];
}

Expand Down
7 changes: 3 additions & 4 deletions src/services/AttachmentResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export default class AttachmentResolver {
#user
#shareToken
#currentDirectory
#attachmentDirectory
#documentId
#initAttachmentListPromise

Expand All @@ -47,7 +46,6 @@ export default class AttachmentResolver {
this.#shareToken = shareToken
this.#currentDirectory = currentDirectory
this.#documentId = fileId ?? session?.documentId
this.#attachmentDirectory = `.attachments.${this.#documentId}`
this.#initAttachmentListPromise = this.#updateAttachmentList()
}

Expand All @@ -64,8 +62,9 @@ export default class AttachmentResolver {
let attachment

// Native attachment
if (src.match(/^\.attachments\.\d+\//)) {
const imageFileName = decodeURIComponent(src.replace(`${this.#attachmentDirectory}/`, '').split('?')[0])
const directoryRegexp = /^\.attachments\.\d+\//
if (src.match(directoryRegexp)) {
const imageFileName = decodeURIComponent(src.replace(directoryRegexp, '').split('?')[0])

// Wait until attachment list got fetched (initialized by constructor)
await this.#initAttachmentListPromise
Expand Down

0 comments on commit 3a5dd3c

Please sign in to comment.