Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 150 additions & 66 deletions lib/Middleware/SessionMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace OCA\Text\Middleware;

use Exception;
use OC\User\NoUserException;
use OCA\Text\Controller\ISessionAwareController;
use OCA\Text\Db\Document;
use OCA\Text\Db\Session;
use OCA\Text\Exception\InvalidDocumentBaseVersionEtagException;
use OCA\Text\Exception\InvalidSessionException;
use OCA\Text\Middleware\Attribute\RequireDocumentBaseVersionEtag;
Expand All @@ -16,6 +19,7 @@
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotPermittedException;
use OCP\IL10N;
Expand All @@ -24,17 +28,20 @@
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use ReflectionException;
use ReflectionMethod;

class SessionMiddleware extends Middleware {

public function __construct(
private IRequest $request,
private SessionService $sessionService,
private IRequest $request,
private SessionService $sessionService,
private DocumentService $documentService,
private IUserSession $userSession,
private IRootFolder $rootFolder,
private ShareManager $shareManager,
private IL10N $l10n,
private IUserSession $userSession,
private IRootFolder $rootFolder,
private ShareManager $shareManager,
private IL10N $l10n,
private ?Document $document = null,
private ?Session $session = null,
private ?string $userId = null,
) {
}

Expand All @@ -48,108 +55,185 @@ public function beforeController(Controller $controller, string $methodName): vo
return;
}

$reflectionMethod = new \ReflectionMethod($controller, $methodName);
//ASSERTION
$documentId = $this->getDocumentId();
$this->document = $this->documentService->getDocument($documentId);
Comment on lines +59 to +60
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This always runs - even if non of the Require... attributes were set.

Before we'd only get the document if any of the attributes was there.
Is this intentional?

I don't think it matters in practice as I assume all ISessionAwareControllers require at least one of them. Just want to point out a change in behavior that i am seeing here.


$reflectionMethod = new ReflectionMethod($controller, $methodName);
$requiresDocumentBaseVersionEtag = !empty($reflectionMethod->getAttributes(RequireDocumentBaseVersionEtag::class));

if ($requiresDocumentBaseVersionEtag) {
$this->assertDocumentBaseVersionEtag();
}

$requiresDocumentSession = !empty($reflectionMethod->getAttributes(RequireDocumentSession::class));
$requiresDocumentSessionOrUserOrShareToken = !empty($reflectionMethod->getAttributes(RequireDocumentSessionOrUserOrShareToken::class));

if (!$requiresDocumentSession && !$requiresDocumentSessionOrUserOrShareToken) {
return;
}

$this->session = $this->sessionService->getValidSession($documentId, $this->getSessionId(), $this->getSessionToken());

