owncloud/core

View on GitHub
apps/dav/lib/Files/FileLocksBackend.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 *
 * @copyright Copyright (c) 2018, ownCloud GmbH
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * 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, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

namespace OCA\DAV\Files;

use OCA\DAV\Connector\Sabre\Node;
use OCA\DAV\Files\PublicFiles\PublicSharedRootNode;
use OCA\DAV\Files\PublicFiles\SharedFile;
use OCA\DAV\Files\PublicFiles\SharedFolder;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IPersistentLockingStorage;
use OCP\Lock\Persistent\ILock;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Locks;
use Sabre\DAV\Locks\Backend\BackendInterface;
use Sabre\DAV\Tree;
use OCP\AppFramework\Utility\ITimeFactory;
use OCA\DAV\Connector\Sabre\ObjectTree;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;

class FileLocksBackend implements BackendInterface {
    /** @var Tree */
    private $tree;
    /** @var bool */
    private $useV1;
    /** @var ITimeFactory */
    private $timeFactory;
    /** @var bool */
    private $isPublicEndpoint;

    public function __construct($tree, $useV1, $timeFactory, $isPublicEndpoint = false) {
        $this->tree = $tree;
        $this->useV1 = $useV1;
        $this->timeFactory = $timeFactory;
        $this->isPublicEndpoint = $isPublicEndpoint;
    }

    /**
     * Returns a list of Sabre\DAV\Locks\LockInfo objects
     *
     * This method should return all the locks for a particular uri, including
     * locks that might be set on a parent uri.
     *
     * If returnChildLocks is set to true, this method should also look for
     * any locks in the subtree of the uri for locks.
     *
     * @param string $uri
     * @param bool $returnChildLocks
     * @return array
     */
    public function getLocks($uri, $returnChildLocks) {
        try {
            $node = $this->tree->getNodeForPath($uri);

            if ($node instanceof SharedFile || $node instanceof SharedFolder) {
                $node = $node->getNode();
            } elseif ($node instanceof PublicSharedRootNode) {
                try {
                    $node = $node->getShare()->getNode();
                } catch (NotFoundException $e) {
                    return [];
                }
            } elseif (!$node instanceof Node) {
                return [];
            }

            $storage = $node->getFileInfo()->getStorage();
            if (!$storage->instanceOfStorage(IPersistentLockingStorage::class)) {
                return [];
            }

            /** @var IPersistentLockingStorage $storage */
            '@phan-var IPersistentLockingStorage $storage';
            $locks = $storage->getLocks($node->getFileInfo()->getInternalPath(), $returnChildLocks);
        } catch (NotFound $e) {
            if ($uri === '') {
                // no more parents
                return [];
            }

            // get parent storage and check for locks on the target path
            list($parentPath, $childPath) = \Sabre\Uri\split($uri);

            try {
                $node = $this->tree->getNodeForPath($parentPath);
            } catch (NotFound $e) {
                return [];
            }

            if ($node instanceof SharedFile || $node instanceof SharedFolder) {
                $node = $node->getNode();
            } elseif ($node instanceof PublicSharedRootNode) {
                try {
                    $node = $node->getShare()->getNode();
                } catch (NotFoundException $e) {
                    return [];
                }
            } elseif (!$node instanceof Node) {
                return [];
            }

            // use storage of parent
            $storage = $node->getFileInfo()->getStorage();
            if (!$storage->instanceOfStorage(IPersistentLockingStorage::class)) {
                return [];
            }

            /** @var IPersistentLockingStorage $storage */
            '@phan-var IPersistentLockingStorage $storage';
            $locks = $storage->getLocks($node->getFileInfo()->getInternalPath() . '/' . $childPath, $returnChildLocks);
        }

        $davLocks = [];
        foreach ($locks as $lock) {
            $lockInfo = new Locks\LockInfo();
            $fileName = $lock->getAbsoluteDavPath();
            $uid = $lock->getDavUserId();

            $pathInView = "/$uid/files/$fileName";

            // v1 object tree, also used by public link
            if ($this->tree instanceof ObjectTree) {
                // get path relative to the view root, which can
                // either be the user's home or a public link share's root
                $subPath = $this->tree->getView()->getRelativePath($pathInView);
                if ($subPath === null) {
                    // path is above the current view, just tell that the lock is on the root then
                    $lockInfo->uri = '';
                } else {
                    $subPath = \ltrim($subPath, '/');
                    $lockInfo->uri = $subPath;
                }
            } else {
                if ($this->useV1) {
                    $lockInfo->uri = $fileName;
                } else {
                    $lockInfo->uri = "files/$uid/$fileName";
                }
            }

            if (!$this->isPublicEndpoint) {
                $lockInfo->token = $lock->getToken();
                $lockInfo->owner = $lock->getOwner();
            }
            $lockInfo->created = $lock->getCreatedAt();
            $lockInfo->depth = $lock->getDepth();
            if ($lock->getScope() === ILock::LOCK_SCOPE_EXCLUSIVE) {
                $lockInfo->scope = Locks\LockInfo::EXCLUSIVE;
            } else {
                $lockInfo->scope = Locks\LockInfo::SHARED;
            }
            $lockInfo->timeout = $lock->getTimeout() - ($this->timeFactory->getTime() - $lock->getCreatedAt());

            $davLocks[] = $lockInfo;
        }
        return $davLocks;
    }

