From 3cd7fede24e383072afbceb92be41125f2ebaa4b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 1 Aug 2022 17:10:16 +0200 Subject: dav api Signed-off-by: Louis Chemineau --- lib/AppInfo/Application.php | 3 ++ lib/Sabre/Album/AlbumPhoto.php | 95 +++++++++++++++++++++++++++++++++ lib/Sabre/Album/AlbumRoot.php | 117 +++++++++++++++++++++++++++++++++++++++++ lib/Sabre/Album/AlbumsHome.php | 107 +++++++++++++++++++++++++++++++++++++ lib/Sabre/PhotosHome.php | 97 ++++++++++++++++++++++++++++++++++ lib/Sabre/RootCollection.php | 72 +++++++++++++++++++++++++ 6 files changed, 491 insertions(+) create mode 100644 lib/Sabre/Album/AlbumPhoto.php create mode 100644 lib/Sabre/Album/AlbumRoot.php create mode 100644 lib/Sabre/Album/AlbumsHome.php create mode 100644 lib/Sabre/PhotosHome.php create mode 100644 lib/Sabre/RootCollection.php (limited to 'lib') diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 2123a2e4..6a69d59e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -25,6 +25,7 @@ declare(strict_types=1); namespace OCA\Photos\AppInfo; +use OCA\DAV\Connector\Sabre\Principal; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -58,6 +59,8 @@ class Application extends App implements IBootstrap { } public function register(IRegistrationContext $context): void { + /** Register $principalBackend for the DAV collection */ + $context->registerServiceAlias('principalBackend', Principal::class); } public function boot(IBootContext $context): void { diff --git a/lib/Sabre/Album/AlbumPhoto.php b/lib/Sabre/Album/AlbumPhoto.php new file mode 100644 index 00000000..88985139 --- /dev/null +++ b/lib/Sabre/Album/AlbumPhoto.php @@ -0,0 +1,95 @@ + + * + * @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 . + * + */ + +namespace OCA\Photos\Sabre\Album; + +use OCA\Photos\Album\AlbumFile; +use OCA\Photos\Album\AlbumInfo; +use OCA\Photos\Album\AlbumMapper; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\Files\File; +use OCP\Files\NotFoundException; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\IFile; + +class AlbumPhoto implements IFile { + private AlbumMapper $albumMapper; + private AlbumInfo $album; + private AlbumFile $file; + private Folder $userFolder; + + public function __construct(AlbumMapper $albumMapper, AlbumInfo $album, AlbumFile $file, Folder $userFolder) { + $this->albumMapper = $albumMapper; + $this->album = $album; + $this->file = $file; + $this->userFolder = $userFolder; + } + + public function delete() { + $this->albumMapper->removeFile($this->album->getId(), $this->file->getFileId()); + } + + public function getName() { + return $this->file->getName(); + } + + public function setName($name) { + throw new Forbidden('Can\'t rename photos trough the album api'); + } + + public function getLastModified() { + return $this->file->getMTime(); + } + + public function put($data) { + throw new Forbidden('Can\'t write to photos trough the album api'); + } + + public function get() { + $nodes = $this->userFolder->getById($this->file->getFileId()); + $node = current($nodes); + if ($node) { + /** @var Node $node */ + if ($node instanceof File) { + return $node->fopen('r'); + } else { + throw new NotFoundException("Photo is a folder"); + } + } else { + throw new NotFoundException("Photo not found for user"); + } + } + + public function getContentType() { + return $this->file->getMimeType(); + } + + public function getETag() { + return $this->file->getEtag(); + } + + public function getSize() { + return $this->file->getSize(); + } +} diff --git a/lib/Sabre/Album/AlbumRoot.php b/lib/Sabre/Album/AlbumRoot.php new file mode 100644 index 00000000..222fa53c --- /dev/null +++ b/lib/Sabre/Album/AlbumRoot.php @@ -0,0 +1,117 @@ + + * + * @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 . + * + */ + +namespace OCA\Photos\Sabre\Album; + +use OCA\DAV\Connector\Sabre\File; +use OCA\Photos\Album\AlbumFile; +use OCA\Photos\Album\AlbumMapper; +use OCA\Photos\Album\AlbumWithFiles; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\IUser; +use Sabre\DAV\Exception\Conflict; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; +use Sabre\DAV\ICopyTarget; +use Sabre\DAV\INode; + +class AlbumRoot implements ICollection, ICopyTarget { + private AlbumMapper $albumMapper; + private AlbumWithFiles $album; + private IRootFolder $rootFolder; + private Folder $userFolder; + private IUser $user; + + public function __construct(AlbumMapper $albumMapper, AlbumWithFiles $album, IRootFolder $rootFolder, Folder $userFolder, IUser $user) { + $this->albumMapper = $albumMapper; + $this->album = $album; + $this->rootFolder = $rootFolder; + $this->userFolder = $userFolder; + $this->user = $user; + } + + public function delete() { + $this->albumMapper->delete($this->album->getAlbum()->getId()); + } + + public function getName(): string { + return basename($this->album->getAlbum()->getTitle()); + } + + public function setName($name) { + $this->albumMapper->rename($this->album->getAlbum()->getId(), $name); + } + + public function createFile($name, $data = null) { + throw new Forbidden('Not allowed to create files in this folder, copy files into this folder instead'); + } + + public function createDirectory($name) { + throw new Forbidden('Not allowed to create directories in this folder'); + } + + public function getChildren(): array { + return array_map(function (AlbumFile $file) { + return new AlbumPhoto($this->albumMapper, $this->album->getAlbum(), $file, $this->userFolder); + }, $this->album->getFiles()); + } + + public function getChild($name): AlbumPhoto { + foreach ($this->album->getFiles() as $file) { + if ($file->getName() === $name) { + return new AlbumPhoto($this->albumMapper, $this->album->getAlbum(), $file, $this->userFolder); + } + } + throw new NotFound("$name not found"); + } + + public function childExists($name): bool { + try { + $this->getChild($name); + return true; + } catch (NotFound $e) { + return false; + } + } + + public function getLastModified(): int { + return 0; + } + + public function copyInto($targetName, $sourcePath, INode $sourceNode): bool { + $uid = $this->user->getUID(); + if ($sourceNode instanceof File) { + $sourceId = $sourceNode->getId(); + if (in_array($sourceId, $this->album->getFileIds())) { + throw new Conflict("File $sourceId is already in the folder"); + } + if ($sourceNode->getFileInfo()->getOwner()->getUID() === $uid) { + $this->albumMapper->addFile($this->album->getAlbum()->getId(), $sourceId); + return true; + } + } + throw new \Exception("Can't add file to album, only files from $uid can be added"); + } +} diff --git a/lib/Sabre/Album/AlbumsHome.php b/lib/Sabre/Album/AlbumsHome.php new file mode 100644 index 00000000..397b4e60 --- /dev/null +++ b/lib/Sabre/Album/AlbumsHome.php @@ -0,0 +1,107 @@ + + * + * @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 . + * + */ +namespace OCA\Photos\Sabre\Album; + +use OCA\Photos\Album\AlbumMapper; +use OCA\Photos\Album\AlbumWithFiles; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\IUser; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; + +class AlbumsHome implements ICollection { + private AlbumMapper $albumMapper; + private array $principalInfo; + private IUser $user; + private IRootFolder $rootFolder; + private Folder $userFolder; + + public function __construct( + array $principalInfo, + AlbumMapper $albumMapper, + IUser $user, + IRootFolder $rootFolder + ) { + $this->principalInfo = $principalInfo; + $this->albumMapper = $albumMapper; + $this->user = $user; + $this->rootFolder = $rootFolder; + $this->userFolder = $rootFolder->getUserFolder($user->getUID()); + } + + public function delete() { + throw new Forbidden(); + } + + public function getName(): string { + return 'albums'; + } + + public function setName($name) { + throw new Forbidden('Permission denied to rename this folder'); + } + + public function createFile($name, $data = null) { + throw new Forbidden('Not allowed to create files in this folder'); + } + + public function createDirectory($name) { + $uid = $this->user->getUID(); + $this->albumMapper->create($uid, $name); + } + + public function getChild($name) { + foreach ($this->getChildren() as $child) { + if ($child->getName() === $name) { + return $child; + } + } + + throw new NotFound(); + } + + /** + * @return AlbumRoot[] + */ + public function getChildren(): array { + $folders = $this->albumMapper->getForUserWithFiles($this->user->getUID()); + return array_map(function (AlbumWithFiles $folder) { + return new AlbumRoot($this->albumMapper, $folder, $this->rootFolder, $this->userFolder, $this->user); + }, $folders); + } + + public function childExists($name): bool { + try { + $this->getChild($name); + return true; + } catch (NotFound $e) { + return false; + } + } + + public function getLastModified(): int { + return 0; + } +} diff --git a/lib/Sabre/PhotosHome.php b/lib/Sabre/PhotosHome.php new file mode 100644 index 00000000..e879aa88 --- /dev/null +++ b/lib/Sabre/PhotosHome.php @@ -0,0 +1,97 @@ + + * + * @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 . + * + */ +namespace OCA\Photos\Sabre; + +use OCA\Photos\Album\AlbumMapper; +use OCA\Photos\Sabre\Album\AlbumsHome; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\IUser; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; + +class PhotosHome implements ICollection { + private AlbumMapper $albumMapper; + private array $principalInfo; + private IUser $user; + private IRootFolder $rootFolder; + private Folder $userFolder; + + public function __construct( + array $principalInfo, + AlbumMapper $albumMapper, + IUser $user, + IRootFolder $rootFolder + ) { + $this->principalInfo = $principalInfo; + $this->albumMapper = $albumMapper; + $this->user = $user; + $this->rootFolder = $rootFolder; + $this->userFolder = $rootFolder->getUserFolder($user->getUID()); + } + + public function delete() { + throw new Forbidden(); + } + + public function getName(): string { + [, $name] = \Sabre\Uri\split($this->principalInfo['uri']); + return $name; + } + + public function setName($name) { + throw new Forbidden('Permission denied to rename this folder'); + } + + public function createFile($name, $data = null) { + throw new Forbidden('Not allowed to create files in this folder'); + } + + public function createDirectory($name) { + throw new Forbidden('Permission denied to create folders in this folder'); + } + + public function getChild($name) { + if ($name === 'albums') { + return new AlbumsHome($this->principalInfo, $this->albumMapper, $this->user, $this->rootFolder); + } + + throw new NotFound(); + } + + /** + * @return AlbumsHome[] + */ + public function getChildren(): array { + return [new AlbumsHome($this->principalInfo, $this->albumMapper, $this->user, $this->rootFolder)]; + } + + public function childExists($name): bool { + return $name === 'albums'; + } + + public function getLastModified(): int { + return 0; + } +} diff --git a/lib/Sabre/RootCollection.php b/lib/Sabre/RootCollection.php new file mode 100644 index 00000000..56ea56e5 --- /dev/null +++ b/lib/Sabre/RootCollection.php @@ -0,0 +1,72 @@ + + * + * @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 . + * + */ +namespace OCA\Photos\Sabre; + +use OCA\Photos\Album\AlbumMapper; +use OCP\Files\IRootFolder; +use OCP\IUserSession; +use Sabre\DAV\INode; +use Sabre\DAVACL\AbstractPrincipalCollection; +use Sabre\DAVACL\PrincipalBackend; + +class RootCollection extends AbstractPrincipalCollection { + private AlbumMapper $folderMapper; + private IUserSession $userSession; + private IRootFolder $rootFolder; + + public function __construct( + AlbumMapper $folderMapper, + IUserSession $userSession, + IRootFolder $rootFolder, + PrincipalBackend\BackendInterface $principalBackend + ) { + parent::__construct($principalBackend, 'principals/users'); + + $this->folderMapper = $folderMapper; + $this->userSession = $userSession; + $this->rootFolder = $rootFolder; + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principalInfo + * @return INode + */ + public function getChildForPrincipal(array $principalInfo): PhotosHome { + [, $name] = \Sabre\Uri\split($principalInfo['uri']); + $user = $this->userSession->getUser(); + if (is_null($user) || $name !== $user->getUID()) { + throw new \Sabre\DAV\Exception\Forbidden(); + } + return new PhotosHome($principalInfo, $this->folderMapper, $user, $this->rootFolder); + } + + public function getName(): string { + return 'photos'; + } +} -- cgit v1.2.3