diff options
author | Daniel Calviño Sánchez <danxuliu@gmail.com> | 2020-12-30 08:08:17 +0300 |
---|---|---|
committer | Vitor Mattos <vitor@php.rio> | 2022-08-31 23:20:10 +0300 |
commit | b2a1185bfb7a0d8677fbed1b50e3529a1e707298 (patch) | |
tree | 59469a766e99e9cc223272b52665ce1dab46de3a | |
parent | af962dea0ed084415431c37167ac2124e26df6c8 (diff) |
Replace generic avatar system with specific Talk endpointsadd-backend-for-conversation-avatars
The generic avatar system will not be included in Nextcloud 21, so for
the time being Talk needs to provide its own endpoints for room avatars.
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
-rw-r--r-- | appinfo/routes.php | 1 | ||||
-rw-r--r-- | appinfo/routes/routesRoomAvatarController.php | 40 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 2 | ||||
-rw-r--r-- | lib/Avatar/RoomAvatarProvider.php | 3 | ||||
-rw-r--r-- | lib/Controller/RoomAvatarController.php | 233 | ||||
-rw-r--r-- | tests/integration/features/bootstrap/AvatarTrait.php | 6 |
6 files changed, 278 insertions, 7 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php index 6fba9ee5a..642e1a4d4 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -39,6 +39,7 @@ return array_merge_recursive( include(__DIR__ . '/routes/routesPublicShareAuthController.php'), include(__DIR__ . '/routes/routesReactionController.php'), include(__DIR__ . '/routes/routesRoomController.php'), + include(__DIR__ . '/routes/routesRoomAvatarController.php'), include(__DIR__ . '/routes/routesSettingsController.php'), include(__DIR__ . '/routes/routesSignalingController.php'), include(__DIR__ . '/routes/routesTempAvatarController.php'), diff --git a/appinfo/routes/routesRoomAvatarController.php b/appinfo/routes/routesRoomAvatarController.php new file mode 100644 index 000000000..abc437dd4 --- /dev/null +++ b/appinfo/routes/routesRoomAvatarController.php @@ -0,0 +1,40 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022, Vitor Mattos <vitor@php.rio> + * + * @author Vitor Mattos <vitor@php.rio> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +$requirements = [ + 'apiVersion' => 'v1', + 'token' => '^[a-z0-9]{4,30}$', +]; + +return [ + 'ocs' => [ + /** @see \OCA\Talk\Controller\RoomAvatarController::getAvatar() */ + ['name' => 'RoomAvatar#getAvatar', 'url' => '/api/{apiVersion}/avatar/{roomToken}/{size}', 'verb' => 'GET', 'requirements' => $requirements], + /** @see \OCA\Talk\Controller\RoomAvatarController::setAvatar() */ + ['name' => 'RoomAvatar#setAvatar', 'url' => '/api/{apiVersion}/avatar/{roomToken}', 'verb' => 'POST', 'requirements' => $requirements], + /** @see \OCA\Talk\Controller\RoomAvatarController::deleteAvatar() */ + ['name' => 'RoomAvatar#deleteAvatar', 'url' => '/api/{apiVersion}/avatar/{roomToken}', 'verb' => 'DELETE', 'requirements' => $requirements], + ], +]; diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 835d4e3a8..cefe4a036 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -31,7 +31,6 @@ use OCA\Circles\Events\RemovingCircleMemberEvent; use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent; use OCA\Talk\Activity\Listener as ActivityListener; use OCA\Talk\Avatar\Listener as AvatarListener; -use OCA\Talk\Avatar\RoomAvatarProvider; use OCA\Talk\Capabilities; use OCA\Talk\Chat\Changelog\Listener as ChangelogListener; use OCA\Talk\Chat\ChatManager; @@ -151,7 +150,6 @@ class Application extends App implements IBootstrap { $context->registerProfileLinkAction(TalkAction::class); $context->registerTalkBackend(TalkBackend::class); - $context->registerAvatarProvider('room', RoomAvatarProvider::class); } public function boot(IBootContext $context): void { diff --git a/lib/Avatar/RoomAvatarProvider.php b/lib/Avatar/RoomAvatarProvider.php index 2ea0444cb..b44d62b0a 100644 --- a/lib/Avatar/RoomAvatarProvider.php +++ b/lib/Avatar/RoomAvatarProvider.php @@ -33,11 +33,10 @@ use OCA\Talk\Room; use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\IAvatar; -use OCP\IAvatarProvider; use OCP\IL10N; use Psr\Log\LoggerInterface; -class RoomAvatarProvider implements IAvatarProvider { +class RoomAvatarProvider { /** @var IAppData */ private $appData; diff --git a/lib/Controller/RoomAvatarController.php b/lib/Controller/RoomAvatarController.php new file mode 100644 index 000000000..fd701039a --- /dev/null +++ b/lib/Controller/RoomAvatarController.php @@ -0,0 +1,233 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Daniel Calviño Sánchez (danxuliu@gmail.com) + * + * @author Daniel Calviño Sánchez <danxuliu@gmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Controller; + +use OCA\Talk\Avatar\RoomAvatarProvider; +use OCP\AppFramework\OCSController; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\FileDisplayResponse; +use OCP\AppFramework\Http\Response; +use OCP\Files\NotFoundException; +use OCP\IL10N; +use OCP\Image; +use OCP\IRequest; +use Psr\Log\LoggerInterface; + +class RoomAvatarController extends OCSController { + + /** @var IL10N */ + protected $l; + + /** @var LoggerInterface */ + protected $logger; + + /** @var RoomAvatarProvider */ + protected $roomAvatarProvider; + + public function __construct($appName, + IRequest $request, + IL10N $l10n, + LoggerInterface $logger, + RoomAvatarProvider $roomAvatarProvider) { + parent::__construct($appName, $request); + + $this->l = $l10n; + $this->logger = $logger; + $this->roomAvatarProvider = $roomAvatarProvider; + } + + /** + * @PublicPage + * + * @param string $roomToken + * @param int $size + * @return DataResponse|FileDisplayResponse + */ + public function getAvatar(string $roomToken, int $size): Response { + $size = $this->sanitizeSize($size); + + try { + $avatar = $this->roomAvatarProvider->getAvatar($roomToken); + } catch (\InvalidArgumentException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + if (!$this->roomAvatarProvider->canBeAccessedByCurrentUser($avatar)) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + try { + $avatarFile = $avatar->getFile($size); + $response = new FileDisplayResponse( + $avatarFile, + Http::STATUS_OK, + [ + 'Content-Type' => $avatarFile->getMimeType(), + 'X-NC-IsCustomAvatar' => $avatar->isCustomAvatar() ? '1' : '0', + ] + ); + } catch (NotFoundException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + $cache = $this->roomAvatarProvider->getCacheTimeToLive($avatar); + if ($cache !== null) { + $response->cacheFor($cache); + } + + return $response; + } + + /** + * Returns the closest value to the predefined set of sizes + * + * @param int $size the size to sanitize + * @return int the sanitized size + */ + private function sanitizeSize(int $size): int { + $validSizes = [64, 128, 256, 512]; + + if ($size < $validSizes[0]) { + return $validSizes[0]; + } + + if ($size > $validSizes[count($validSizes) - 1]) { + return $validSizes[count($validSizes) - 1]; + } + + for ($i = 0; $i < count($validSizes) - 1; $i++) { + if ($size >= $validSizes[$i] && $size <= $validSizes[$i + 1]) { + $middlePoint = ($validSizes[$i] + $validSizes[$i + 1]) / 2; + if ($size < $middlePoint) { + return $validSizes[$i]; + } + return $validSizes[$i + 1]; + } + } + + return $size; + } + + /** + * @PublicPage + * + * @param string $roomToken + * @return DataResponse + */ + public function setAvatar(string $roomToken): DataResponse { + $files = $this->request->getUploadedFile('files'); + + if (is_null($files)) { + return new DataResponse( + ['data' => ['message' => $this->l->t('No file provided')]], + Http::STATUS_BAD_REQUEST + ); + } + + if ( + $files['error'][0] !== 0 || + !is_uploaded_file($files['tmp_name'][0]) || + \OC\Files\Filesystem::isFileBlacklisted($files['tmp_name'][0]) + ) { + return new DataResponse( + ['data' => ['message' => $this->l->t('Invalid file provided')]], + Http::STATUS_BAD_REQUEST + ); + } + + if ($files['size'][0] > 20 * 1024 * 1024) { + return new DataResponse( + ['data' => ['message' => $this->l->t('File is too big')]], + Http::STATUS_BAD_REQUEST + ); + } + + $content = file_get_contents($files['tmp_name'][0]); + unlink($files['tmp_name'][0]); + + $image = new Image(); + $image->loadFromData($content); + + try { + $avatar = $this->roomAvatarProvider->getAvatar($roomToken); + } catch (\InvalidArgumentException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + if (!$this->roomAvatarProvider->canBeModifiedByCurrentUser($avatar)) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + try { + $avatar->set($image); + return new DataResponse( + ['status' => 'success'] + ); + } catch (\OC\NotSquareException $e) { + return new DataResponse( + ['data' => ['message' => $this->l->t('Crop is not square')]], + Http::STATUS_BAD_REQUEST + ); + } catch (\Exception $e) { + $this->logger->error('Error when setting avatar', ['app' => 'core', 'exception' => $e]); + return new DataResponse( + ['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], + Http::STATUS_BAD_REQUEST + ); + } + } + + /** + * @PublicPage + * + * @param string $roomToken + * @return DataResponse + */ + public function deleteAvatar(string $roomToken): DataResponse { + try { + $avatar = $this->roomAvatarProvider->getAvatar($roomToken); + } catch (\InvalidArgumentException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + if (!$this->roomAvatarProvider->canBeModifiedByCurrentUser($avatar)) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + try { + $avatar->remove(); + return new DataResponse(); + } catch (\Exception $e) { + $this->logger->error('Error when deleting avatar', ['app' => 'core', 'exception' => $e]); + return new DataResponse( + ['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], + Http::STATUS_BAD_REQUEST + ); + } + } +} diff --git a/tests/integration/features/bootstrap/AvatarTrait.php b/tests/integration/features/bootstrap/AvatarTrait.php index d6758a150..8edc61467 100644 --- a/tests/integration/features/bootstrap/AvatarTrait.php +++ b/tests/integration/features/bootstrap/AvatarTrait.php @@ -74,7 +74,7 @@ trait AvatarTrait { */ public function userGetsAvatarForRoomWithSizeWith(string $user, string $identifier, string $size, string $statusCode) { $this->setCurrentUser($user); - $this->sendRequest('GET', '/core/avatar/room/' . FeatureContext::getTokenForIdentifier($identifier) . '/' . $size, null); + $this->sendRequest('GET', '/apps/spreed/api/v3/avatar/' . FeatureContext::getTokenForIdentifier($identifier) . '/' . $size, null); $this->assertStatusCode($this->response, $statusCode); if ($statusCode !== '200') { @@ -107,7 +107,7 @@ trait AvatarTrait { $file = \GuzzleHttp\Psr7\stream_for(fopen($source, 'r')); $this->setCurrentUser($user); - $this->sendRequest('POST', '/core/avatar/room/' . FeatureContext::getTokenForIdentifier($identifier), + $this->sendRequest('POST', '/apps/spreed/api/v3/avatar/' . FeatureContext::getTokenForIdentifier($identifier), [ 'multipart' => [ [ @@ -138,7 +138,7 @@ trait AvatarTrait { */ public function userDeletesAvatarForRoomWith(string $user, string $identifier, string $statusCode) { $this->setCurrentUser($user); - $this->sendRequest('DELETE', '/core/avatar/room/' . FeatureContext::getTokenForIdentifier($identifier), null); + $this->sendRequest('DELETE', '/apps/spreed/api/v3/avatar/' . FeatureContext::getTokenForIdentifier($identifier), null); $this->assertStatusCode($this->response, $statusCode); } |