StoragesServiceTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  6. * @author Joas Schilling <coding@schilljs.com>
  7. * @author Morris Jobke <hey@morrisjobke.de>
  8. * @author Robin Appelman <robin@icewind.nl>
  9. * @author Robin McCorkell <robin@mccorkell.me.uk>
  10. * @author Roeland Jago Douma <roeland@famdouma.nl>
  11. * @author Vincent Petry <vincent@nextcloud.com>
  12. *
  13. * @license AGPL-3.0
  14. *
  15. * This code is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU Affero General Public License, version 3,
  17. * as published by the Free Software Foundation.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU Affero General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU Affero General Public License, version 3,
  25. * along with this program. If not, see <http://www.gnu.org/licenses/>
  26. *
  27. */
  28. namespace OCA\Files_External\Tests\Service;
  29. use OC\Files\Filesystem;
  30. use OCA\Files_External\Lib\Auth\AuthMechanism;
  31. use OCA\Files_External\Lib\Auth\InvalidAuth;
  32. use OCA\Files_External\Lib\Backend\Backend;
  33. use OCA\Files_External\Lib\Backend\InvalidBackend;
  34. use OCA\Files_External\Lib\StorageConfig;
  35. use OCA\Files_External\NotFoundException;
  36. use OCA\Files_External\Service\BackendService;
  37. use OCA\Files_External\Service\DBConfigService;
  38. use OCA\Files_External\Service\StoragesService;
  39. use OCP\AppFramework\IAppContainer;
  40. use OCP\EventDispatcher\IEventDispatcher;
  41. use OCP\Files\Cache\ICache;
  42. use OCP\Files\Config\IUserMountCache;
  43. use OCP\Files\Mount\IMountPoint;
  44. use OCP\Files\Storage\IStorage;
  45. use OCP\IDBConnection;
  46. use OCP\IUser;
  47. use OCP\Server;
  48. class CleaningDBConfig extends DBConfigService {
  49. private $mountIds = [];
  50. public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) {
  51. $id = parent::addMount($mountPoint, $storageBackend, $authBackend, $priority, $type); // TODO: Change the autogenerated stub
  52. $this->mountIds[] = $id;
  53. return $id;
  54. }
  55. public function clean() {
  56. foreach ($this->mountIds as $id) {
  57. $this->removeMount($id);
  58. }
  59. }
  60. }
  61. /**
  62. * @group DB
  63. */
  64. abstract class StoragesServiceTest extends \Test\TestCase {
  65. /**
  66. * @var StoragesService
  67. */
  68. protected $service;
  69. /** @var BackendService */
  70. protected $backendService;
  71. /**
  72. * Data directory
  73. *
  74. * @var string
  75. */
  76. protected $dataDir;
  77. /** @var CleaningDBConfig */
  78. protected $dbConfig;
  79. /**
  80. * Hook calls
  81. *
  82. * @var array
  83. */
  84. protected static $hookCalls;
  85. /**
  86. * @var \PHPUnit\Framework\MockObject\MockObject|\OCP\Files\Config\IUserMountCache
  87. */
  88. protected $mountCache;
  89. /**
  90. * @var \PHPUnit\Framework\MockObject\MockObject|IEventDispatcher
  91. */
  92. protected IEventDispatcher $eventDispatcher;
  93. protected function setUp(): void {
  94. parent::setUp();
  95. $this->dbConfig = new CleaningDBConfig(\OC::$server->getDatabaseConnection(), \OC::$server->getCrypto());
  96. self::$hookCalls = [];
  97. $config = \OC::$server->getConfig();
  98. $this->dataDir = $config->getSystemValue(
  99. 'datadirectory',
  100. \OC::$SERVERROOT . '/data/'
  101. );
  102. \OCA\Files_External\MountConfig::$skipTest = true;
  103. $this->mountCache = $this->createMock(IUserMountCache::class);
  104. $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
  105. // prepare BackendService mock
  106. $this->backendService =
  107. $this->getMockBuilder('\OCA\Files_External\Service\BackendService')
  108. ->disableOriginalConstructor()
  109. ->getMock();
  110. $authMechanisms = [
  111. 'identifier:\Auth\Mechanism' => $this->getAuthMechMock('null', '\Auth\Mechanism'),
  112. 'identifier:\Other\Auth\Mechanism' => $this->getAuthMechMock('null', '\Other\Auth\Mechanism'),
  113. 'identifier:\OCA\Files_External\Lib\Auth\NullMechanism' => $this->getAuthMechMock(),
  114. ];
  115. $this->backendService->method('getAuthMechanism')
  116. ->willReturnCallback(function ($class) use ($authMechanisms) {
  117. if (isset($authMechanisms[$class])) {
  118. return $authMechanisms[$class];
  119. }
  120. return null;
  121. });
  122. $this->backendService->method('getAuthMechanismsByScheme')
  123. ->willReturnCallback(function ($schemes) use ($authMechanisms) {
  124. return array_filter($authMechanisms, function ($authMech) use ($schemes) {
  125. return in_array($authMech->getScheme(), $schemes, true);
  126. });
  127. });
  128. $this->backendService->method('getAuthMechanisms')
  129. ->willReturn($authMechanisms);
  130. $sftpBackend = $this->getBackendMock('\OCA\Files_External\Lib\Backend\SFTP', '\OCA\Files_External\Lib\Storage\SFTP');
  131. $backends = [
  132. 'identifier:\OCA\Files_External\Lib\Backend\DAV' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\DAV', '\OC\Files\Storage\DAV'),
  133. 'identifier:\OCA\Files_External\Lib\Backend\SMB' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\SMB', '\OCA\Files_External\Lib\Storage\SMB'),
  134. 'identifier:\OCA\Files_External\Lib\Backend\SFTP' => $sftpBackend,
  135. 'identifier:sftp_alias' => $sftpBackend,
  136. ];
  137. $backends['identifier:\OCA\Files_External\Lib\Backend\SFTP']->method('getLegacyAuthMechanism')
  138. ->willReturn($authMechanisms['identifier:\Other\Auth\Mechanism']);
  139. $this->backendService->method('getBackend')
  140. ->willReturnCallback(function ($backendClass) use ($backends) {
  141. if (isset($backends[$backendClass])) {
  142. return $backends[$backendClass];
  143. }
  144. return null;
  145. });
  146. $this->backendService->method('getBackends')
  147. ->willReturn($backends);
  148. $this->overwriteService(BackendService::class, $this->backendService);
  149. \OCP\Util::connectHook(
  150. Filesystem::CLASSNAME,
  151. Filesystem::signal_create_mount,
  152. get_class($this), 'createHookCallback');
  153. \OCP\Util::connectHook(
  154. Filesystem::CLASSNAME,
  155. Filesystem::signal_delete_mount,
  156. get_class($this), 'deleteHookCallback');
  157. $containerMock = $this->createMock(IAppContainer::class);
  158. $containerMock->method('query')
  159. ->willReturnCallback(function ($name) {
  160. if ($name === 'OCA\Files_External\Service\BackendService') {
  161. return $this->backendService;
  162. }
  163. });
  164. }
  165. protected function tearDown(): void {
  166. \OCA\Files_External\MountConfig::$skipTest = false;
  167. self::$hookCalls = [];
  168. if ($this->dbConfig) {
  169. $this->dbConfig->clean();
  170. }
  171. }
  172. protected function getBackendMock($class = '\OCA\Files_External\Lib\Backend\SMB', $storageClass = '\OCA\Files_External\Lib\Storage\SMB') {
  173. $backend = $this->getMockBuilder(Backend::class)
  174. ->disableOriginalConstructor()
  175. ->getMock();
  176. $backend->method('getStorageClass')
  177. ->willReturn($storageClass);
  178. $backend->method('getIdentifier')
  179. ->willReturn('identifier:' . $class);
  180. return $backend;
  181. }
  182. protected function getAuthMechMock($scheme = 'null', $class = '\OCA\Files_External\Lib\Auth\NullMechanism') {
  183. $authMech = $this->getMockBuilder(AuthMechanism::class)
  184. ->disableOriginalConstructor()
  185. ->getMock();
  186. $authMech->method('getScheme')
  187. ->willReturn($scheme);
  188. $authMech->method('getIdentifier')
  189. ->willReturn('identifier:' . $class);
  190. return $authMech;
  191. }
  192. /**
  193. * Creates a StorageConfig instance based on array data
  194. *
  195. * @param array $data
  196. *
  197. * @return StorageConfig storage config instance
  198. */
  199. protected function makeStorageConfig($data) {
  200. $storage = new StorageConfig();
  201. if (isset($data['id'])) {
  202. $storage->setId($data['id']);
  203. }
  204. $storage->setMountPoint($data['mountPoint']);
  205. if (!isset($data['backend'])) {
  206. // data providers are run before $this->backendService is initialised
  207. // so $data['backend'] can be specified directly
  208. $data['backend'] = $this->backendService->getBackend($data['backendIdentifier']);
  209. }
  210. if (!isset($data['backend'])) {
  211. throw new \Exception('oops, no backend');
  212. }
  213. if (!isset($data['authMechanism'])) {
  214. $data['authMechanism'] = $this->backendService->getAuthMechanism($data['authMechanismIdentifier']);
  215. }
  216. if (!isset($data['authMechanism'])) {
  217. throw new \Exception('oops, no auth mechanism');
  218. }
  219. $storage->setBackend($data['backend']);
  220. $storage->setAuthMechanism($data['authMechanism']);
  221. $storage->setBackendOptions($data['backendOptions']);
  222. if (isset($data['applicableUsers'])) {
  223. $storage->setApplicableUsers($data['applicableUsers']);
  224. }
  225. if (isset($data['applicableGroups'])) {
  226. $storage->setApplicableGroups($data['applicableGroups']);
  227. }
  228. if (isset($data['priority'])) {
  229. $storage->setPriority($data['priority']);
  230. }
  231. if (isset($data['mountOptions'])) {
  232. $storage->setMountOptions($data['mountOptions']);
  233. }
  234. return $storage;
  235. }
  236. protected function ActualNonExistingStorageTest() {
  237. $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB');
  238. $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism');
  239. $storage = new StorageConfig(255);
  240. $storage->setMountPoint('mountpoint');
  241. $storage->setBackend($backend);
  242. $storage->setAuthMechanism($authMechanism);
  243. $this->service->updateStorage($storage);
  244. }
  245. public function testNonExistingStorage() {
  246. $this->expectException(\OCA\Files_External\NotFoundException::class);
  247. $this->ActualNonExistingStorageTest();
  248. }
  249. public function deleteStorageDataProvider() {
  250. return [
  251. // regular case, can properly delete the oc_storages entry
  252. [
  253. [
  254. 'host' => 'example.com',
  255. 'user' => 'test',
  256. 'password' => 'testPassword',
  257. 'root' => 'someroot',
  258. ],
  259. 'webdav::test@example.com//someroot/'
  260. ],
  261. [
  262. [
  263. 'host' => 'example.com',
  264. 'user' => '$user',
  265. 'password' => 'testPassword',
  266. 'root' => 'someroot',
  267. ],
  268. 'webdav::someone@example.com//someroot/'
  269. ],
  270. ];
  271. }
  272. /**
  273. * @dataProvider deleteStorageDataProvider
  274. */
  275. public function testDeleteStorage($backendOptions, $rustyStorageId) {
  276. $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\DAV');
  277. $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism');
  278. $storage = new StorageConfig(255);
  279. $storage->setMountPoint('mountpoint');
  280. $storage->setBackend($backend);
  281. $storage->setAuthMechanism($authMechanism);
  282. $storage->setBackendOptions($backendOptions);
  283. $newStorage = $this->service->addStorage($storage);
  284. $id = $newStorage->getId();
  285. // manually trigger storage entry because normally it happens on first
  286. // access, which isn't possible within this test
  287. $storageCache = new \OC\Files\Cache\Storage($rustyStorageId, true, Server::get(IDBConnection::class));
  288. /** @var IUserMountCache $mountCache */
  289. $mountCache = \OC::$server->get(IUserMountCache::class);
  290. $mountCache->clear();
  291. $user = $this->createMock(IUser::class);
  292. $user->method('getUID')->willReturn('test');
  293. $cache = $this->createMock(ICache::class);
  294. $storage = $this->createMock(IStorage::class);
  295. $storage->method('getCache')->willReturn($cache);
  296. $mount = $this->createMock(IMountPoint::class);
  297. $mount->method('getStorage')
  298. ->willReturn($storage);
  299. $mount->method('getStorageId')
  300. ->willReturn($rustyStorageId);
  301. $mount->method('getNumericStorageId')
  302. ->willReturn($storageCache->getNumericId());
  303. $mount->method('getStorageRootId')
  304. ->willReturn(1);
  305. $mount->method('getMountPoint')
  306. ->willReturn('dummy');
  307. $mount->method('getMountId')
  308. ->willReturn($id);
  309. $mountCache->registerMounts($user, [
  310. $mount
  311. ]);
  312. // get numeric id for later check
  313. $numericId = $storageCache->getNumericId();
  314. $this->service->removeStorage($id);
  315. $caught = false;
  316. try {
  317. $this->service->getStorage(1);
  318. } catch (NotFoundException $e) {
  319. $caught = true;
  320. }
  321. $this->assertTrue($caught);
  322. // storage id was removed from oc_storages
  323. $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  324. $storageCheckQuery = $qb->select('*')
  325. ->from('storages')
  326. ->where($qb->expr()->eq('numeric_id', $qb->expr()->literal($numericId)));
  327. $result = $storageCheckQuery->execute();
  328. $storages = $result->fetchAll();
  329. $result->closeCursor();
  330. $this->assertCount(0, $storages, "expected 0 storages, got " . json_encode($storages));
  331. }
  332. protected function actualDeletedUnexistingStorageTest() {
  333. $this->service->removeStorage(255);
  334. }
  335. public function testDeleteUnexistingStorage() {
  336. $this->expectException(\OCA\Files_External\NotFoundException::class);
  337. $this->actualDeletedUnexistingStorageTest();
  338. }
  339. public function testCreateStorage() {
  340. $mountPoint = 'mount';
  341. $backendIdentifier = 'identifier:\OCA\Files_External\Lib\Backend\SMB';
  342. $authMechanismIdentifier = 'identifier:\Auth\Mechanism';
  343. $backendOptions = ['param' => 'foo', 'param2' => 'bar'];
  344. $mountOptions = ['option' => 'foobar'];
  345. $applicableUsers = ['user1', 'user2'];
  346. $applicableGroups = ['group'];
  347. $priority = 123;
  348. $backend = $this->backendService->getBackend($backendIdentifier);
  349. $authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier);
  350. $storage = $this->service->createStorage(
  351. $mountPoint,
  352. $backendIdentifier,
  353. $authMechanismIdentifier,
  354. $backendOptions,
  355. $mountOptions,
  356. $applicableUsers,
  357. $applicableGroups,
  358. $priority
  359. );
  360. $this->assertEquals('/' . $mountPoint, $storage->getMountPoint());
  361. $this->assertEquals($backend, $storage->getBackend());
  362. $this->assertEquals($authMechanism, $storage->getAuthMechanism());
  363. $this->assertEquals($backendOptions, $storage->getBackendOptions());
  364. $this->assertEquals($mountOptions, $storage->getMountOptions());
  365. $this->assertEquals($applicableUsers, $storage->getApplicableUsers());
  366. $this->assertEquals($applicableGroups, $storage->getApplicableGroups());
  367. $this->assertEquals($priority, $storage->getPriority());
  368. }
  369. public function testCreateStorageInvalidClass() {
  370. $storage = $this->service->createStorage(
  371. 'mount',
  372. 'identifier:\OC\Not\A\Backend',
  373. 'identifier:\Auth\Mechanism',
  374. []
  375. );
  376. $this->assertInstanceOf(InvalidBackend::class, $storage->getBackend());
  377. }
  378. public function testCreateStorageInvalidAuthMechanismClass() {
  379. $storage = $this->service->createStorage(
  380. 'mount',
  381. 'identifier:\OCA\Files_External\Lib\Backend\SMB',
  382. 'identifier:\Not\An\Auth\Mechanism',
  383. []
  384. );
  385. $this->assertInstanceOf(InvalidAuth::class, $storage->getAuthMechanism());
  386. }
  387. public function testGetStoragesBackendNotVisible() {
  388. $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB');
  389. $backend->expects($this->once())
  390. ->method('isVisibleFor')
  391. ->with($this->service->getVisibilityType())
  392. ->willReturn(false);
  393. $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism');
  394. $authMechanism->method('isVisibleFor')
  395. ->with($this->service->getVisibilityType())
  396. ->willReturn(true);
  397. $storage = new StorageConfig(255);
  398. $storage->setMountPoint('mountpoint');
  399. $storage->setBackend($backend);
  400. $storage->setAuthMechanism($authMechanism);
  401. $storage->setBackendOptions(['password' => 'testPassword']);
  402. $newStorage = $this->service->addStorage($storage);
  403. $this->assertCount(1, $this->service->getAllStorages());
  404. $this->assertEmpty($this->service->getStorages());
  405. }
  406. public function testGetStoragesAuthMechanismNotVisible() {
  407. $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB');
  408. $backend->method('isVisibleFor')
  409. ->with($this->service->getVisibilityType())
  410. ->willReturn(true);
  411. $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism');
  412. $authMechanism->expects($this->once())
  413. ->method('isVisibleFor')
  414. ->with($this->service->getVisibilityType())
  415. ->willReturn(false);
  416. $storage = new StorageConfig(255);
  417. $storage->setMountPoint('mountpoint');
  418. $storage->setBackend($backend);
  419. $storage->setAuthMechanism($authMechanism);
  420. $storage->setBackendOptions(['password' => 'testPassword']);
  421. $newStorage = $this->service->addStorage($storage);
  422. $this->assertCount(1, $this->service->getAllStorages());
  423. $this->assertEmpty($this->service->getStorages());
  424. }
  425. public static function createHookCallback($params) {
  426. self::$hookCalls[] = [
  427. 'signal' => Filesystem::signal_create_mount,
  428. 'params' => $params
  429. ];
  430. }
  431. public static function deleteHookCallback($params) {
  432. self::$hookCalls[] = [
  433. 'signal' => Filesystem::signal_delete_mount,
  434. 'params' => $params
  435. ];
  436. }
  437. /**
  438. * Asserts hook call
  439. *
  440. * @param array $callData hook call data to check
  441. * @param string $signal signal name
  442. * @param string $mountPath mount path
  443. * @param string $mountType mount type
  444. * @param string $applicable applicable users
  445. */
  446. protected function assertHookCall($callData, $signal, $mountPath, $mountType, $applicable) {
  447. $this->assertEquals($signal, $callData['signal']);
  448. $params = $callData['params'];
  449. $this->assertEquals(
  450. $mountPath,
  451. $params[Filesystem::signal_param_path]
  452. );
  453. $this->assertEquals(
  454. $mountType,
  455. $params[Filesystem::signal_param_mount_type]
  456. );
  457. $this->assertEquals(
  458. $applicable,
  459. $params[Filesystem::signal_param_users]
  460. );
  461. }
  462. public function testUpdateStorageMountPoint() {
  463. $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB');
  464. $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism');
  465. $storage = new StorageConfig();
  466. $storage->setMountPoint('mountpoint');
  467. $storage->setBackend($backend);
  468. $storage->setAuthMechanism($authMechanism);
  469. $storage->setBackendOptions(['password' => 'testPassword']);
  470. $savedStorage = $this->service->addStorage($storage);
  471. $newAuthMechanism = $this->backendService->getAuthMechanism('identifier:\Other\Auth\Mechanism');
  472. $updatedStorage = new StorageConfig($savedStorage->getId());
  473. $updatedStorage->setMountPoint('mountpoint2');
  474. $updatedStorage->setBackend($backend);
  475. $updatedStorage->setAuthMechanism($newAuthMechanism);
  476. $updatedStorage->setBackendOptions(['password' => 'password2']);
  477. $this->service->updateStorage($updatedStorage);
  478. $savedStorage = $this->service->getStorage($updatedStorage->getId());
  479. $this->assertEquals('/mountpoint2', $savedStorage->getMountPoint());
  480. $this->assertEquals($newAuthMechanism, $savedStorage->getAuthMechanism());
  481. $this->assertEquals('password2', $savedStorage->getBackendOption('password'));
  482. }
  483. }