Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Middleware): introduce InjectionMiddleware, with Table support first #927

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
17 changes: 17 additions & 0 deletions lib/Controller/AEnvironmentAwareOCSController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace OCA\Tables\Controller;

use OCA\Tables\Db\Table;

class AEnvironmentAwareOCSController extends AOCSController {
protected ?Table $table;

public function setTable(Table $table): void {
$this->table = $table;
}

public function getTable(): ?Table {
return $this->table;
}
}
91 changes: 36 additions & 55 deletions lib/Controller/ApiTablesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Errors\PermissionError;
use OCA\Tables\Middleware\Attribute\RequireTable;
use OCA\Tables\ResponseDefinitions;
use OCA\Tables\Service\TableService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IL10N;
use OCP\IRequest;
Expand All @@ -17,7 +19,7 @@
/**
* @psalm-import-type TablesTable from ResponseDefinitions
*/
class ApiTablesController extends AOCSController {
class ApiTablesController extends AEnvironmentAwareOCSController {
private TableService $service;

public function __construct(
Expand All @@ -33,12 +35,11 @@ public function __construct(
/**
* [api v2] Returns all Tables
*
* @NoAdminRequired
*
* @return DataResponse<Http::STATUS_OK, TablesTable[], array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Tables returned
*/
#[NoAdminRequired]
public function index(): DataResponse {
try {
return new DataResponse($this->service->formatTables($this->service->findAll($this->userId)));
Expand All @@ -50,32 +51,21 @@ public function index(): DataResponse {
/**
* [api v2] Get a table object
*
* @NoAdminRequired
*
* @param int $id Table ID
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Table returned
* 403: No permissions
* 404: Not found
*/
public function show(int $id): DataResponse {
try {
return new DataResponse($this->service->find($id)->jsonSerialize());
} catch (PermissionError $e) {
return $this->handlePermissionError($e);
} catch (InternalError $e) {
return $this->handleError($e);
} catch (NotFoundError $e) {
return $this->handleNotFoundError($e);
}
#[NoAdminRequired]
#[RequireTable(enhance: true)]
public function show(): DataResponse {
return new DataResponse($this->getTable()->jsonSerialize());
}

/**
* [api v2] Create a new table and return it
*
* @NoAdminRequired
*
* @param string $title Title of the table
* @param string|null $emoji Emoji for the table
* @param string $template Template to use if wanted
Expand All @@ -84,6 +74,7 @@ public function show(int $id): DataResponse {
*
* 200: Tables returned
*/
#[NoAdminRequired]
public function create(string $title, ?string $emoji, string $template = 'custom'): DataResponse {
try {
return new DataResponse($this->service->create($title, $template, $emoji)->jsonSerialize());
Expand All @@ -95,9 +86,6 @@ public function create(string $title, ?string $emoji, string $template = 'custom
/**
* [api v2] Update tables properties
*
* @NoAdminRequired
*
* @param int $id Table ID
* @param string|null $title New table title
* @param string|null $emoji New table emoji
* @param bool $archived whether the table is archived
Expand All @@ -106,41 +94,36 @@ public function create(string $title, ?string $emoji, string $template = 'custom
* 200: Tables returned
* 403: No permissions
* 404: Not found
*
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function update(int $id, ?string $title = null, ?string $emoji = null, ?bool $archived = null): DataResponse {
try {
return new DataResponse($this->service->update($id, $title, $emoji, $archived, $this->userId)->jsonSerialize());
} catch (PermissionError $e) {
return $this->handlePermissionError($e);
} catch (InternalError $e) {
return $this->handleError($e);
} catch (NotFoundError $e) {
return $this->handleNotFoundError($e);
}
#[NoAdminRequired]
#[RequireTable(enhance: true)]
public function update(?string $title = null, ?string $emoji = null, ?bool $archived = null): DataResponse {
// TODO: service class to accept Table instead of ID
return new DataResponse($this->service->update($this->getTable()->getId(), $title, $emoji, $archived, $this->userId)->jsonSerialize());
}

/**
* [api v2] Delete a table
*
* @NoAdminRequired
*
* @param int $id Table ID
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Deleted table returned
* 403: No permissions
* 404: Not found
*
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function destroy(int $id): DataResponse {
try {
return new DataResponse($this->service->delete($id)->jsonSerialize());
} catch (PermissionError $e) {
return $this->handlePermissionError($e);
} catch (InternalError $e) {
return $this->handleError($e);
} catch (NotFoundError $e) {
return $this->handleNotFoundError($e);
}
#[NoAdminRequired]
#[RequireTable(enhance: true)]
public function destroy(): DataResponse {
// TODO: service class to accept Table instead of ID
return new DataResponse($this->service->delete($this->getTable()->getId())->jsonSerialize());
}

/**
Expand All @@ -150,24 +133,22 @@ public function destroy(int $id): DataResponse {
*
* @NoAdminRequired
*
* @param int $id Table ID
* @param string $newOwnerUserId New user ID
*
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Ownership changed
* 403: No permissions
* 404: Not found
*
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function transfer(int $id, string $newOwnerUserId): DataResponse {
try {
return new DataResponse($this->service->setOwner($id, $newOwnerUserId)->jsonSerialize());
} catch (PermissionError $e) {
return $this->handlePermissionError($e);
} catch (InternalError $e) {
return $this->handleError($e);
} catch (NotFoundError $e) {
return $this->handleNotFoundError($e);
}
#[NoAdminRequired]
#[RequireTable(enhance: true)]
public function transfer(string $newOwnerUserId): DataResponse {
// TODO: service class to accept Table instead of ID
return new DataResponse($this->service->setOwner($this->getTable()->getId(), $newOwnerUserId)->jsonSerialize());
}
}
19 changes: 19 additions & 0 deletions lib/Middleware/Attribute/RequireTable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace OCA\Tables\Middleware\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class RequireTable {
public function __construct(
protected bool $enhance = false,
) {
}

public function enhance(): bool {
return $this->enhance;
}
}
102 changes: 102 additions & 0 deletions lib/Middleware/InjectionMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace OCA\Tables\Middleware;

use InvalidArgumentException;
use OCA\Tables\Controller\AEnvironmentAwareOCSController;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Errors\PermissionError;
use OCA\Tables\Middleware\Attribute\RequireTable;
use OCA\Tables\Service\TableService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCS\OCSException;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
use ReflectionAttribute;
use ReflectionMethod;

class InjectionMiddleware extends Middleware {
public function __construct(
protected IRequest $request,
protected IURLGenerator $urlGenerator,
protected IUserSession $userSession,
protected LoggerInterface $logger,
protected IL10N $l,
protected TableService $tableService,
) {
}

/**
* @throws PermissionError
* @throws \ReflectionException
* @throws NotFoundError
* @throws InternalError
*/
public function beforeController($controller, $methodName): void {
if (!$controller instanceof AEnvironmentAwareOCSController) {
return;
}

$reflectionMethod = new ReflectionMethod($controller, $methodName);
$attributes = $reflectionMethod->getAttributes(RequireTable::class);
if (!empty($attributes)) {
/** @var ReflectionAttribute $attribute */
$attribute = current($attributes);
/** @var RequireTable $requireTableAttribute */
$requireTableAttribute = $attribute->newInstance();
$this->getTable($controller, $requireTableAttribute->enhance());
}
}

public function afterException($controller, $methodName, $exception): Response {
if ($exception instanceof InvalidArgumentException) {
throw new OCSException($exception->getMessage(), Http::STATUS_BAD_REQUEST, $exception);
}

$loggerOptions = [
'errorCode' => $exception->getCode(),
'errorMessage' => $exception->getMessage(),
'exception' => $exception,
];

if ($exception instanceof NotFoundError) {
$this->logger->warning('A not found error occurred: [{errorCode}] {errorMessage}', $loggerOptions);
return new DataResponse(['message' => $this->l->t('A not found error occurred. More details can be found in the logs. Please reach out to your administration.')], Http::STATUS_NOT_FOUND);
}
if ($exception instanceof InternalError) {
$this->logger->warning('An internal error or exception occurred: [{errorCode}] {errorMessage}', $loggerOptions);
return new DataResponse(['message' => $this->l->t('An unexpected error occurred. More details can be found in the logs. Please reach out to your administration.')], Http::STATUS_INTERNAL_SERVER_ERROR);
}
if ($exception instanceof PermissionError) {
$this->logger->warning('A permission error occurred: [{errorCode}] {errorMessage}', $loggerOptions);
return new DataResponse(['message' => $this->l->t('A permission error occurred. More details can be found in the logs. Please reach out to your administration.')], Http::STATUS_FORBIDDEN);
}

$this->logger->warning('A unknown error occurred: [{errorCode}] {errorMessage}', $loggerOptions);
return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
}

/**
* @throws NotFoundError
* @throws PermissionError
* @throws InternalError
*/
protected function getTable(AEnvironmentAwareOCSController $controller, bool $enhance): void {
$tableId = $this->request->getParam('id');
if ($tableId === null) {
throw new InvalidArgumentException('Missing table ID.');
}

$userId = $this->userSession->getUser()?->getUID();

$controller->setTable($this->tableService->find($tableId, !$enhance, $userId));
}
}
Loading