mirror of https://github.com/nextcloud/bookmarks
321 lines
8.6 KiB
PHP
321 lines
8.6 KiB
PHP
<?php
|
|
/*
|
|
* Copyright (c) 2020-2024. The Nextcloud Bookmarks contributors.
|
|
*
|
|
* This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
|
|
*/
|
|
|
|
namespace OCA\Bookmarks\Service;
|
|
|
|
use OCA\Bookmarks\Db\BookmarkMapper;
|
|
use OCA\Bookmarks\Db\Folder;
|
|
use OCA\Bookmarks\Db\FolderMapper;
|
|
use OCA\Bookmarks\Db\PublicFolderMapper;
|
|
use OCA\Bookmarks\Db\Share;
|
|
use OCA\Bookmarks\Db\SharedFolderMapper;
|
|
use OCA\Bookmarks\Db\ShareMapper;
|
|
use OCA\Bookmarks\Db\TreeMapper;
|
|
use OCA\Bookmarks\Exception\UnauthenticatedError;
|
|
use OCP\AppFramework\Db\DoesNotExistException;
|
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
|
use OCP\IRequest;
|
|
use OCP\IUserSession;
|
|
|
|
class Authorizer {
|
|
public const PERM_NONE = 0;
|
|
public const PERM_READ = 1;
|
|
public const PERM_EDIT = 2; // Allows editing the direct item
|
|
public const PERM_RESHARE = 4;
|
|
public const PERM_WRITE = 8; // Allows adding and editing the item's descendants
|
|
public const PERM_ALL = 15;
|
|
|
|
private $userId;
|
|
private $token = null;
|
|
|
|
private $cors = false;
|
|
|
|
|
|
public function __construct(
|
|
private FolderMapper $folderMapper,
|
|
private BookmarkMapper $bookmarkMapper,
|
|
private PublicFolderMapper $publicMapper,
|
|
private ShareMapper $shareMapper,
|
|
private TreeMapper $treeMapper,
|
|
private IUserSession $userSession,
|
|
private SharedFolderMapper $sharedFolderMapper
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @param bool $cors
|
|
*/
|
|
public function setCORS($cors) {
|
|
$this->cors = $cors;
|
|
}
|
|
|
|
/**
|
|
* @param IRequest $request
|
|
*/
|
|
public function setCredentials(IRequest $request): void {
|
|
$queryParam = $request->getParam('token');
|
|
if ($queryParam !== null) {
|
|
$this->setToken($queryParam);
|
|
}
|
|
|
|
$auth = $request->getHeader('Authorization');
|
|
|
|
if ($auth !== null && $auth !== '') {
|
|
[$type, $credentials] = explode(' ', $auth);
|
|
if (strtolower($type) === 'bearer') {
|
|
$this->setToken($credentials);
|
|
}
|
|
}
|
|
|
|
if (!$this->cors && $this->userSession->isLoggedIn()) {
|
|
$this->setUserId($this->userSession->getUser()->getUID());
|
|
} elseif (isset($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'])) {
|
|
if ($this->userSession->getUser() !== null) {
|
|
$this->setUserId($this->userSession->getUser()->getUID());
|
|
return;
|
|
}
|
|
if ($this->userSession->login($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW']) === false) {
|
|
return;
|
|
}
|
|
$this->setUserId($this->userSession->getUser()->getUID());
|
|
} elseif ($auth !== null && $auth !== '') {
|
|
[$type, $credentials] = explode(' ', $auth);
|
|
if (strtolower($type) === 'basic') {
|
|
if ($this->userSession->getUser() !== null) {
|
|
$this->setUserId($this->userSession->getUser()->getUID());
|
|
return;
|
|
}
|
|
[$username, $password] = explode(':', base64_decode($credentials));
|
|
if ($this->userSession->login($username, $password) === false) {
|
|
return;
|
|
}
|
|
$this->setUserId($this->userSession->getUser()->getUID());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param null|string $token
|
|
*/
|
|
public function setToken(?string $token): void {
|
|
$this->token = $token;
|
|
}
|
|
|
|
/**
|
|
* @return null|string
|
|
*/
|
|
public function getToken(): ?string {
|
|
return $this->token;
|
|
}
|
|
|
|
/**
|
|
* @param string|null $userId
|
|
*/
|
|
public function setUserId(?string $userId): void {
|
|
$this->userId = $userId;
|
|
}
|
|
|
|
/**
|
|
* @return null|string
|
|
*/
|
|
public function getUserId(): ?string {
|
|
return $this->userId;
|
|
}
|
|
|
|
/**
|
|
* @param int $folderId
|
|
* @param IRequest $request
|
|
* @return int
|
|
* @throws UnauthenticatedError
|
|
*/
|
|
public function getPermissionsForFolder(int $folderId, IRequest $request): int {
|
|
$this->setCredentials($request);
|
|
$perms = self::PERM_NONE;
|
|
if (isset($this->userId)) {
|
|
$perms |= $this->getUserPermissionsForFolder($this->userId, $folderId);
|
|
} elseif (isset($this->token)) {
|
|
$perms |= $this->getTokenPermissionsForFolder($this->token, $folderId);
|
|
} else {
|
|
throw new UnauthenticatedError();
|
|
}
|
|
return $perms;
|
|
}
|
|
|
|
/**
|
|
* @param int $bookmarkId
|
|
* @param IRequest $request
|
|
* @return int
|
|
* @throws UnauthenticatedError
|
|
*/
|
|
public function getPermissionsForBookmark(int $bookmarkId, IRequest $request): int {
|
|
$this->setCredentials($request);
|
|
$perms = self::PERM_NONE;
|
|
if (isset($this->userId)) {
|
|
$perms |= $this->getUserPermissionsForBookmark($this->userId, $bookmarkId);
|
|
} elseif (isset($this->token)) {
|
|
$perms |= $this->getTokenPermissionsForBookmark($this->token, $bookmarkId);
|
|
} else {
|
|
throw new UnauthenticatedError();
|
|
}
|
|
return $perms;
|
|
}
|
|
|
|
/**
|
|
* @param $canWrite
|
|
* @param $canShare
|
|
*
|
|
* @return int
|
|
*
|
|
* @psalm-return 0|positive-int
|
|
*/
|
|
protected function getMaskFromFlags($canWrite, $canShare): int {
|
|
$perms = self::PERM_READ;
|
|
if ($canWrite) {
|
|
$perms |= self::PERM_EDIT;
|
|
$perms |= self::PERM_WRITE;
|
|
}
|
|
if ($canShare) {
|
|
$perms |= self::PERM_RESHARE;
|
|
}
|
|
return $perms;
|
|
}
|
|
|
|
/**
|
|
* Check permissions
|
|
*
|
|
*
|
|
* @param int $perm
|
|
* @param int $perms
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function hasPermission(int $perm, int $perms): bool {
|
|
return (boolean)($perms & $perm);
|
|
}
|
|
|
|
/**
|
|
* @param string $userId
|
|
* @param int $bookmarkId
|
|
* @return int
|
|
* @psalm-return 0|positive-int
|
|
*/
|
|
public function getUserPermissionsForBookmark(string $userId, int $bookmarkId): int {
|
|
return $this->findPermissionsByUserAndItem($userId, TreeMapper::TYPE_BOOKMARK, $bookmarkId);
|
|
}
|
|
|
|
/**
|
|
* @param string $token
|
|
* @param int $bookmarkId
|
|
*
|
|
* @return int
|
|
* @psalm-return 0|positive-int
|
|
*/
|
|
public function getTokenPermissionsForBookmark(string $token, int $bookmarkId): int {
|
|
try {
|
|
$publicFolder = $this->publicMapper->find($token);
|
|
} catch (DoesNotExistException $e) {
|
|
return self::PERM_NONE;
|
|
} catch (MultipleObjectsReturnedException $e) {
|
|
return self::PERM_NONE;
|
|
}
|
|
if ($this->treeMapper->hasDescendant($publicFolder->getFolderId(), TreeMapper::TYPE_BOOKMARK, $bookmarkId)) {
|
|
return self::PERM_READ;
|
|
}
|
|
return self::PERM_NONE;
|
|
}
|
|
|
|
/**
|
|
* @param string $userId
|
|
* @param int $folderId
|
|
* @return int
|
|
* @psalm-return 0|positive-int
|
|
*/
|
|
public function getUserPermissionsForFolder(string $userId, int $folderId): int {
|
|
if ($folderId === -1) {
|
|
return self::PERM_ALL;
|
|
}
|
|
|
|
return $this->findPermissionsByUserAndItem($userId, TreeMapper::TYPE_FOLDER, $folderId);
|
|
}
|
|
|
|
/**
|
|
* @param string $userId
|
|
* @param string $type
|
|
* @param int $itemId
|
|
* @return 0|positive-int
|
|
*/
|
|
private function findPermissionsByUserAndItem(string $userId, string $type, int $itemId): int {
|
|
try {
|
|
if ($type === TreeMapper::TYPE_FOLDER) {
|
|
$item = $this->folderMapper->find($itemId);
|
|
} elseif ($type === TreeMapper::TYPE_BOOKMARK) {
|
|
$item = $this->bookmarkMapper->find($itemId);
|
|
} else {
|
|
$item = $this->sharedFolderMapper->find($itemId);
|
|
}
|
|
} catch (DoesNotExistException) {
|
|
return self::PERM_ALL;
|
|
} catch (MultipleObjectsReturnedException) {
|
|
return self::PERM_NONE;
|
|
}
|
|
if ($item->getUserId() === $userId) {
|
|
return self::PERM_ALL;
|
|
}
|
|
|
|
$shares = $this->shareMapper->findByOwner($item->getUserId());
|
|
foreach ($shares as $share) {
|
|
if ($share->getFolderId() === $itemId && $type === TreeMapper::TYPE_FOLDER) {
|
|
// If the sought folder is the root folder of the share, we give EDIT permissions + optionally RESHARE
|
|
// because the user can edit the shared folder
|
|
$perms = $this->getMaskFromFlags($share->getCanWrite(), $share->getCanShare()) | self::PERM_EDIT;
|
|
} elseif ($this->treeMapper->hasDescendant($share->getFolderId(), $type, $itemId)) {
|
|
$perms = $this->getMaskFromFlags($share->getCanWrite(), $share->getCanShare());
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
$sharedFolders = $this->sharedFolderMapper->findByShare($share->getId());
|
|
foreach ($sharedFolders as $sharedFolder) {
|
|
if ($sharedFolder->getUserId() === $userId) {
|
|
return $perms;
|
|
}
|
|
$secondLevelPerms = $this->findPermissionsByUserAndItem($userId, TreeMapper::TYPE_SHARE, $sharedFolder->getId());
|
|
if ($secondLevelPerms !== self::PERM_NONE) {
|
|
return $perms & $secondLevelPerms;
|
|
}
|
|
}
|
|
}
|
|
|
|
return self::PERM_NONE;
|
|
}
|
|
|
|
/**
|
|
* @param string $token
|
|
* @param int $folderId
|
|
*
|
|
* @return int
|
|
* @psalm-return 0|positive-int
|
|
*
|
|
*/
|
|
public function getTokenPermissionsForFolder(string $token, int $folderId): int {
|
|
try {
|
|
$publicFolder = $this->publicMapper->find($token);
|
|
} catch (DoesNotExistException $e) {
|
|
return self::PERM_NONE;
|
|
} catch (MultipleObjectsReturnedException $e) {
|
|
return self::PERM_NONE;
|
|
}
|
|
if ($folderId === -1) {
|
|
return self::PERM_READ;
|
|
}
|
|
if ($publicFolder->getFolderId() === $folderId || $this->treeMapper->hasDescendant($publicFolder->getFolderId(), TreeMapper::TYPE_FOLDER, $folderId)) {
|
|
return self::PERM_READ;
|
|
}
|
|
return self::PERM_NONE;
|
|
}
|
|
}
|