123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- <?php
- /**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <pvince81@owncloud.com>
- *
- * @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\Files_Sharing\External;
- use GuzzleHttp\Exception\ClientException;
- use GuzzleHttp\Exception\ConnectException;
- use OC\Files\Storage\DAV;
- use OC\ForbiddenException;
- use OCA\Files_Sharing\ISharedStorage;
- use OCP\AppFramework\Http;
- use OCP\Federation\ICloudId;
- use OCP\Files\NotFoundException;
- use OCP\Files\StorageInvalidException;
- use OCP\Files\StorageNotAvailableException;
- class Storage extends DAV implements ISharedStorage {
- /** @var ICloudId */
- private $cloudId;
- /** @var string */
- private $mountPoint;
- /** @var string */
- private $token;
- /** @var \OCP\ICacheFactory */
- private $memcacheFactory;
- /** @var \OCP\Http\Client\IClientService */
- private $httpClient;
- /** @var bool */
- private $updateChecked = false;
- /**
- * @var \OCA\Files_Sharing\External\Manager
- */
- private $manager;
- public function __construct($options) {
- $this->memcacheFactory = \OC::$server->getMemCacheFactory();
- $this->httpClient = $options['HttpClientService'];
- $this->manager = $options['manager'];
- $this->cloudId = $options['cloudId'];
- $discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
- list($protocol, $remote) = explode('://', $this->cloudId->getRemote());
- if (strpos($remote, '/')) {
- list($host, $root) = explode('/', $remote, 2);
- } else {
- $host = $remote;
- $root = '';
- }
- $secure = $protocol === 'https';
- $federatedSharingEndpoints = $discoveryService->discover($this->cloudId->getRemote(), 'FEDERATED_SHARING');
- $webDavEndpoint = isset($federatedSharingEndpoints['webdav']) ? $federatedSharingEndpoints['webdav'] : '/public.php/webdav';
- $root = rtrim($root, '/') . $webDavEndpoint;
- $this->mountPoint = $options['mountpoint'];
- $this->token = $options['token'];
- parent::__construct(array(
- 'secure' => $secure,
- 'host' => $host,
- 'root' => $root,
- 'user' => $options['token'],
- 'password' => (string)$options['password']
- ));
- }
- public function getWatcher($path = '', $storage = null) {
- if (!$storage) {
- $storage = $this;
- }
- if (!isset($this->watcher)) {
- $this->watcher = new Watcher($storage);
- $this->watcher->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE);
- }
- return $this->watcher;
- }
- public function getRemoteUser() {
- return $this->cloudId->getUser();
- }
- public function getRemote() {
- return $this->cloudId->getRemote();
- }
- public function getMountPoint() {
- return $this->mountPoint;
- }
- public function getToken() {
- return $this->token;
- }
- public function getPassword() {
- return $this->password;
- }
- /**
- * @brief get id of the mount point
- * @return string
- */
- public function getId() {
- return 'shared::' . md5($this->token . '@' . $this->getRemote());
- }
- public function getCache($path = '', $storage = null) {
- if (is_null($this->cache)) {
- $this->cache = new Cache($this, $this->cloudId);
- }
- return $this->cache;
- }
- /**
- * @param string $path
- * @param \OC\Files\Storage\Storage $storage
- * @return \OCA\Files_Sharing\External\Scanner
- */
- public function getScanner($path = '', $storage = null) {
- if (!$storage) {
- $storage = $this;
- }
- if (!isset($this->scanner)) {
- $this->scanner = new Scanner($storage);
- }
- return $this->scanner;
- }
- /**
- * check if a file or folder has been updated since $time
- *
- * @param string $path
- * @param int $time
- * @throws \OCP\Files\StorageNotAvailableException
- * @throws \OCP\Files\StorageInvalidException
- * @return bool
- */
- public function hasUpdated($path, $time) {
- // since for owncloud webdav servers we can rely on etag propagation we only need to check the root of the storage
- // because of that we only do one check for the entire storage per request
- if ($this->updateChecked) {
- return false;
- }
- $this->updateChecked = true;
- try {
- return parent::hasUpdated('', $time);
- } catch (StorageInvalidException $e) {
- // check if it needs to be removed
- $this->checkStorageAvailability();
- throw $e;
- } catch (StorageNotAvailableException $e) {
- // check if it needs to be removed or just temp unavailable
- $this->checkStorageAvailability();
- throw $e;
- }
- }
- public function test() {
- try {
- return parent::test();
- } catch (StorageInvalidException $e) {
- // check if it needs to be removed
- $this->checkStorageAvailability();
- throw $e;
- } catch (StorageNotAvailableException $e) {
- // check if it needs to be removed or just temp unavailable
- $this->checkStorageAvailability();
- throw $e;
- }
- }
- /**
- * Check whether this storage is permanently or temporarily
- * unavailable
- *
- * @throws \OCP\Files\StorageNotAvailableException
- * @throws \OCP\Files\StorageInvalidException
- */
- public function checkStorageAvailability() {
- // see if we can find out why the share is unavailable
- try {
- $this->getShareInfo();
- } catch (NotFoundException $e) {
- // a 404 can either mean that the share no longer exists or there is no Nextcloud on the remote
- if ($this->testRemote()) {
- // valid Nextcloud instance means that the public share no longer exists
- // since this is permanent (re-sharing the file will create a new token)
- // we remove the invalid storage
- $this->manager->removeShare($this->mountPoint);
- $this->manager->getMountManager()->removeMount($this->mountPoint);
- throw new StorageInvalidException();
- } else {
- // Nextcloud instance is gone, likely to be a temporary server configuration error
- throw new StorageNotAvailableException();
- }
- } catch (ForbiddenException $e) {
- // auth error, remove share for now (provide a dialog in the future)
- $this->manager->removeShare($this->mountPoint);
- $this->manager->getMountManager()->removeMount($this->mountPoint);
- throw new StorageInvalidException();
- } catch (\GuzzleHttp\Exception\ConnectException $e) {
- throw new StorageNotAvailableException();
- } catch (\GuzzleHttp\Exception\RequestException $e) {
- throw new StorageNotAvailableException();
- } catch (\Exception $e) {
- throw $e;
- }
- }
- public function file_exists($path) {
- if ($path === '') {
- return true;
- } else {
- return parent::file_exists($path);
- }
- }
- /**
- * check if the configured remote is a valid federated share provider
- *
- * @return bool
- */
- protected function testRemote() {
- try {
- return $this->testRemoteUrl($this->getRemote() . '/ocs-provider/index.php')
- || $this->testRemoteUrl($this->getRemote() . '/ocs-provider/')
- || $this->testRemoteUrl($this->getRemote() . '/status.php');
- } catch (\Exception $e) {
- return false;
- }
- }
- /**
- * @param string $url
- * @return bool
- */
- private function testRemoteUrl($url) {
- $cache = $this->memcacheFactory->createDistributed('files_sharing_remote_url');
- if($cache->hasKey($url)) {
- return (bool)$cache->get($url);
- }
- $client = $this->httpClient->newClient();
- try {
- $result = $client->get($url, [
- 'timeout' => 10,
- 'connect_timeout' => 10,
- ])->getBody();
- $data = json_decode($result);
- $returnValue = (is_object($data) && !empty($data->version));
- } catch (ConnectException $e) {
- $returnValue = false;
- } catch (ClientException $e) {
- $returnValue = false;
- }
- $cache->set($url, $returnValue, 60*60*24);
- return $returnValue;
- }
- /**
- * Whether the remote is an ownCloud/Nextcloud, used since some sharing features are not
- * standardized. Let's use this to detect whether to use it.
- *
- * @return bool
- */
- public function remoteIsOwnCloud() {
- if(defined('PHPUNIT_RUN') || !$this->testRemoteUrl($this->getRemote() . '/status.php')) {
- return false;
- }
- return true;
- }
- /**
- * @return mixed
- * @throws ForbiddenException
- * @throws NotFoundException
- * @throws \Exception
- */
- public function getShareInfo() {
- $remote = $this->getRemote();
- $token = $this->getToken();
- $password = $this->getPassword();
- // If remote is not an ownCloud do not try to get any share info
- if(!$this->remoteIsOwnCloud()) {
- return ['status' => 'unsupported'];
- }
- $url = rtrim($remote, '/') . '/index.php/apps/files_sharing/shareinfo?t=' . $token;
- // TODO: DI
- $client = \OC::$server->getHTTPClientService()->newClient();
- try {
- $response = $client->post($url, [
- 'body' => ['password' => $password],
- 'timeout' => 10,
- 'connect_timeout' => 10,
- ]);
- } catch (\GuzzleHttp\Exception\RequestException $e) {
- if ($e->getCode() === Http::STATUS_UNAUTHORIZED || $e->getCode() === Http::STATUS_FORBIDDEN) {
- throw new ForbiddenException();
- }
- if ($e->getCode() === Http::STATUS_NOT_FOUND) {
- throw new NotFoundException();
- }
- // throw this to be on the safe side: the share will still be visible
- // in the UI in case the failure is intermittent, and the user will
- // be able to decide whether to remove it if it's really gone
- throw new StorageNotAvailableException();
- }
- return json_decode($response->getBody(), true);
- }
- public function getOwner($path) {
- return $this->cloudId->getDisplayId();
- }
- public function isSharable($path) {
- if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
- return false;
- }
- return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
- }
- public function getPermissions($path) {
- $response = $this->propfind($path);
- if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
- $permissions = $response['{http://open-collaboration-services.org/ns}share-permissions'];
- } else {
- // use default permission if remote server doesn't provide the share permissions
- if ($this->is_dir($path)) {
- $permissions = \OCP\Constants::PERMISSION_ALL;
- } else {
- $permissions = \OCP\Constants::PERMISSION_ALL & ~\OCP\Constants::PERMISSION_CREATE;
- }
- }
- return $permissions;
- }
- }
|