Skip to content

Commit

Permalink
Added WebdavClientService in core + first unit tests for DAV class
Browse files Browse the repository at this point in the history
  • Loading branch information
Vincent Petry committed Oct 19, 2017
1 parent 28fec80 commit 5b5689c
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 27 deletions.
37 changes: 10 additions & 27 deletions lib/private/Files/Storage/DAV.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
use OCP\Files\StorageInvalidException;
use OCP\Files\StorageNotAvailableException;
use OCP\Util;
use Sabre\DAV\Client;
use Sabre\DAV\Xml\Property\ResourceType;
use Sabre\HTTP\ClientException;
use Sabre\HTTP\ClientHttpException;
Expand Down Expand Up @@ -84,13 +83,17 @@ class DAV extends Common {
/** @var \OCP\Http\Client\IClientService */
private $httpClientService;

/** @var \OCP\Http\Client\IWebdavClientService */
private $webdavClientService;

/**
* @param array $params
* @throws \Exception
*/
public function __construct($params) {
$this->statCache = new ArrayCache();
$this->httpClientService = \OC::$server->getHTTPClientService();
$this->webdavClientService = \OC::$server->getWebdavClientService();
if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
$host = $params['host'];
//remove leading http[s], will be generated in createBaseUri()
Expand All @@ -111,17 +114,6 @@ public function __construct($params) {
} else {
$this->secure = false;
}
if ($this->secure === true) {
// inject mock for testing
$certManager = \OC::$server->getCertificateManager();
if (is_null($certManager)) { //no user
$certManager = \OC::$server->getCertificateManager(null);
}
$certPath = $certManager->getAbsoluteBundlePath();
if (file_exists($certPath)) {
$this->certPath = $certPath;
}
}
$this->root = isset($params['root']) ? $params['root'] : '/';
if (!$this->root || $this->root[0] != '/') {
$this->root = '/' . $this->root;
Expand All @@ -143,22 +135,13 @@ private function init() {
$settings = [
'baseUri' => $this->createBaseUri(),
'userName' => $this->user,
'password' => $this->password,
'password' => $this->password
];
if (isset($this->authType)) {
$settings['authType'] = $this->authType;
}

$proxy = \OC::$server->getConfig()->getSystemValue('proxy', '');
if($proxy !== '') {
$settings['proxy'] = $proxy;
}

$this->client = new Client($settings);
$this->client->setThrowExceptions(true);
if ($this->secure === true && $this->certPath) {
$this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
}
$this->client = $this->webdavClientService->newClient($settings);
}

/**
Expand Down Expand Up @@ -837,10 +820,10 @@ private function convertException(Exception $e, $path = '') {
// ignore exception for MethodNotAllowed, false will be returned
return;
}
$this->throwByStatusCode($e->getHttpStatus(), $path);
$this->throwByStatusCode($e->getHttpStatus(), $e, $path);
} else if ($e instanceof \GuzzleHttp\Exception\ClientException || $e instanceof \GuzzleHttp\Exception\ServerException) {
if ($e->getResponse() instanceof ResponseInterface) {
$this->throwByStatusCode($e->getResponse()->getStatusCode());
$this->throwByStatusCode($e->getResponse()->getStatusCode(), $e);
}
// connection timeout or refused, server could be temporarily down
throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
Expand All @@ -863,7 +846,7 @@ private function convertException(Exception $e, $path = '') {
* @param string $path optional path for some exceptions
* @throws \Exception Sabre or ownCloud exceptions
*/
private function throwByStatusCode($statusCode, $path = '') {
private function throwByStatusCode($statusCode, $e, $path = '') {
switch ($statusCode) {
case Http::STATUS_LOCKED:
throw new \OCP\Lock\LockedException($path);
Expand All @@ -873,7 +856,7 @@ private function throwByStatusCode($statusCode, $path = '') {
case Http::STATUS_INSUFFICIENT_STORAGE:
throw new InsufficientStorage();
case Http::STATUS_FORBIDDEN:
throw new Forbidden();
throw new Forbidden('Forbidden');
}
throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
}
Expand Down
80 changes: 80 additions & 0 deletions lib/private/Http/Client/WebdavClientService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
/**
* @author Vincent Petry <pvince81@owncloud.com>
*
* @copyright Copyright (c) 2017, 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 OC\Http\Client;

use OCP\Http\Client\IWebdavClientService;
use OCP\IConfig;
use OCP\ICertificateManager;
use Sabre\DAV\Client;

/**
* Class WebdavClientService
*
* @package OC\Http
*/
class WebdavClientService implements IWebdavClientService {
/** @var IConfig */
private $config;
/** @var ICertificateManager */
private $certificateManager;

/**
* @param IConfig $config
* @param ICertificateManager $certificateManager
*/
public function __construct(IConfig $config,
ICertificateManager $certificateManager) {
$this->config = $config;
$this->certificateManager = $certificateManager;
}

/**
* Instantiate new Sabre client
*
* @param array $settings
* @return Client
*/
public function newClient($settings) {
if (!isset($settings['proxy'])) {
$proxy = \OC::$server->getConfig()->getSystemValue('proxy', '');
if($proxy !== '') {
$settings['proxy'] = $proxy;
}
}

$certPath = null;
if (strpos($settings['baseUri'], 'https') === 0) {
$certPath = $this->certificateManager->getAbsoluteBundlePath();
if (!file_exists($certPath)) {
$certPath = null;
}
}

$client = new Client($settings);
$client->setThrowExceptions(true);

if ($certPath !== null) {
$client->addCurlSetting(CURLOPT_CAINFO, $certPath);
}
return $client;
}
}
18 changes: 18 additions & 0 deletions lib/private/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
use OC\Files\External\Service\UserGlobalStoragesService;
use OC\Files\External\Service\GlobalStoragesService;
use OC\Files\External\Service\DBConfigService;
use OC\Http\Client\WebdavClientService;

/**
* Class Server
Expand Down Expand Up @@ -491,6 +492,14 @@ public function __construct($webRoot, \OC\Config $config) {
new \OC\Security\CertificateManager($uid, new View(), $c->getConfig())
);
});
$this->registerService('WebdavClientService', function (Server $c) {
$user = \OC_User::getUser();
$uid = $user ? $user : null;
return new WebdavClientService(
$c->getConfig(),
new \OC\Security\CertificateManager($uid, new View(), $c->getConfig())
);
});
$this->registerService('EventLogger', function (Server $c) {
$eventLogger = new EventLogger();
if ($c->getSystemConfig()->getValue('debug', false)) {
Expand Down Expand Up @@ -1264,6 +1273,15 @@ public function getHTTPClientService() {
return $this->query('HttpClientService');
}

/**
* Returns an instance of the Webdav client service
*
* @return \OCP\Http\Client\IWebdavClientService
*/
public function getWebdavClientService() {
return $this->query('WebdavClientService');
}

/**
* Create a new event source
*
Expand Down
38 changes: 38 additions & 0 deletions lib/public/Http/Client/IWebdavClientService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/**
* @author Vincent Petry <pvince81@owncloud.com>
*
* @copyright Copyright (c) 2017, 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 OCP\Http\Client;

use Sabre\HTTP\Client;

/**
* Interface IWebdavClientService
*
* @package OCP\Http
* @since 10.0.4
*/
interface IWebdavClientService {
/**
* @return Client
* @since 10.0.4
*/
public function newClient($params);
}
132 changes: 132 additions & 0 deletions tests/lib/Files/Storage/DavTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
/**
* ownCloud
*
* @author Robin Appelman
* @copyright 2012 Robin Appelman icewind@owncloud.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace Test\Files\Storage;

use Test\TestCase;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IClient;
use Sabre\DAV\Client;
use OCP\Http\Client\IWebdavClientService;
use Sabre\HTTP\ClientHttpException;
use OCP\Lock\LockedException;
use OCP\AppFramework\Http;
use OCP\Files\StorageInvalidException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ClientException;
use Sabre\DAV\Exception\InsufficientStorage;
use Sabre\DAV\Exception\Forbidden;
use OCP\Files\StorageNotAvailableException;

/**
* Class DavTest
*
* @group DB
*
* @package Test\Files\Storage
*/
class DavTest extends TestCase {

private $instance;

private $httpClientService;
private $webdavClientService;

protected function setUp() {
parent::setUp();

$this->httpClientService = $this->createMock(IClientService::class);
$this->overwriteService('HttpClientService', $this->httpClientService);

$this->webdavClientService = $this->createMock(IWebdavClientService::class);
$this->overwriteService('WebdavClientService', $this->webdavClientService);

$this->instance = new \OC\Files\Storage\DAV([
'user' => 'davuser',
'password' => 'davpassword',
'host' => 'davhost',
'root' => 'davroot',
]);
}

protected function tearDown() {
$this->restoreService('HttpClientService');
$this->restoreService('WebdavClientService');
parent::tearDown();
}

public function testId() {
$this->assertEquals('webdav::davuser@davhost//davroot/', $this->instance->getId());
}

private function createClientHttpException($statusCode) {
$response = $this->createMock(\Sabre\HTTP\ResponseInterface::class);
$response->method('getStatusText')->willReturn('');
$response->method('getStatus')->willReturn($statusCode);
return new ClientHttpException($response);
}

private function createGuzzleClientException($statusCode) {
$response = $this->createMock(\GuzzleHttp\MessageResponseInterface::class);
$response->method('getStatusCode')->willReturn($statusCode);
return new ClientException($response);
}

private function createGuzzleServerException($statusCode) {
$response = $this->createMock(\GuzzleHttp\MessageResponseInterface::class);
$response->method('getStatusCode')->willReturn($statusCode);
return new ServerException($response);
}

public function convertExceptionDataProvider() {
return [
[$this->createClientHttpException(Http::STATUS_UNAUTHORIZED), StorageInvalidException::class],
[$this->createClientHttpException(Http::STATUS_LOCKED), LockedException::class],
[$this->createClientHttpException(Http::STATUS_INSUFFICIENT_STORAGE), InsufficientStorage::class],
[$this->createClientHttpException(Http::STATUS_FORBIDDEN), Forbidden::class],
[$this->createClientHttpException(Http::STATUS_INTERNAL_SERVER_ERROR), StorageNotAvailableException::class],
[new \InvalidArgumentException(), StorageNotAvailableException::class],
[new StorageNotAvailableException(), StorageNotAvailableException::class],
[new StorageInvalidException(), StorageInvalidException::class],
];
}

/**
* @dataProvider convertExceptionDataProvider
*/
public function testConvertException($inputException, $expectedExceptionClass) {
$client = $this->createMock(Client::class);
$this->webdavClientService->method('newClient')->willReturn($client);
$client->method('propfind')->will($this->throwException($inputException));

$thrownException = null;
try {
$this->instance->opendir('/test');
} catch (\Exception $e) {
$thrownException = $e;
}

$this->assertNotNull($thrownException);
$this->assertInstanceOf($expectedExceptionClass, $thrownException);
}
}

0 comments on commit 5b5689c

Please sign in to comment.