config.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. <?php
  2. /**
  3. * @author Bart Visscher <bartv@thisnet.nl>
  4. * @author Björn Schießle <schiessle@owncloud.com>
  5. * @author Frank Karlitschek <frank@owncloud.org>
  6. * @author Joas Schilling <nickvergessen@owncloud.com>
  7. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Lukas Reschke <lukas@owncloud.com>
  9. * @author Michael Gapczynski <GapczynskiM@gmail.com>
  10. * @author Morris Jobke <hey@morrisjobke.de>
  11. * @author Philipp Kapfer <philipp.kapfer@gmx.at>
  12. * @author Robin Appelman <icewind@owncloud.com>
  13. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  14. * @author Thomas Müller <thomas.mueller@tmit.eu>
  15. * @author Vincent Petry <pvince81@owncloud.com>
  16. *
  17. * @copyright Copyright (c) 2015, ownCloud, Inc.
  18. * @license AGPL-3.0
  19. *
  20. * This code is free software: you can redistribute it and/or modify
  21. * it under the terms of the GNU Affero General Public License, version 3,
  22. * as published by the Free Software Foundation.
  23. *
  24. * This program is distributed in the hope that it will be useful,
  25. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  26. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  27. * GNU Affero General Public License for more details.
  28. *
  29. * You should have received a copy of the GNU Affero General Public License, version 3,
  30. * along with this program. If not, see <http://www.gnu.org/licenses/>
  31. *
  32. */
  33. /**
  34. * Class to configure mount.json globally and for users
  35. */
  36. class OC_Mount_Config {
  37. // TODO: make this class non-static and give it a proper namespace
  38. const MOUNT_TYPE_GLOBAL = 'global';
  39. const MOUNT_TYPE_GROUP = 'group';
  40. const MOUNT_TYPE_USER = 'user';
  41. const MOUNT_TYPE_PERSONAL = 'personal';
  42. // getBackendStatus return types
  43. const STATUS_SUCCESS = 0;
  44. const STATUS_ERROR = 1;
  45. // whether to skip backend test (for unit tests, as this static class is not mockable)
  46. public static $skipTest = false;
  47. private static $backends = array();
  48. /**
  49. * @param string $class
  50. * @param array $definition
  51. * @return bool
  52. */
  53. public static function registerBackend($class, $definition) {
  54. if (!isset($definition['backend'])) {
  55. return false;
  56. }
  57. OC_Mount_Config::$backends[$class] = $definition;
  58. return true;
  59. }
  60. /**
  61. * Setup backends
  62. *
  63. * @return array of previously registered backends
  64. */
  65. public static function setUp($backends = array()) {
  66. $backup = self::$backends;
  67. self::$backends = $backends;
  68. return $backup;
  69. }
  70. /**
  71. * Get details on each of the external storage backends, used for the mount config UI
  72. * If a custom UI is needed, add the key 'custom' and a javascript file with that name will be loaded
  73. * If the configuration parameter should be secret, add a '*' to the beginning of the value
  74. * If the configuration parameter is a boolean, add a '!' to the beginning of the value
  75. * If the configuration parameter is optional, add a '&' to the beginning of the value
  76. * If the configuration parameter is hidden, add a '#' to the beginning of the value
  77. *
  78. * @return array
  79. */
  80. public static function getBackends() {
  81. $sortFunc = function ($a, $b) {
  82. return strcasecmp($a['backend'], $b['backend']);
  83. };
  84. $backEnds = array();
  85. foreach (OC_Mount_Config::$backends as $class => $backend) {
  86. if (isset($backend['has_dependencies']) and $backend['has_dependencies'] === true) {
  87. if (!method_exists($class, 'checkDependencies')) {
  88. \OCP\Util::writeLog('files_external',
  89. "Backend class $class has dependencies but doesn't provide method checkDependencies()",
  90. \OCP\Util::DEBUG);
  91. continue;
  92. } elseif ($class::checkDependencies() !== true) {
  93. continue;
  94. }
  95. }
  96. $backEnds[$class] = $backend;
  97. }
  98. uasort($backEnds, $sortFunc);
  99. return $backEnds;
  100. }
  101. /**
  102. * Hook that mounts the given user's visible mount points
  103. *
  104. * @param array $data
  105. */
  106. public static function initMountPointsHook($data) {
  107. self::addStorageIdToConfig(null);
  108. if ($data['user']) {
  109. self::addStorageIdToConfig($data['user']);
  110. $user = \OC::$server->getUserManager()->get($data['user']);
  111. if (!$user) {
  112. \OCP\Util::writeLog(
  113. 'files_external',
  114. 'Cannot init external mount points for non-existant user "' . $data['user'] . '".',
  115. \OCP\Util::WARN
  116. );
  117. return;
  118. }
  119. $userView = new \OC\Files\View('/' . $user->getUID() . '/files');
  120. $changePropagator = new \OC\Files\Cache\ChangePropagator($userView);
  121. $etagPropagator = new \OCA\Files_External\EtagPropagator($user, $changePropagator, \OC::$server->getConfig());
  122. $etagPropagator->propagateDirtyMountPoints();
  123. \OCP\Util::connectHook(
  124. \OC\Files\Filesystem::CLASSNAME,
  125. \OC\Files\Filesystem::signal_create_mount,
  126. $etagPropagator, 'updateHook');
  127. \OCP\Util::connectHook(
  128. \OC\Files\Filesystem::CLASSNAME,
  129. \OC\Files\Filesystem::signal_delete_mount,
  130. $etagPropagator, 'updateHook');
  131. }
  132. }
  133. /**
  134. * Returns the mount points for the given user.
  135. * The mount point is relative to the data directory.
  136. *
  137. * @param string $user user
  138. * @return array of mount point string as key, mountpoint config as value
  139. */
  140. public static function getAbsoluteMountPoints($user) {
  141. $mountPoints = array();
  142. $backends = self::getBackends();
  143. // Load system mount points
  144. $mountConfig = self::readData();
  145. // Global mount points (is this redundant?)
  146. if (isset($mountConfig[self::MOUNT_TYPE_GLOBAL])) {
  147. foreach ($mountConfig[self::MOUNT_TYPE_GLOBAL] as $mountPoint => $options) {
  148. $options['personal'] = false;
  149. $options['options'] = self::decryptPasswords($options['options']);
  150. if (!isset($options['priority'])) {
  151. $options['priority'] = $backends[$options['class']]['priority'];
  152. }
  153. // Override if priority greater
  154. if ((!isset($mountPoints[$mountPoint]))
  155. || ($options['priority'] >= $mountPoints[$mountPoint]['priority'])
  156. ) {
  157. $options['priority_type'] = self::MOUNT_TYPE_GLOBAL;
  158. $options['backend'] = $backends[$options['class']]['backend'];
  159. $mountPoints[$mountPoint] = $options;
  160. }
  161. }
  162. }
  163. // All user mount points
  164. if (isset($mountConfig[self::MOUNT_TYPE_USER]) && isset($mountConfig[self::MOUNT_TYPE_USER]['all'])) {
  165. $mounts = $mountConfig[self::MOUNT_TYPE_USER]['all'];
  166. foreach ($mounts as $mountPoint => $options) {
  167. $mountPoint = self::setUserVars($user, $mountPoint);
  168. foreach ($options as &$option) {
  169. $option = self::setUserVars($user, $option);
  170. }
  171. $options['personal'] = false;
  172. $options['options'] = self::decryptPasswords($options['options']);
  173. if (!isset($options['priority'])) {
  174. $options['priority'] = $backends[$options['class']]['priority'];
  175. }
  176. // Override if priority greater
  177. if ((!isset($mountPoints[$mountPoint]))
  178. || ($options['priority'] >= $mountPoints[$mountPoint]['priority'])
  179. ) {
  180. $options['priority_type'] = self::MOUNT_TYPE_GLOBAL;
  181. $options['backend'] = $backends[$options['class']]['backend'];
  182. $mountPoints[$mountPoint] = $options;
  183. }
  184. }
  185. }
  186. // Group mount points
  187. if (isset($mountConfig[self::MOUNT_TYPE_GROUP])) {
  188. foreach ($mountConfig[self::MOUNT_TYPE_GROUP] as $group => $mounts) {
  189. if (\OC_Group::inGroup($user, $group)) {
  190. foreach ($mounts as $mountPoint => $options) {
  191. $mountPoint = self::setUserVars($user, $mountPoint);
  192. foreach ($options as &$option) {
  193. $option = self::setUserVars($user, $option);
  194. }
  195. $options['personal'] = false;
  196. $options['options'] = self::decryptPasswords($options['options']);
  197. if (!isset($options['priority'])) {
  198. $options['priority'] = $backends[$options['class']]['priority'];
  199. }
  200. // Override if priority greater or if priority type different
  201. if ((!isset($mountPoints[$mountPoint]))
  202. || ($options['priority'] >= $mountPoints[$mountPoint]['priority'])
  203. || ($mountPoints[$mountPoint]['priority_type'] !== self::MOUNT_TYPE_GROUP)
  204. ) {
  205. $options['priority_type'] = self::MOUNT_TYPE_GROUP;
  206. $options['backend'] = $backends[$options['class']]['backend'];
  207. $mountPoints[$mountPoint] = $options;
  208. }
  209. }
  210. }
  211. }
  212. }
  213. // User mount points
  214. if (isset($mountConfig[self::MOUNT_TYPE_USER])) {
  215. foreach ($mountConfig[self::MOUNT_TYPE_USER] as $mountUser => $mounts) {
  216. if (strtolower($mountUser) === strtolower($user)) {
  217. foreach ($mounts as $mountPoint => $options) {
  218. $mountPoint = self::setUserVars($user, $mountPoint);
  219. foreach ($options as &$option) {
  220. $option = self::setUserVars($user, $option);
  221. }
  222. $options['personal'] = false;
  223. $options['options'] = self::decryptPasswords($options['options']);
  224. if (!isset($options['priority'])) {
  225. $options['priority'] = $backends[$options['class']]['priority'];
  226. }
  227. // Override if priority greater or if priority type different
  228. if ((!isset($mountPoints[$mountPoint]))
  229. || ($options['priority'] >= $mountPoints[$mountPoint]['priority'])
  230. || ($mountPoints[$mountPoint]['priority_type'] !== self::MOUNT_TYPE_USER)
  231. ) {
  232. $options['priority_type'] = self::MOUNT_TYPE_USER;
  233. $options['backend'] = $backends[$options['class']]['backend'];
  234. $mountPoints[$mountPoint] = $options;
  235. }
  236. }
  237. }
  238. }
  239. }
  240. $personalBackends = self::getPersonalBackends();
  241. // Load personal mount points
  242. $mountConfig = self::readData($user);
  243. if (isset($mountConfig[self::MOUNT_TYPE_USER][$user])) {
  244. foreach ($mountConfig[self::MOUNT_TYPE_USER][$user] as $mountPoint => $options) {
  245. if (isset($personalBackends[$options['class']])) {
  246. $options['personal'] = true;
  247. $options['options'] = self::decryptPasswords($options['options']);
  248. // Always override previous config
  249. $options['priority_type'] = self::MOUNT_TYPE_PERSONAL;
  250. $options['backend'] = $backends[$options['class']]['backend'];
  251. $mountPoints[$mountPoint] = $options;
  252. }
  253. }
  254. }
  255. return $mountPoints;
  256. }
  257. /**
  258. * fill in the correct values for $user
  259. *
  260. * @param string $user user value
  261. * @param string|array $input
  262. * @return string
  263. */
  264. private static function setUserVars($user, $input) {
  265. if (is_array($input)) {
  266. foreach ($input as &$value) {
  267. if (is_string($value)) {
  268. $value = str_replace('$user', $user, $value);
  269. }
  270. }
  271. } else {
  272. $input = str_replace('$user', $user, $input);
  273. }
  274. return $input;
  275. }
  276. /**
  277. * Get details on each of the external storage backends, used for the mount config UI
  278. * Some backends are not available as a personal backend, f.e. Local and such that have
  279. * been disabled by the admin.
  280. *
  281. * If a custom UI is needed, add the key 'custom' and a javascript file with that name will be loaded
  282. * If the configuration parameter should be secret, add a '*' to the beginning of the value
  283. * If the configuration parameter is a boolean, add a '!' to the beginning of the value
  284. * If the configuration parameter is optional, add a '&' to the beginning of the value
  285. * If the configuration parameter is hidden, add a '#' to the beginning of the value
  286. *
  287. * @return array
  288. */
  289. public static function getPersonalBackends() {
  290. // Check whether the user has permissions to add personal storage backends
  291. // return an empty array if this is not the case
  292. if (OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes') !== 'yes') {
  293. return array();
  294. }
  295. $backEnds = self::getBackends();
  296. // Remove local storage and other disabled storages
  297. unset($backEnds['\OC\Files\Storage\Local']);
  298. $allowedBackEnds = explode(',', OCP\Config::getAppValue('files_external', 'user_mounting_backends', ''));
  299. foreach ($backEnds as $backend => $null) {
  300. if (!in_array($backend, $allowedBackEnds)) {
  301. unset($backEnds[$backend]);
  302. }
  303. }
  304. return $backEnds;
  305. }
  306. /**
  307. * Get the system mount points
  308. * The returned array is not in the same format as getUserMountPoints()
  309. *
  310. * @return array
  311. */
  312. public static function getSystemMountPoints() {
  313. $mountPoints = self::readData();
  314. $backends = self::getBackends();
  315. $system = array();
  316. if (isset($mountPoints[self::MOUNT_TYPE_GROUP])) {
  317. foreach ($mountPoints[self::MOUNT_TYPE_GROUP] as $group => $mounts) {
  318. foreach ($mounts as $mountPoint => $mount) {
  319. // Update old classes to new namespace
  320. if (strpos($mount['class'], 'OC_Filestorage_') !== false) {
  321. $mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15);
  322. }
  323. $mount['options'] = self::decryptPasswords($mount['options']);
  324. if (!isset($mount['priority'])) {
  325. $mount['priority'] = $backends[$mount['class']]['priority'];
  326. }
  327. // Remove '/$user/files/' from mount point
  328. $mountPoint = substr($mountPoint, 13);
  329. $config = array(
  330. 'class' => $mount['class'],
  331. 'mountpoint' => $mountPoint,
  332. 'backend' => $backends[$mount['class']]['backend'],
  333. 'priority' => $mount['priority'],
  334. 'options' => $mount['options'],
  335. 'applicable' => array('groups' => array($group), 'users' => array())
  336. );
  337. if (isset($mount['id'])) {
  338. $config['id'] = (int)$mount['id'];
  339. }
  340. if (isset($mount['storage_id'])) {
  341. $config['storage_id'] = (int)$mount['storage_id'];
  342. }
  343. if (isset($mount['mountOptions'])) {
  344. $config['mountOptions'] = $mount['mountOptions'];
  345. }
  346. $hash = self::makeConfigHash($config);
  347. // If an existing config exists (with same class, mountpoint and options)
  348. if (isset($system[$hash])) {
  349. // add the groups into that config
  350. $system[$hash]['applicable']['groups']
  351. = array_merge($system[$hash]['applicable']['groups'], array($group));
  352. } else {
  353. $system[$hash] = $config;
  354. }
  355. }
  356. }
  357. }
  358. if (isset($mountPoints[self::MOUNT_TYPE_USER])) {
  359. foreach ($mountPoints[self::MOUNT_TYPE_USER] as $user => $mounts) {
  360. foreach ($mounts as $mountPoint => $mount) {
  361. // Update old classes to new namespace
  362. if (strpos($mount['class'], 'OC_Filestorage_') !== false) {
  363. $mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15);
  364. }
  365. $mount['options'] = self::decryptPasswords($mount['options']);
  366. if (!isset($mount['priority'])) {
  367. $mount['priority'] = $backends[$mount['class']]['priority'];
  368. }
  369. // Remove '/$user/files/' from mount point
  370. $mountPoint = substr($mountPoint, 13);
  371. $config = array(
  372. 'class' => $mount['class'],
  373. 'mountpoint' => $mountPoint,
  374. 'backend' => $backends[$mount['class']]['backend'],
  375. 'priority' => $mount['priority'],
  376. 'options' => $mount['options'],
  377. 'applicable' => array('groups' => array(), 'users' => array($user))
  378. );
  379. if (isset($mount['id'])) {
  380. $config['id'] = (int)$mount['id'];
  381. }
  382. if (isset($mount['storage_id'])) {
  383. $config['storage_id'] = (int)$mount['storage_id'];
  384. }
  385. if (isset($mount['mountOptions'])) {
  386. $config['mountOptions'] = $mount['mountOptions'];
  387. }
  388. $hash = self::makeConfigHash($config);
  389. // If an existing config exists (with same class, mountpoint and options)
  390. if (isset($system[$hash])) {
  391. // add the users into that config
  392. $system[$hash]['applicable']['users']
  393. = array_merge($system[$hash]['applicable']['users'], array($user));
  394. } else {
  395. $system[$hash] = $config;
  396. }
  397. }
  398. }
  399. }
  400. return array_values($system);
  401. }
  402. /**
  403. * Get the personal mount points of the current user
  404. * The returned array is not in the same format as getUserMountPoints()
  405. *
  406. * @return array
  407. */
  408. public static function getPersonalMountPoints() {
  409. $mountPoints = self::readData(OCP\User::getUser());
  410. $backEnds = self::getBackends();
  411. $uid = OCP\User::getUser();
  412. $personal = array();
  413. if (isset($mountPoints[self::MOUNT_TYPE_USER][$uid])) {
  414. foreach ($mountPoints[self::MOUNT_TYPE_USER][$uid] as $mountPoint => $mount) {
  415. // Update old classes to new namespace
  416. if (strpos($mount['class'], 'OC_Filestorage_') !== false) {
  417. $mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15);
  418. }
  419. $mount['options'] = self::decryptPasswords($mount['options']);
  420. $config = array(
  421. 'class' => $mount['class'],
  422. // Remove '/uid/files/' from mount point
  423. 'mountpoint' => substr($mountPoint, strlen($uid) + 8),
  424. 'backend' => $backEnds[$mount['class']]['backend'],
  425. 'options' => $mount['options']
  426. );
  427. if (isset($mount['id'])) {
  428. $config['id'] = (int)$mount['id'];
  429. }
  430. if (isset($mount['storage_id'])) {
  431. $config['storage_id'] = (int)$mount['storage_id'];
  432. }
  433. if (isset($mount['mountOptions'])) {
  434. $config['mountOptions'] = $mount['mountOptions'];
  435. }
  436. $personal[] = $config;
  437. }
  438. }
  439. return $personal;
  440. }
  441. /**
  442. * Test connecting using the given backend configuration
  443. *
  444. * @param string $class backend class name
  445. * @param array $options backend configuration options
  446. * @return int see self::STATUS_*
  447. */
  448. public static function getBackendStatus($class, $options, $isPersonal) {
  449. if (self::$skipTest) {
  450. return self::STATUS_SUCCESS;
  451. }
  452. foreach ($options as &$option) {
  453. $option = self::setUserVars(OCP\User::getUser(), $option);
  454. }
  455. if (class_exists($class)) {
  456. try {
  457. $storage = new $class($options);
  458. if ($storage->test($isPersonal)) {
  459. return self::STATUS_SUCCESS;
  460. }
  461. } catch (Exception $exception) {
  462. \OCP\Util::logException('files_external', $exception);
  463. }
  464. }
  465. return self::STATUS_ERROR;
  466. }
  467. /**
  468. * Add a mount point to the filesystem
  469. *
  470. * @param string $mountPoint Mount point
  471. * @param string $class Backend class
  472. * @param array $classOptions Backend parameters for the class
  473. * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER
  474. * @param string $applicable User or group to apply mount to
  475. * @param bool $isPersonal Personal or system mount point i.e. is this being called from the personal or admin page
  476. * @param int|null $priority Mount point priority, null for default
  477. * @return boolean
  478. *
  479. * @deprecated use StoragesService#addStorage() instead
  480. */
  481. public static function addMountPoint($mountPoint,
  482. $class,
  483. $classOptions,
  484. $mountType,
  485. $applicable,
  486. $isPersonal = false,
  487. $priority = null) {
  488. $backends = self::getBackends();
  489. $mountPoint = OC\Files\Filesystem::normalizePath($mountPoint);
  490. $relMountPoint = $mountPoint;
  491. if ($mountPoint === '' || $mountPoint === '/') {
  492. // can't mount at root folder
  493. return false;
  494. }
  495. if (!isset($backends[$class])) {
  496. // invalid backend
  497. return false;
  498. }
  499. if ($isPersonal) {
  500. // Verify that the mount point applies for the current user
  501. // Prevent non-admin users from mounting local storage and other disabled backends
  502. $allowed_backends = self::getPersonalBackends();
  503. if ($applicable != OCP\User::getUser() || !isset($allowed_backends[$class])) {
  504. return false;
  505. }
  506. $mountPoint = '/' . $applicable . '/files/' . ltrim($mountPoint, '/');
  507. } else {
  508. $mountPoint = '/$user/files/' . ltrim($mountPoint, '/');
  509. }
  510. $mount = array($applicable => array(
  511. $mountPoint => array(
  512. 'class' => $class,
  513. 'options' => self::encryptPasswords($classOptions))
  514. )
  515. );
  516. if (!$isPersonal && !is_null($priority)) {
  517. $mount[$applicable][$mountPoint]['priority'] = $priority;
  518. }
  519. $mountPoints = self::readData($isPersonal ? OCP\User::getUser() : null);
  520. // who else loves multi-dimensional array ?
  521. $isNew = !isset($mountPoints[$mountType]) ||
  522. !isset($mountPoints[$mountType][$applicable]) ||
  523. !isset($mountPoints[$mountType][$applicable][$mountPoint]);
  524. $mountPoints = self::mergeMountPoints($mountPoints, $mount, $mountType);
  525. // Set default priority if none set
  526. if (!isset($mountPoints[$mountType][$applicable][$mountPoint]['priority'])) {
  527. if (isset($backends[$class]['priority'])) {
  528. $mountPoints[$mountType][$applicable][$mountPoint]['priority']
  529. = $backends[$class]['priority'];
  530. } else {
  531. $mountPoints[$mountType][$applicable][$mountPoint]['priority']
  532. = 100;
  533. }
  534. }
  535. self::writeData($isPersonal ? OCP\User::getUser() : null, $mountPoints);
  536. $result = self::getBackendStatus($class, $classOptions, $isPersonal);
  537. if ($result === self::STATUS_SUCCESS && $isNew) {
  538. \OC_Hook::emit(
  539. \OC\Files\Filesystem::CLASSNAME,
  540. \OC\Files\Filesystem::signal_create_mount,
  541. array(
  542. \OC\Files\Filesystem::signal_param_path => $relMountPoint,
  543. \OC\Files\Filesystem::signal_param_mount_type => $mountType,
  544. \OC\Files\Filesystem::signal_param_users => $applicable,
  545. )
  546. );
  547. }
  548. return $result;
  549. }
  550. /**
  551. *
  552. * @param string $mountPoint Mount point
  553. * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER
  554. * @param string $applicable User or group to remove mount from
  555. * @param bool $isPersonal Personal or system mount point
  556. * @return bool
  557. *
  558. * @deprecated use StoragesService#removeStorage() instead
  559. */
  560. public static function removeMountPoint($mountPoint, $mountType, $applicable, $isPersonal = false) {
  561. // Verify that the mount point applies for the current user
  562. $relMountPoints = $mountPoint;
  563. if ($isPersonal) {
  564. if ($applicable != OCP\User::getUser()) {
  565. return false;
  566. }
  567. $mountPoint = '/' . $applicable . '/files/' . ltrim($mountPoint, '/');
  568. } else {
  569. $mountPoint = '/$user/files/' . ltrim($mountPoint, '/');
  570. }
  571. $mountPoint = \OC\Files\Filesystem::normalizePath($mountPoint);
  572. $mountPoints = self::readData($isPersonal ? OCP\User::getUser() : null);
  573. // Remove mount point
  574. unset($mountPoints[$mountType][$applicable][$mountPoint]);
  575. // Unset parent arrays if empty
  576. if (empty($mountPoints[$mountType][$applicable])) {
  577. unset($mountPoints[$mountType][$applicable]);
  578. if (empty($mountPoints[$mountType])) {
  579. unset($mountPoints[$mountType]);
  580. }
  581. }
  582. self::writeData($isPersonal ? OCP\User::getUser() : null, $mountPoints);
  583. \OC_Hook::emit(
  584. \OC\Files\Filesystem::CLASSNAME,
  585. \OC\Files\Filesystem::signal_delete_mount,
  586. array(
  587. \OC\Files\Filesystem::signal_param_path => $relMountPoints,
  588. \OC\Files\Filesystem::signal_param_mount_type => $mountType,
  589. \OC\Files\Filesystem::signal_param_users => $applicable,
  590. )
  591. );
  592. return true;
  593. }
  594. /**
  595. *
  596. * @param string $mountPoint Mount point
  597. * @param string $target The new mount point
  598. * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER
  599. * @return bool
  600. */
  601. public static function movePersonalMountPoint($mountPoint, $target, $mountType) {
  602. $mountPoint = rtrim($mountPoint, '/');
  603. $user = OCP\User::getUser();
  604. $mountPoints = self::readData($user);
  605. if (!isset($mountPoints[$mountType][$user][$mountPoint])) {
  606. return false;
  607. }
  608. $mountPoints[$mountType][$user][$target] = $mountPoints[$mountType][$user][$mountPoint];
  609. // Remove old mount point
  610. unset($mountPoints[$mountType][$user][$mountPoint]);
  611. self::writeData($user, $mountPoints);
  612. return true;
  613. }
  614. /**
  615. * Read the mount points in the config file into an array
  616. *
  617. * @param string|null $user If not null, personal for $user, otherwise system
  618. * @return array
  619. */
  620. public static function readData($user = null) {
  621. if (isset($user)) {
  622. $jsonFile = OC_User::getHome($user) . '/mount.json';
  623. } else {
  624. $datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/');
  625. $jsonFile = \OC_Config::getValue('mount_file', $datadir . '/mount.json');
  626. }
  627. if (is_file($jsonFile)) {
  628. $mountPoints = json_decode(file_get_contents($jsonFile), true);
  629. if (is_array($mountPoints)) {
  630. return $mountPoints;
  631. }
  632. }
  633. return array();
  634. }
  635. /**
  636. * Write the mount points to the config file
  637. *
  638. * @param string|null $user If not null, personal for $user, otherwise system
  639. * @param array $data Mount points
  640. */
  641. public static function writeData($user, $data) {
  642. if (isset($user)) {
  643. $file = OC_User::getHome($user) . '/mount.json';
  644. } else {
  645. $datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/');
  646. $file = \OC_Config::getValue('mount_file', $datadir . '/mount.json');
  647. }
  648. foreach ($data as &$applicables) {
  649. foreach ($applicables as &$mountPoints) {
  650. foreach ($mountPoints as &$options) {
  651. self::addStorageId($options);
  652. }
  653. }
  654. }
  655. $content = json_encode($data, JSON_PRETTY_PRINT);
  656. @file_put_contents($file, $content);
  657. @chmod($file, 0640);
  658. }
  659. /**
  660. * check dependencies
  661. */
  662. public static function checkDependencies() {
  663. $dependencies = array();
  664. foreach (OC_Mount_Config::$backends as $class => $backend) {
  665. if (isset($backend['has_dependencies']) and $backend['has_dependencies'] === true) {
  666. $result = $class::checkDependencies();
  667. if ($result !== true) {
  668. if (!is_array($result)) {
  669. $result = array($result);
  670. }
  671. foreach ($result as $key => $value) {
  672. if (is_numeric($key)) {
  673. OC_Mount_Config::addDependency($dependencies, $value, $backend['backend']);
  674. } else {
  675. OC_Mount_Config::addDependency($dependencies, $key, $backend['backend'], $value);
  676. }
  677. }
  678. }
  679. }
  680. }
  681. if (count($dependencies) > 0) {
  682. return OC_Mount_Config::generateDependencyMessage($dependencies);
  683. }
  684. return '';
  685. }
  686. private static function addDependency(&$dependencies, $module, $backend, $message = null) {
  687. if (!isset($dependencies[$module])) {
  688. $dependencies[$module] = array();
  689. }
  690. if ($message === null) {
  691. $dependencies[$module][] = $backend;
  692. } else {
  693. $dependencies[$module][] = array('backend' => $backend, 'message' => $message);
  694. }
  695. }
  696. private static function generateDependencyMessage($dependencies) {
  697. $l = new \OC_L10N('files_external');
  698. $dependencyMessage = '';
  699. foreach ($dependencies as $module => $backends) {
  700. $dependencyGroup = array();
  701. foreach ($backends as $backend) {
  702. if (is_array($backend)) {
  703. $dependencyMessage .= '<br />' . $l->t('<b>Note:</b> ') . $backend['message'];
  704. } else {
  705. $dependencyGroup[] = $backend;
  706. }
  707. }
  708. $dependencyGroupCount = count($dependencyGroup);
  709. if ($dependencyGroupCount > 0) {
  710. $backends = '';
  711. for ($i = 0; $i < $dependencyGroupCount; $i++) {
  712. if ($i > 0 && $i === $dependencyGroupCount - 1) {
  713. $backends .= ' ' . $l->t('and') . ' ';
  714. } elseif ($i > 0) {
  715. $backends .= ', ';
  716. }
  717. $backends .= '<i>' . $dependencyGroup[$i] . '</i>';
  718. }
  719. $dependencyMessage .= '<br />' . OC_Mount_Config::getSingleDependencyMessage($l, $module, $backends);
  720. }
  721. }
  722. return $dependencyMessage;
  723. }
  724. /**
  725. * Returns a dependency missing message
  726. *
  727. * @param OC_L10N $l
  728. * @param string $module
  729. * @param string $backend
  730. * @return string
  731. */
  732. private static function getSingleDependencyMessage(OC_L10N $l, $module, $backend) {
  733. switch (strtolower($module)) {
  734. case 'curl':
  735. return $l->t('<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it.', $backend);
  736. case 'ftp':
  737. return $l->t('<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it.', $backend);
  738. default:
  739. return $l->t('<b>Note:</b> "%s" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it.', array($module, $backend));
  740. }
  741. }
  742. /**
  743. * Encrypt passwords in the given config options
  744. *
  745. * @param array $options mount options
  746. * @return array updated options
  747. */
  748. public static function encryptPasswords($options) {
  749. if (isset($options['password'])) {
  750. $options['password_encrypted'] = self::encryptPassword($options['password']);
  751. // do not unset the password, we want to keep the keys order
  752. // on load... because that's how the UI currently works
  753. $options['password'] = '';
  754. }
  755. return $options;
  756. }
  757. /**
  758. * Decrypt passwords in the given config options
  759. *
  760. * @param array $options mount options
  761. * @return array updated options
  762. */
  763. public static function decryptPasswords($options) {
  764. // note: legacy options might still have the unencrypted password in the "password" field
  765. if (isset($options['password_encrypted'])) {
  766. $options['password'] = self::decryptPassword($options['password_encrypted']);
  767. unset($options['password_encrypted']);
  768. }
  769. return $options;
  770. }
  771. /**
  772. * Encrypt a single password
  773. *
  774. * @param string $password plain text password
  775. * @return string encrypted password
  776. */
  777. private static function encryptPassword($password) {
  778. $cipher = self::getCipher();
  779. $iv = \OCP\Util::generateRandomBytes(16);
  780. $cipher->setIV($iv);
  781. return base64_encode($iv . $cipher->encrypt($password));
  782. }
  783. /**
  784. * Decrypts a single password
  785. *
  786. * @param string $encryptedPassword encrypted password
  787. * @return string plain text password
  788. */
  789. private static function decryptPassword($encryptedPassword) {
  790. $cipher = self::getCipher();
  791. $binaryPassword = base64_decode($encryptedPassword);
  792. $iv = substr($binaryPassword, 0, 16);
  793. $cipher->setIV($iv);
  794. $binaryPassword = substr($binaryPassword, 16);
  795. return $cipher->decrypt($binaryPassword);
  796. }
  797. /**
  798. * Merges mount points
  799. *
  800. * @param array $data Existing mount points
  801. * @param array $mountPoint New mount point
  802. * @param string $mountType
  803. * @return array
  804. */
  805. private static function mergeMountPoints($data, $mountPoint, $mountType) {
  806. $applicable = key($mountPoint);
  807. $mountPath = key($mountPoint[$applicable]);
  808. if (isset($data[$mountType])) {
  809. if (isset($data[$mountType][$applicable])) {
  810. // Merge priorities
  811. if (isset($data[$mountType][$applicable][$mountPath])
  812. && isset($data[$mountType][$applicable][$mountPath]['priority'])
  813. && !isset($mountPoint[$applicable][$mountPath]['priority'])
  814. ) {
  815. $mountPoint[$applicable][$mountPath]['priority']
  816. = $data[$mountType][$applicable][$mountPath]['priority'];
  817. }
  818. $data[$mountType][$applicable]
  819. = array_merge($data[$mountType][$applicable], $mountPoint[$applicable]);
  820. } else {
  821. $data[$mountType] = array_merge($data[$mountType], $mountPoint);
  822. }
  823. } else {
  824. $data[$mountType] = $mountPoint;
  825. }
  826. return $data;
  827. }
  828. /**
  829. * Returns the encryption cipher
  830. */
  831. private static function getCipher() {
  832. if (!class_exists('Crypt_AES', false)) {
  833. include('Crypt/AES.php');
  834. }
  835. $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
  836. $cipher->setKey(\OC::$server->getConfig()->getSystemValue('passwordsalt', null));
  837. return $cipher;
  838. }
  839. /**
  840. * Computes a hash based on the given configuration.
  841. * This is mostly used to find out whether configurations
  842. * are the same.
  843. */
  844. public static function makeConfigHash($config) {
  845. $data = json_encode(
  846. array(
  847. 'c' => $config['class'],
  848. 'm' => $config['mountpoint'],
  849. 'o' => $config['options'],
  850. 'p' => isset($config['priority']) ? $config['priority'] : -1,
  851. 'mo' => isset($config['mountOptions']) ? $config['mountOptions'] : [],
  852. )
  853. );
  854. return hash('md5', $data);
  855. }
  856. /**
  857. * Add storage id to the storage configurations that did not have any.
  858. *
  859. * @param string $user user for which to process storage configs
  860. */
  861. private static function addStorageIdToConfig($user) {
  862. $config = self::readData($user);
  863. $needUpdate = false;
  864. foreach ($config as &$applicables) {
  865. foreach ($applicables as &$mountPoints) {
  866. foreach ($mountPoints as &$options) {
  867. $needUpdate |= !isset($options['storage_id']);
  868. }
  869. }
  870. }
  871. if ($needUpdate) {
  872. self::writeData($user, $config);
  873. }
  874. }
  875. /**
  876. * Get storage id from the numeric storage id and set
  877. * it into the given options argument. Only do this
  878. * if there was no storage id set yet.
  879. *
  880. * This might also fail if a storage wasn't fully configured yet
  881. * and couldn't be mounted, in which case this will simply return false.
  882. *
  883. * @param array $options storage options
  884. *
  885. * @return bool true if the storage id was added, false otherwise
  886. */
  887. private static function addStorageId(&$options) {
  888. if (isset($options['storage_id'])) {
  889. return false;
  890. }
  891. $class = $options['class'];
  892. try {
  893. /** @var \OC\Files\Storage\Storage $storage */
  894. $storage = new $class($options['options']);
  895. // TODO: introduce StorageConfigException
  896. } catch (\Exception $e) {
  897. // storage might not be fully configured yet (ex: Dropbox)
  898. // note that storage instances aren't supposed to open any connections
  899. // in the constructor, so this exception is likely to be a config exception
  900. return false;
  901. }
  902. $options['storage_id'] = $storage->getCache()->getNumericStorageId();
  903. return true;
  904. }
  905. }