try {
$this->assertDocumentSession();

if (!empty($reflectionMethod->getAttributes(RequireDocumentSessionOrUserOrShareToken::class))) {
try {
$this->assertDocumentSession($controller);
} catch (InvalidSessionException) {
$this->assertUserOrShareToken($controller);
if (!$this->getToken()) {
$this->userId = $this->session->getUserId();
}
} catch (InvalidSessionException) {
if (!$requiresDocumentSessionOrUserOrShareToken) {
throw new InvalidSessionException();
}

$this->assertUserOrShareToken();
}

if (!empty($reflectionMethod->getAttributes(RequireDocumentBaseVersionEtag::class))) {
$this->assertDocumentBaseVersionEtag();
//OTHERS
$this->setControllerData($controller);
}

public function afterException($controller, $methodName, Exception $exception): JSONResponse|Response {
if ($exception instanceof InvalidDocumentBaseVersionEtagException) {
return new JSONResponse(['error' => $this->l10n->t('Editing session has expired. Please reload the page.')], Http::STATUS_PRECONDITION_FAILED);
}

if (!empty($reflectionMethod->getAttributes(RequireDocumentSession::class))) {
$this->assertDocumentSession($controller);
if ($exception instanceof InvalidSessionException) {
return new JSONResponse([], 403);
}

return parent::afterException($controller, $methodName, $exception);
}

/**
* @throws InvalidDocumentBaseVersionEtagException
*/
private function assertDocumentBaseVersionEtag(): void {
$documentId = (int)$this->request->getParam('documentId');
$baseVersionEtag = $this->request->getParam('baseVersionEtag');
$baseVersionEtag = $this->getBaseVersionEtag();

$document = $this->documentService->getDocument($documentId);
if ($baseVersionEtag && $document?->getBaseVersionEtag() !== $baseVersionEtag) {
if ($baseVersionEtag && $this->document?->getBaseVersionEtag() !== $baseVersionEtag) {
throw new InvalidDocumentBaseVersionEtagException();
}
}

/**
* @throws InvalidSessionException
*/
private function assertDocumentSession(ISessionAwareController $controller): void {
$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) {
private function assertDocumentSession(): void {
if (!$this->document || !$this->session) {
throw new InvalidSessionException();
}
}

$document = $this->documentService->getDocument($documentId);
if (!$document) {

/**
* @throws InvalidSessionException
*/
private function assertUserOrShareToken(): void {
if (!$this->document) {
throw new InvalidSessionException();
}

$controller->setSession($session);
$controller->setDocument($document);
if (!$shareToken) {
$controller->setUserId($session->getUserId());
$documentId = $this->getDocumentId();

if (null !== ($userId = $this->getSessionUserId())) {
$this->assertUserHasAccessToDocument($userId, $documentId);

$this->userId = $userId;

return;
}

if (null !== ($shareToken = $this->getShareToken())) {
$this->assertShareTokenHasAccessToDocument($shareToken, $documentId);

return;
}

throw new InvalidSessionException();
}

/**
* @throws NotPermittedException
* @throws NoUserException
* @throws InvalidSessionException
*/
private function assertUserOrShareToken(ISessionAwareController $controller): void {
$documentId = (int)$this->request->getParam('documentId');
if (null !== $userId = $this->userSession->getUser()?->getUID()) {
// Check if user has access to document
if (count($this->rootFolder->getUserFolder($userId)->getById($documentId)) === 0) {
throw new InvalidSessionException();
}
$controller->setUserId($userId);
} elseif ('' !== $shareToken = (string)$this->request->getParam('shareToken')) {
try {
$share = $this->shareManager->getShareByToken($shareToken);
} catch (ShareNotFound) {
throw new InvalidSessionException();
}
// Check if shareToken has access to document
if (count($this->rootFolder->getUserFolder($share->getShareOwner())->getById($documentId)) === 0) {
throw new InvalidSessionException();
}
} else {
private function assertUserHasAccessToDocument(string $userId, int $documentId): void {
try {
$userFolder = $this->getUserFolder($userId);
} catch (NoUserException|NotPermittedException) {
throw new InvalidSessionException();
}

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

$controller->setDocument($document);
}

public function afterException($controller, $methodName, \Exception $exception): JSONResponse|Response {
if ($exception instanceof InvalidDocumentBaseVersionEtagException) {
return new JSONResponse(['error' => $this->l10n->t('Editing session has expired. Please reload the page.')], Http::STATUS_PRECONDITION_FAILED);
/**
* @throws InvalidSessionException
*/
private function assertShareTokenHasAccessToDocument(string $shareToken, int $documentId): void {
try {
$share = $this->shareManager->getShareByToken($shareToken);
} catch (ShareNotFound) {
throw new InvalidSessionException();
}

if ($exception instanceof InvalidSessionException) {
return new JSONResponse([], 403);
try {
$userFolder = $this->getUserFolder($share->getShareOwner());
} catch (NoUserException|NotPermittedException) {
throw new InvalidSessionException();
}

return parent::afterException($controller, $methodName, $exception);
if (count($userFolder->getById($documentId)) === 0) {
throw new InvalidSessionException();
}
}

private function getDocumentId(): int {
return (int)$this->request->getParam('documentId');
}

private function getSessionId(): int {
return (int)$this->request->getParam('sessionId');
}

private function getSessionToken(): string {
return (string)$this->request->getParam('sessionToken');
}

private function getToken(): string {
return (string)$this->request->getParam('token');
}

private function getShareToken(): ?string {
return $this->request->getParam('shareToken');
}

private function getBaseVersionEtag(): string {
return (string)$this->request->getParam('baseVersionEtag');
}

private function getSessionUserId(): ?string {
return $this->userSession->getUser()?->getUID();
}

/**
* @throws NotPermittedException
* @throws NoUserException
*/
private function getUserFolder(string $userId): Folder {
return $this->rootFolder->getUserFolder($userId);
}

private function setControllerData(ISessionAwareController $controller): void {
if ($this->document) {
$controller->setDocument($this->document);
}
if ($this->session) {
$controller->setSession($this->session);
}
if ($this->userId !== null) {
$controller->setUserId($this->userId);
}
}
}