    /**
     * Locks a uri
     *
     * @param string $uri
     * @param Locks\LockInfo $lockInfo
     * @return bool
     */
    public function lock($uri, Locks\LockInfo $lockInfo) {
        if ($this->isPublicEndpoint) {
            throw new Forbidden('Forbidden to lock from public endpoint');
        }
        try {
            $node = $this->tree->getNodeForPath($uri);
        } catch (NotFound $e) {
            return false;
        }
        if (!$node instanceof Node) {
            return false;
        }

        $storage = $node->getFileInfo()->getStorage();
        if (!$storage->instanceOfStorage(IPersistentLockingStorage::class)) {
            return false;
        }

        /** @var IPersistentLockingStorage $storage */
        '@phan-var IPersistentLockingStorage $storage';
        $lock = $storage->lockNodePersistent($node->getFileInfo()->getInternalPath(), [
            'token' => $lockInfo->token,
            'scope' => $lockInfo->scope === Locks\LockInfo::EXCLUSIVE ? ILock::LOCK_SCOPE_EXCLUSIVE : ILock::LOCK_SCOPE_SHARED,
            'depth' => $lockInfo->depth,
            'owner' => $lockInfo->owner,
            'timeout' => $lockInfo->timeout
        ]);

        // in case the timeout has not been accepted, adjust in lock info
        $lockInfo->timeout = $lock->getTimeout();
        $lockInfo->owner = $lock->getOwner();

        return !empty($lock);
    }

    /**
     * Removes a lock from a uri
     *
     * @param string $uri
     * @param Locks\LockInfo $lockInfo
     * @return bool
     */
    public function unlock($uri, Locks\LockInfo $lockInfo) {
        if ($this->isPublicEndpoint) {
            throw new Forbidden('Forbidden to unlock from public endpoint');
        }
        try {
            $node = $this->tree->getNodeForPath($uri);
        } catch (NotFound $e) {
            return false;
        }
        if (!$node instanceof Node) {
            return false;
        }

        $storage = $node->getFileInfo()->getStorage();
        if (!$storage->instanceOfStorage(IPersistentLockingStorage::class)) {
            return false;
        }

        /** @var IPersistentLockingStorage $storage */
        '@phan-var IPersistentLockingStorage $storage';
        return $storage->unlockNodePersistent($node->getFileInfo()->getInternalPath(), [
            'token' => $lockInfo->token
        ]);
    }
}