diff options
Diffstat (limited to 'apps')
88 files changed, 4582 insertions, 3251 deletions
diff --git a/apps/files/controller/apicontroller.php b/apps/files/controller/apicontroller.php index 0cc222d7ce9..494d97db7a6 100644 --- a/apps/files/controller/apicontroller.php +++ b/apps/files/controller/apicontroller.php @@ -78,7 +78,7 @@ class ApiController extends Controller { return new DataResponse(['message' => 'Requested size must be numeric and a positive value.'], Http::STATUS_BAD_REQUEST); } - $preview = $this->previewManager->createPreview('files/'.urldecode($file), $x, $y, true); + $preview = $this->previewManager->createPreview('files/'.$file, $x, $y, true); if ($preview->valid()) { return new DataDisplayResponse($preview->data(), Http::STATUS_OK, ['Content-Type' => 'image/png']); } else { diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 1d0493f2140..f3f137a0537 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -616,7 +616,7 @@ name: 'Delete', mime: 'all', // permission is READ because we show a hint instead if there is no permission - permissions: OC.PERMISSION_READ, + permissions: OC.PERMISSION_DELETE, icon: function() { return OC.imagePath('core', 'actions/delete'); }, diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index e00cbfa66ab..68c1f9d919f 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -1933,6 +1933,8 @@ updateSelectionSummary: function() { var summary = this._selectionSummary.summary; var canDelete; + var selection; + if (summary.totalFiles === 0 && summary.totalDirs === 0) { this.$el.find('#headerName a.name>span:first').text(t('files','Name')); this.$el.find('#headerSize a>span:first').text(t('files','Size')); @@ -1944,16 +1946,22 @@ canDelete = (this.getDirectoryPermissions() & OC.PERMISSION_DELETE) && this.isSelectedDeletable(); this.$el.find('.selectedActions').removeClass('hidden'); this.$el.find('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); - var selection = ''; - if (summary.totalDirs > 0) { - selection += n('files', '%n folder', '%n folders', summary.totalDirs); - if (summary.totalFiles > 0) { - selection += ' & '; - } - } - if (summary.totalFiles > 0) { - selection += n('files', '%n file', '%n files', summary.totalFiles); + + var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs); + var fileInfo = n('files', '%n file', '%n files', summary.totalFiles); + + if (summary.totalDirs > 0 && summary.totalFiles > 0) { + var selectionVars = { + dirs: directoryInfo, + files: fileInfo + }; + selection = t('files', '{dirs} and {files}', selectionVars); + } else if (summary.totalDirs > 0) { + selection = directoryInfo; + } else { + selection = fileInfo; } + this.$el.find('#headerName a.name>span:first').text(selection); this.$el.find('#modified a>span:first').text(''); this.$el.find('table').addClass('multiselect'); diff --git a/apps/files/js/files.js b/apps/files/js/files.js index f70055d0f52..245648a79e2 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -246,12 +246,12 @@ // Use jquery-visibility to de-/re-activate file stats sync if ($.support.pageVisibility) { $(document).on({ - 'show.visibility': function() { + 'show': function() { if (!updateStorageStatisticsIntervalId) { updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval); } }, - 'hide.visibility': function() { + 'hide': function() { clearInterval(updateStorageStatisticsIntervalId); updateStorageStatisticsIntervalId = 0; } diff --git a/apps/files/js/jquery-visibility.js b/apps/files/js/jquery-visibility.js index 18f57d1f2bd..4d65e7771f9 100644 --- a/apps/files/js/jquery-visibility.js +++ b/apps/files/js/jquery-visibility.js @@ -1,31 +1,88 @@ -/*! http://mths.be/visibility v1.0.5 by @mathias */ -(function (window, document, $, undefined) { +/*! + * jquery-visibility v1.0.11 + * Page visibility shim for jQuery. + * + * Project Website: http://mths.be/visibility + * + * @version 1.0.11 + * @license MIT. + * @author Mathias Bynens - @mathias + * @author Jan Paepke - @janpaepke + */ +;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], function ($) { + return factory(root, $); + }); + } else if (typeof exports === 'object') { + // Node/CommonJS + module.exports = factory(root, require('jquery')); + } else { + // Browser globals + factory(root, jQuery); + } +}(this, function(window, $, undefined) { + "use strict"; - var prefix, - property, - // In Opera, `'onfocusin' in document == true`, hence the extra `hasFocus` check to detect IE-like behavior - eventName = 'onfocusin' in document && 'hasFocus' in document ? 'focusin focusout' : 'focus blur', - prefixes = ['', 'moz', 'ms', 'o', 'webkit'], - $support = $.support, - $event = $.event; + var + document = window.document, + property, // property name of document, that stores page visibility + vendorPrefixes = ['webkit', 'o', 'ms', 'moz', ''], + $support = $.support || {}, + // In Opera, `'onfocusin' in document == true`, hence the extra `hasFocus` check to detect IE-like behavior + eventName = 'onfocusin' in document && 'hasFocus' in document ? + 'focusin focusout' : + 'focus blur'; - while ((property = prefix = prefixes.pop()) != undefined) { - property = (prefix ? prefix + 'H' : 'h') + 'idden'; - if ($support.pageVisibility = typeof document[property] == 'boolean') { + var prefix; + while ((prefix = vendorPrefixes.pop()) !== undefined) { + property = (prefix ? prefix + 'H': 'h') + 'idden'; + $support.pageVisibility = document[property] !== undefined; + if ($support.pageVisibility) { eventName = prefix + 'visibilitychange'; break; } } - $(/blur$/.test(eventName) ? window : document).on(eventName, function (event) { - var type = event.type, - originalEvent = event.originalEvent; - // If it’s a `{focusin,focusout}` event (IE), `fromElement` and `toElement` should both be `null` or `undefined`; - // else, the page visibility hasn’t changed, but the user just clicked somewhere in the doc. - // In IE9, we need to check the `relatedTarget` property instead. - if (!/^focus./.test(type) || originalEvent == undefined || (originalEvent.toElement == undefined && originalEvent.fromElement == undefined && originalEvent.relatedTarget == undefined)) { - $event.trigger((property && document[property] || /^(?:blur|focusout)$/.test(type) ? 'hide' : 'show') + '.visibility'); + // normalize to and update document hidden property + function updateState() { + if (property !== 'hidden') { + document.hidden = $support.pageVisibility ? document[property] : undefined; } - }); + } + updateState(); -}(this, document, jQuery)); + $(/blur$/.test(eventName) ? window : document).on(eventName, function(event) { + var type = event.type; + var originalEvent = event.originalEvent; + + // Avoid errors from triggered native events for which `originalEvent` is + // not available. + if (!originalEvent) { + return; + } + + var toElement = originalEvent.toElement; + + // If it’s a `{focusin,focusout}` event (IE), `fromElement` and `toElement` + // should both be `null` or `undefined`; else, the page visibility hasn’t + // changed, but the user just clicked somewhere in the doc. In IE9, we need + // to check the `relatedTarget` property instead. + if ( + !/^focus./.test(type) || ( + toElement === undefined && + originalEvent.fromElement === undefined && + originalEvent.relatedTarget === undefined + ) + ) { + $(document).triggerHandler( + property && document[property] || /^(?:blur|focusout)$/.test(type) ? + 'hide' : + 'show' + ); + } + // and update the current state + updateState(); + }); +})); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 38073389382..7ed60084fa9 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -1606,7 +1606,7 @@ describe('OCA.Files.FileList tests', function() { fileList.findFileEl('One.txt').find('input:checkbox').click(); fileList.findFileEl('Three.pdf').find('input:checkbox').click(); fileList.findFileEl('somedir').find('input:checkbox').click(); - expect($summary.text()).toEqual('1 folder & 2 files'); + expect($summary.text()).toEqual('1 folder and 2 files'); }); it('Unselecting files hides selection summary', function() { var $summary = $('#headerName a.name>span:first'); diff --git a/apps/files_external/3rdparty/.gitignore b/apps/files_external/3rdparty/.gitignore index a9a7266a624..c8d4e6eed0b 100644 --- a/apps/files_external/3rdparty/.gitignore +++ b/apps/files_external/3rdparty/.gitignore @@ -1 +1,4 @@ example.php +icewind/smb/tests +icewind/smb/install_libsmbclient.sh +icewind/smb/.travis.yml diff --git a/apps/files_external/3rdparty/composer.json b/apps/files_external/3rdparty/composer.json index 9680d92e548..ad007df23de 100644 --- a/apps/files_external/3rdparty/composer.json +++ b/apps/files_external/3rdparty/composer.json @@ -6,7 +6,7 @@ "vendor-dir": "." }, "require": { - "icewind/smb": "1.0.1", + "icewind/smb": "1.0.4", "icewind/streams": "0.2" } } diff --git a/apps/files_external/3rdparty/composer.lock b/apps/files_external/3rdparty/composer.lock index 84fbb046d5a..033a5a17a13 100644 --- a/apps/files_external/3rdparty/composer.lock +++ b/apps/files_external/3rdparty/composer.lock @@ -1,23 +1,23 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "7b46d64e33feb600c5f0ec830b211e6f", + "hash": "5c612406bc1235075305b09a5d6996a9", "packages": [ { "name": "icewind/smb", - "version": "v1.0.1", + "version": "v1.0.4", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "8041bc1960bf2da94e60b88b34e5c78300eac476" + "reference": "9277bd20262a01b38a33cc7356e98055f2262d32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/8041bc1960bf2da94e60b88b34e5c78300eac476", - "reference": "8041bc1960bf2da94e60b88b34e5c78300eac476", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/9277bd20262a01b38a33cc7356e98055f2262d32", + "reference": "9277bd20262a01b38a33cc7356e98055f2262d32", "shasum": "" }, "require": { @@ -45,7 +45,7 @@ } ], "description": "php wrapper for smbclient and libsmbclient-php", - "time": "2015-04-20 11:16:24" + "time": "2015-08-17 14:20:38" }, { "name": "icewind/streams", diff --git a/apps/files_external/3rdparty/composer/installed.json b/apps/files_external/3rdparty/composer/installed.json index 42e8fdd29db..89c8ed56529 100644 --- a/apps/files_external/3rdparty/composer/installed.json +++ b/apps/files_external/3rdparty/composer/installed.json @@ -43,17 +43,17 @@ }, { "name": "icewind/smb", - "version": "v1.0.1", - "version_normalized": "1.0.1.0", + "version": "v1.0.4", + "version_normalized": "1.0.4.0", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "8041bc1960bf2da94e60b88b34e5c78300eac476" + "reference": "9277bd20262a01b38a33cc7356e98055f2262d32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/8041bc1960bf2da94e60b88b34e5c78300eac476", - "reference": "8041bc1960bf2da94e60b88b34e5c78300eac476", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/9277bd20262a01b38a33cc7356e98055f2262d32", + "reference": "9277bd20262a01b38a33cc7356e98055f2262d32", "shasum": "" }, "require": { @@ -63,7 +63,7 @@ "require-dev": { "satooshi/php-coveralls": "dev-master" }, - "time": "2015-04-20 11:16:24", + "time": "2015-08-17 14:20:38", "type": "library", "installation-source": "source", "autoload": { diff --git a/apps/files_external/3rdparty/icewind/smb/.travis.yml b/apps/files_external/3rdparty/icewind/smb/.travis.yml deleted file mode 100644 index c1ac3727d08..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/.travis.yml +++ /dev/null @@ -1,50 +0,0 @@ -language: php -php: - - 5.3 - - 5.4 - - 5.5 - -env: - global: - - CURRENT_DIR=`pwd` - -before_install: - - pass=$(perl -e 'print crypt("test", "password")') - - sudo useradd -m -p $pass test - - sudo apt-get update -qq - - sudo apt-get install samba smbclient libsmbclient-dev libsmbclient - - wget -O /tmp/libsmbclient-php.zip https://github.com/eduardok/libsmbclient-php/archive/master.zip - - unzip /tmp/libsmbclient-php.zip -d /tmp - - cd /tmp/libsmbclient-php-master - - phpize && ./configure && make && sudo make install - - echo 'extension="libsmbclient.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - - cd $CURRENT_DIR - - chmod go+w $HOME - - printf "%s\n%s\n" test test|sudo smbpasswd -s test - - sudo mkdir /home/test/test - - sudo chown test /home/test/test - - | - echo "[test] - comment = test - path = /home/test - guest ok = yes - writeable = yes - map archive = yes - map system = yes - map hidden = yes - create mask = 0777 - inherit permissions = yes" | sudo tee -a /etc/samba/smb.conf - - sudo service smbd restart - - testparm -s - -install: - - composer install --dev --no-interaction - -script: - - mkdir -p build/logs - - cd tests - - phpunit --coverage-clover ../build/logs/clover.xml --configuration phpunit.xml - -after_script: - - cd $CURRENT_DIR - - php vendor/bin/coveralls -v diff --git a/apps/files_external/3rdparty/icewind/smb/src/AbstractShare.php b/apps/files_external/3rdparty/icewind/smb/src/AbstractShare.php new file mode 100644 index 00000000000..a5cfe59a3c4 --- /dev/null +++ b/apps/files_external/3rdparty/icewind/smb/src/AbstractShare.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Licensed under the MIT license: + * http://opensource.org/licenses/MIT + */ + +namespace Icewind\SMB; + +use Icewind\SMB\Exception\InvalidPathException; + +abstract class AbstractShare implements IShare { + private $forbiddenCharacters; + + public function __construct() { + $this->forbiddenCharacters = array('?', '<', '>', ':', '*', '|', '"', chr(0), "\n", "\r"); + } + + protected function verifyPath($path) { + foreach ($this->forbiddenCharacters as $char) { + if (strpos($path, $char) !== false) { + throw new InvalidPathException('Invalid path, "' . $char . '" is not allowed'); + } + } + } +} diff --git a/apps/files_external/3rdparty/icewind/smb/src/Connection.php b/apps/files_external/3rdparty/icewind/smb/src/Connection.php index 6191b11ac8e..c857398c327 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Connection.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Connection.php @@ -8,8 +8,10 @@ namespace Icewind\SMB; use Icewind\SMB\Exception\AuthenticationException; +use Icewind\SMB\Exception\ConnectException; use Icewind\SMB\Exception\ConnectionException; use Icewind\SMB\Exception\InvalidHostException; +use Icewind\SMB\Exception\NoLoginServerException; class Connection extends RawConnection { const DELIMITER = 'smb:'; @@ -26,18 +28,25 @@ class Connection extends RawConnection { /** * get all unprocessed output from smbclient until the next prompt * - * @throws ConnectionException * @return string + * @throws AuthenticationException + * @throws ConnectException + * @throws ConnectionException + * @throws InvalidHostException + * @throws NoLoginServerException */ public function read() { if (!$this->isValid()) { - throw new ConnectionException(); + throw new ConnectionException('Connection not valid'); } $line = $this->readLine(); //first line is prompt $this->checkConnectionError($line); $output = array(); $line = $this->readLine(); + if ($line === false) { + throw new ConnectException('Unknown error'); + } $length = mb_strlen(self::DELIMITER); while (mb_substr($line, 0, $length) !== self::DELIMITER) { //next prompt functions as delimiter $output[] .= $line; @@ -52,20 +61,24 @@ class Connection extends RawConnection { * @param $line * @throws AuthenticationException * @throws InvalidHostException + * @throws NoLoginServerException */ private function checkConnectionError($line) { $line = rtrim($line, ')'); if (substr($line, -23) === ErrorCodes::LogonFailure) { - throw new AuthenticationException(); + throw new AuthenticationException('Invalid login'); } if (substr($line, -26) === ErrorCodes::BadHostName) { - throw new InvalidHostException(); + throw new InvalidHostException('Invalid hostname'); } if (substr($line, -22) === ErrorCodes::Unsuccessful) { - throw new InvalidHostException(); + throw new InvalidHostException('Connection unsuccessful'); } if (substr($line, -28) === ErrorCodes::ConnectionRefused) { - throw new InvalidHostException(); + throw new InvalidHostException('Connection refused'); + } + if (substr($line, -26) === ErrorCodes::NoLogonServers) { + throw new NoLoginServerException('No login server'); } } diff --git a/apps/files_external/3rdparty/icewind/smb/src/ErrorCodes.php b/apps/files_external/3rdparty/icewind/smb/src/ErrorCodes.php index d9f2507cba9..03bd574c185 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/ErrorCodes.php +++ b/apps/files_external/3rdparty/icewind/smb/src/ErrorCodes.php @@ -15,6 +15,7 @@ class ErrorCodes { const BadHostName = 'NT_STATUS_BAD_NETWORK_NAME'; const Unsuccessful = 'NT_STATUS_UNSUCCESSFUL'; const ConnectionRefused = 'NT_STATUS_CONNECTION_REFUSED'; + const NoLogonServers = 'NT_STATUS_NO_LOGON_SERVERS'; const PathNotFound = 'NT_STATUS_OBJECT_PATH_NOT_FOUND'; const NoSuchFile = 'NT_STATUS_NO_SUCH_FILE'; diff --git a/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidPathException.php b/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidPathException.php new file mode 100644 index 00000000000..4bba74f269f --- /dev/null +++ b/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidPathException.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Licensed under the MIT license: + * http://opensource.org/licenses/MIT + */ + +namespace Icewind\SMB\Exception; + +class InvalidPathException extends InvalidRequestException {} diff --git a/apps/files_external/3rdparty/icewind/smb/tests/bootstrap.php b/apps/files_external/3rdparty/icewind/smb/src/Exception/NoLoginServerException.php index dc2e34b183e..43f7f9aeff9 100644 --- a/apps/files_external/3rdparty/icewind/smb/tests/bootstrap.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Exception/NoLoginServerException.php @@ -5,5 +5,6 @@ * http://opensource.org/licenses/MIT */ -date_default_timezone_set('UTC'); -require_once __DIR__.'/../vendor/autoload.php'; +namespace Icewind\SMB\Exception; + +class NoLoginServerException extends ConnectException {} diff --git a/apps/files_external/3rdparty/icewind/smb/src/NativeServer.php b/apps/files_external/3rdparty/icewind/smb/src/NativeServer.php index 4628e3ec108..ce8e6d636a9 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/NativeServer.php +++ b/apps/files_external/3rdparty/icewind/smb/src/NativeServer.php @@ -24,12 +24,7 @@ class NativeServer extends Server { } protected function connect() { - $user = $this->getUser(); - $workgroup = null; - if (strpos($user, '/')) { - list($workgroup, $user) = explode($user, '/'); - } - $this->state->init($workgroup, $user, $this->getPassword()); + $this->state->init($this->getWorkgroup(), $this->getUser(), $this->getPassword()); } /** diff --git a/apps/files_external/3rdparty/icewind/smb/src/NativeShare.php b/apps/files_external/3rdparty/icewind/smb/src/NativeShare.php index c84e9611667..1f1d225c00a 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/NativeShare.php +++ b/apps/files_external/3rdparty/icewind/smb/src/NativeShare.php @@ -7,7 +7,7 @@ namespace Icewind\SMB; -class NativeShare implements IShare { +class NativeShare extends AbstractShare { /** * @var Server $server */ @@ -28,6 +28,7 @@ class NativeShare implements IShare { * @param string $name */ public function __construct($server, $name) { + parent::__construct(); $this->server = $server; $this->name = $name; $this->state = new NativeState(); @@ -43,15 +44,7 @@ class NativeShare implements IShare { return; } - $user = $this->server->getUser(); - if (strpos($user, '/')) { - list($workgroup, $user) = explode('/', $user); - } elseif (strpos($user, '\\')) { - list($workgroup, $user) = explode('\\', $user); - } else { - $workgroup = null; - } - $this->state->init($workgroup, $user, $this->server->getPassword()); + $this->state->init($this->server->getWorkgroup(), $this->server->getUser(), $this->server->getPassword()); } /** @@ -64,6 +57,7 @@ class NativeShare implements IShare { } private function buildUrl($path) { + $this->verifyPath($path); $url = sprintf('smb://%s/%s', $this->server->getHost(), $this->name); if ($path) { $path = trim($path, '/'); @@ -149,6 +143,7 @@ class NativeShare implements IShare { * @throws \Icewind\SMB\Exception\InvalidTypeException */ public function del($path) { + $this->connect(); return $this->state->unlink($this->buildUrl($path)); } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Server.php b/apps/files_external/3rdparty/icewind/smb/src/Server.php index f7227d4baef..bbc0475d5e4 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Server.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Server.php @@ -30,6 +30,11 @@ class Server { protected $password; /** + * @var string $workgroup + */ + protected $workgroup; + + /** * Check if the smbclient php extension is available * * @return bool @@ -45,11 +50,29 @@ class Server { */ public function __construct($host, $user, $password) { $this->host = $host; + list($workgroup, $user) = $this->splitUser($user); $this->user = $user; + $this->workgroup = $workgroup; $this->password = $password; } /** + * Split workgroup from username + * + * @param $user + * @return string[] [$workgroup, $user] + */ + public function splitUser($user) { + if (strpos($user, '/')) { + return explode('/', $user, 2); + } elseif (strpos($user, '\\')) { + return explode('\\', $user); + } else { + return array(null, $user); + } + } + + /** * @return string */ public function getAuthString() { @@ -78,13 +101,21 @@ class Server { } /** + * @return string + */ + public function getWorkgroup() { + return $this->workgroup; + } + + /** * @return \Icewind\SMB\IShare[] * * @throws \Icewind\SMB\Exception\AuthenticationException * @throws \Icewind\SMB\Exception\InvalidHostException */ public function listShares() { - $command = Server::CLIENT . ' --authentication-file=/proc/self/fd/3' . + $workgroupArgument = ($this->workgroup) ? ' -W ' . escapeshellarg($this->workgroup) : ''; + $command = Server::CLIENT . $workgroupArgument . ' --authentication-file=/proc/self/fd/3' . ' -gL ' . escapeshellarg($this->getHost()); $connection = new RawConnection($command); $connection->writeAuthentication($this->getUser(), $this->getPassword()); diff --git a/apps/files_external/3rdparty/icewind/smb/src/Share.php b/apps/files_external/3rdparty/icewind/smb/src/Share.php index 7c24f9f2e90..d9ab729025d 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Share.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Share.php @@ -7,17 +7,13 @@ namespace Icewind\SMB; -use Icewind\SMB\Exception\AccessDeniedException; -use Icewind\SMB\Exception\AlreadyExistsException; use Icewind\SMB\Exception\ConnectionException; -use Icewind\SMB\Exception\Exception; use Icewind\SMB\Exception\FileInUseException; use Icewind\SMB\Exception\InvalidTypeException; -use Icewind\SMB\Exception\NotEmptyException; use Icewind\SMB\Exception\NotFoundException; use Icewind\Streams\CallbackWrapper; -class Share implements IShare { +class Share extends AbstractShare { /** * @var Server $server */ @@ -43,6 +39,7 @@ class Share implements IShare { * @param string $name */ public function __construct($server, $name) { + parent::__construct(); $this->server = $server; $this->name = $name; $this->parser = new Parser(new TimeZoneProvider($this->server->getHost())); @@ -57,10 +54,11 @@ class Share implements IShare { if ($this->connection and $this->connection->isValid()) { return; } - $command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s', + $workgroupArgument = ($this->server->getWorkgroup()) ? ' -W ' . escapeshellarg($this->server->getWorkgroup()) : ''; + $command = sprintf('%s %s --authentication-file=/proc/self/fd/3 %s', Server::CLIENT, - $this->server->getHost(), - $this->name + $workgroupArgument, + escapeshellarg('//' . $this->server->getHost() . '/' . $this->name) ); $this->connection = new Connection($command); $this->connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); @@ -256,18 +254,18 @@ class Share implements IShare { */ public function read($source) { $source = $this->escapePath($source); - // close the single quote, open a double quote where we put the single quote... - $source = str_replace('\'', '\'"\'"\'', $source); // since returned stream is closed by the caller we need to create a new instance // since we can't re-use the same file descriptor over multiple calls - $command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s -c \'get %s /proc/self/fd/5\'', + $workgroupArgument = ($this->server->getWorkgroup()) ? ' -W ' . escapeshellarg($this->server->getWorkgroup()) : ''; + $command = sprintf('%s %s --authentication-file=/proc/self/fd/3 %s', Server::CLIENT, - $this->server->getHost(), - $this->name, - $source + $workgroupArgument, + escapeshellarg('//' . $this->server->getHost() . '/' . $this->name) ); $connection = new Connection($command); $connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); + $connection->write('get ' . $source . ' /proc/self/fd/5'); + $connection->write('exit'); $fh = $connection->getFileOutputStream(); stream_context_set_option($fh, 'file', 'connection', $connection); return $fh; @@ -284,23 +282,24 @@ class Share implements IShare { */ public function write($target) { $target = $this->escapePath($target); - // close the single quote, open a double quote where we put the single quote... - $target = str_replace('\'', '\'"\'"\'', $target); // since returned stream is closed by the caller we need to create a new instance // since we can't re-use the same file descriptor over multiple calls - $command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s -c \'put /proc/self/fd/4 %s\'', + $workgroupArgument = ($this->server->getWorkgroup()) ? ' -W ' . escapeshellarg($this->server->getWorkgroup()) : ''; + $command = sprintf('%s %s --authentication-file=/proc/self/fd/3 %s', Server::CLIENT, - $this->server->getHost(), - $this->name, - $target + $workgroupArgument, + escapeshellarg('//' . $this->server->getHost() . '/' . $this->name) ); - $connection = new RawConnection($command); + $connection = new Connection($command); $connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); $fh = $connection->getFileInputStream(); + $connection->write('put /proc/self/fd/4 ' . $target); + $connection->write('exit'); + // use a close callback to ensure the upload is finished before continuing // this also serves as a way to keep the connection in scope - return CallbackWrapper::wrap($fh, null, null, function () use ($connection) { + return CallbackWrapper::wrap($fh, null, null, function () use ($connection, $target) { $connection->close(false); // dont terminate, give the upload some time }); } @@ -378,6 +377,7 @@ class Share implements IShare { * @return string */ protected function escapePath($path) { + $this->verifyPath($path); if ($path === '/') { $path = ''; } diff --git a/apps/files_external/3rdparty/icewind/smb/tests/AbstractShare.php b/apps/files_external/3rdparty/icewind/smb/tests/AbstractShare.php deleted file mode 100644 index f8ccb7119ed..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/tests/AbstractShare.php +++ /dev/null @@ -1,539 +0,0 @@ -<?php -/** - * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> - * This file is licensed under the Licensed under the MIT license: - * http://opensource.org/licenses/MIT - */ - -namespace Icewind\SMB\Test; - -use Icewind\SMB\FileInfo; - -abstract class AbstractShare extends \PHPUnit_Framework_TestCase { - /** - * @var \Icewind\SMB\Server $server - */ - protected $server; - - /** - * @var \Icewind\SMB\IShare $share - */ - protected $share; - - /** - * @var string $root - */ - protected $root; - - protected $config; - - public function tearDown() { - try { - if ($this->share) { - $this->cleanDir($this->root); - } - unset($this->share); - } catch (\Exception $e) { - unset($this->share); - throw $e; - } - } - - public function nameProvider() { - // / ? < > \ : * | " are illegal characters in path on windows - return array( - array('simple'), - array('with spaces_and-underscores'), - array("single'quote'"), - array('日本語'), - array('url %2F +encode'), - array('a somewhat longer filename than the other with more charaters as the all the other filenames'), - array('$as#d€££Ö€ßœĚęĘĞĜΣΥΦΩΫ') - ); - } - - public function fileDataProvider() { - return array( - array('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'), - array('Mixed language, 日本語 が わからか and Various _/* characters \\|” €') - ); - } - - public function nameAndDataProvider() { - $names = $this->nameProvider(); - $data = $this->fileDataProvider(); - $result = array(); - foreach ($names as $name) { - foreach ($data as $text) { - $result[] = array($name[0], $text[0]); - } - } - return $result; - } - - public function cleanDir($dir) { - $content = $this->share->dir($dir); - foreach ($content as $metadata) { - if ($metadata->isDirectory()) { - $this->cleanDir($metadata->getPath()); - } else { - $this->share->del($metadata->getPath()); - } - } - $this->share->rmdir($dir); - } - - private function getTextFile($text = '') { - if (!$text) { - $text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'; - } - $file = tempnam('/tmp', 'smb_test_'); - file_put_contents($file, $text); - return $file; - } - - public function testListShares() { - $shares = $this->server->listShares(); - foreach ($shares as $share) { - if ($share->getName() === $this->config->share) { - return; - } - } - $this->fail('Share "' . $this->config->share . '" not found'); - } - - public function testRootStartsEmpty() { - $this->assertEquals(array(), $this->share->dir($this->root)); - } - - /** - * @dataProvider nameProvider - */ - public function testMkdir($name) { - $this->share->mkdir($this->root . '/' . $name); - $dirs = $this->share->dir($this->root); - $this->assertCount(1, $dirs); - $this->assertEquals($name, $dirs[0]->getName()); - $this->assertTrue($dirs[0]->isDirectory()); - } - - /** - * @dataProvider nameProvider - */ - public function testRenameDirectory($name) { - $this->share->mkdir($this->root . '/' . $name); - $this->share->rename($this->root . '/' . $name, $this->root . '/' . $name . '_rename'); - $dirs = $this->share->dir($this->root); - $this->assertEquals(1, count($dirs)); - $this->assertEquals($name . '_rename', $dirs[0]->getName()); - } - - /** - * @dataProvider nameProvider - */ - public function testRmdir($name) { - $this->share->mkdir($this->root . '/' . $name); - $this->share->rmdir($this->root . '/' . $name); - $this->assertCount(0, $this->share->dir($this->root)); - } - - /** - * @dataProvider nameAndDataProvider - */ - public function testPut($name, $text) { - $tmpFile = $this->getTextFile($text); - $size = filesize($tmpFile); - - $this->share->put($tmpFile, $this->root . '/' . $name); - unlink($tmpFile); - - $files = $this->share->dir($this->root); - $this->assertCount(1, $files); - $this->assertEquals($name, $files[0]->getName()); - $this->assertEquals($size, $files[0]->getSize()); - $this->assertFalse($files[0]->isDirectory()); - } - - /** - * @dataProvider nameProvider - */ - public function testRenameFile($name) { - $tmpFile = $this->getTextFile(); - - $this->share->put($tmpFile, $this->root . '/' . $name); - unlink($tmpFile); - - $this->share->rename($this->root . '/' . $name, $this->root . '/' . $name . '_renamed'); - - $files = $this->share->dir($this->root); - $this->assertEquals(1, count($files)); - $this->assertEquals($name . '_renamed', $files[0]->getName()); - } - - /** - * @dataProvider nameAndDataProvider - */ - public function testGet($name, $text) { - $tmpFile = $this->getTextFile($text); - - $this->share->put($tmpFile, $this->root . '/' . $name); - unlink($tmpFile); - - $targetFile = tempnam('/tmp', 'smb_test_'); - $this->share->get($this->root . '/' . $name, $targetFile); - - $this->assertEquals($text, file_get_contents($targetFile)); - unlink($targetFile); - } - - /** - * @dataProvider nameProvider - */ - public function testDel($name) { - $tmpFile = $this->getTextFile(); - - $this->share->put($tmpFile, $this->root . '/' . $name); - unlink($tmpFile); - - $this->share->del($this->root . '/' . $name); - $this->assertCount(0, $this->share->dir($this->root)); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testCreateFolderInNonExistingFolder() { - $this->share->mkdir($this->root . '/foo/bar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testRemoveFolderInNonExistingFolder() { - $this->share->rmdir($this->root . '/foo/bar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testRemoveNonExistingFolder() { - $this->share->rmdir($this->root . '/foo'); - } - - /** - * @expectedException \Icewind\SMB\Exception\AlreadyExistsException - */ - public function testCreateExistingFolder() { - $this->share->mkdir($this->root . '/bar'); - $this->share->mkdir($this->root . '/bar'); - $this->share->rmdir($this->root . '/bar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\InvalidTypeException - */ - public function testCreateFileExistingFolder() { - $this->share->mkdir($this->root . '/bar'); - $this->share->put($this->getTextFile(), $this->root . '/bar'); - $this->share->rmdir($this->root . '/bar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testCreateFileInNonExistingFolder() { - $this->share->put($this->getTextFile(), $this->root . '/foo/bar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testTestRemoveNonExistingFile() { - $this->share->del($this->root . '/foo'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testDownloadNonExistingFile() { - $this->share->get($this->root . '/foo', '/dev/null'); - } - - /** - * @expectedException \Icewind\SMB\Exception\InvalidTypeException - */ - public function testDownloadFolder() { - $this->share->mkdir($this->root . '/foobar'); - $this->share->get($this->root . '/foobar', '/dev/null'); - $this->share->rmdir($this->root . '/foobar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\InvalidTypeException - */ - public function testDelFolder() { - $this->share->mkdir($this->root . '/foobar'); - $this->share->del($this->root . '/foobar'); - $this->share->rmdir($this->root . '/foobar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\InvalidTypeException - */ - public function testRmdirFile() { - $this->share->put($this->getTextFile(), $this->root . '/foobar'); - $this->share->rmdir($this->root . '/foobar'); - $this->share->del($this->root . '/foobar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotEmptyException - */ - public function testRmdirNotEmpty() { - $this->share->mkdir($this->root . '/foobar'); - $this->share->put($this->getTextFile(), $this->root . '/foobar/asd'); - $this->share->rmdir($this->root . '/foobar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testDirNonExisting() { - $this->share->dir('/foobar/asd'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testRmDirNonExisting() { - $this->share->rmdir('/foobar/asd'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testRenameNonExisting() { - $this->share->rename('/foobar/asd', '/foobar/bar'); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testRenameTargetNonExisting() { - $txt = $this->getTextFile(); - $this->share->put($txt, $this->root . '/foo.txt'); - unlink($txt); - $this->share->rename($this->root . '/foo.txt', $this->root . '/bar/foo.txt'); - } - - public function testModifiedDate() { - $now = time(); - $this->share->put($this->getTextFile(), $this->root . '/foo.txt'); - $dir = $this->share->dir($this->root); - $mtime = $dir[0]->getMTime(); - $this->assertTrue(abs($now - $mtime) <= 2, 'Modified time differs by ' . abs($now - $mtime) . ' seconds'); - $this->share->del($this->root . '/foo.txt'); - } - - /** - * @dataProvider nameAndDataProvider - */ - public function testReadStream($name, $text) { - $sourceFile = $this->getTextFile($text); - $this->share->put($sourceFile, $this->root . '/' . $name); - $fh = $this->share->read($this->root . '/' . $name); - $content = stream_get_contents($fh); - fclose($fh); - $this->share->del($this->root . '/' . $name); - - $this->assertEquals(file_get_contents($sourceFile), $content); - } - - /** - * @dataProvider nameAndDataProvider - */ - public function testWriteStream($name, $text) { - $fh = $this->share->write($this->root . '/' . $name); - fwrite($fh, $text); - fclose($fh); - - $tmpFile1 = tempnam('/tmp', 'smb_test_'); - $this->share->get($this->root . '/' . $name, $tmpFile1); - $this->assertEquals($text, file_get_contents($tmpFile1)); - $this->share->del($this->root . '/' . $name); - unlink($tmpFile1); - } - - public function testDir() { - $txtFile = $this->getTextFile(); - - $this->share->mkdir($this->root . '/dir'); - $this->share->put($txtFile, $this->root . '/file.txt'); - unlink($txtFile); - - $dir = $this->share->dir($this->root); - if ($dir[0]->getName() === 'dir') { - $dirEntry = $dir[0]; - } else { - $dirEntry = $dir[1]; - } - $this->assertTrue($dirEntry->isDirectory()); - $this->assertFalse($dirEntry->isReadOnly()); - $this->assertFalse($dirEntry->isReadOnly()); - - if ($dir[0]->getName() === 'file.txt') { - $fileEntry = $dir[0]; - } else { - $fileEntry = $dir[1]; - } - $this->assertFalse($fileEntry->isDirectory()); - $this->assertFalse($fileEntry->isReadOnly()); - $this->assertFalse($fileEntry->isReadOnly()); - } - - /** - * @dataProvider nameProvider - */ - public function testStat($name) { - $txtFile = $this->getTextFile(); - $size = filesize($txtFile); - - $this->share->put($txtFile, $this->root . '/' . $name); - unlink($txtFile); - - $info = $this->share->stat($this->root . '/' . $name); - $this->assertEquals($size, $info->getSize()); - } - - /** - * @expectedException \Icewind\SMB\Exception\NotFoundException - */ - public function testStatNonExisting() { - $this->share->stat($this->root . '/fo.txt'); - } - - /** - * note setting archive and system bit is not supported - * - * @dataProvider nameProvider - */ - public function testSetMode($name) { - $txtFile = $this->getTextFile(); - - $this->share->put($txtFile, $this->root . '/' . $name); - - $this->share->setMode($this->root . '/' . $name, FileInfo::MODE_NORMAL); - $info = $this->share->stat($this->root . '/' . $name); - $this->assertFalse($info->isReadOnly()); - $this->assertFalse($info->isArchived()); - $this->assertFalse($info->isSystem()); - $this->assertFalse($info->isHidden()); - - $this->share->setMode($this->root . '/' . $name, FileInfo::MODE_READONLY); - $info = $this->share->stat($this->root . '/' . $name); - $this->assertTrue($info->isReadOnly()); - $this->assertFalse($info->isArchived()); - $this->assertFalse($info->isSystem()); - $this->assertFalse($info->isHidden()); - - $this->share->setMode($this->root . '/' . $name, FileInfo::MODE_ARCHIVE); - $info = $this->share->stat($this->root . '/' . $name); - $this->assertFalse($info->isReadOnly()); - $this->assertTrue($info->isArchived()); - $this->assertFalse($info->isSystem()); - $this->assertFalse($info->isHidden()); - - $this->share->setMode($this->root . '/' . $name, FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE); - $info = $this->share->stat($this->root . '/' . $name); - $this->assertTrue($info->isReadOnly()); - $this->assertTrue($info->isArchived()); - $this->assertFalse($info->isSystem()); - $this->assertFalse($info->isHidden()); - - $this->share->setMode($this->root . '/' . $name, FileInfo::MODE_HIDDEN); - $info = $this->share->stat($this->root . '/' . $name); - $this->assertFalse($info->isReadOnly()); - $this->assertFalse($info->isArchived()); - $this->assertFalse($info->isSystem()); - $this->assertTrue($info->isHidden()); - - $this->share->setMode($this->root . '/' . $name, FileInfo::MODE_SYSTEM); - $info = $this->share->stat($this->root . '/' . $name); - $this->assertFalse($info->isReadOnly()); - $this->assertFalse($info->isArchived()); - $this->assertTrue($info->isSystem()); - $this->assertFalse($info->isHidden()); - - $this->share->setMode($this->root . '/' . $name, FileInfo::MODE_NORMAL); - $info = $this->share->stat($this->root . '/' . $name); - $this->assertFalse($info->isReadOnly()); - $this->assertFalse($info->isArchived()); - $this->assertFalse($info->isSystem()); - $this->assertFalse($info->isHidden()); - } - - public function pathProvider() { - // / ? < > \ : * | " are illegal characters in path on windows - return array( - array('dir/sub/foo.txt'), - array('bar.txt'), - array("single'quote'/sub/foo.txt"), - array('日本語/url %2F +encode/asd.txt'), - array( - 'a somewhat longer folder than the other with more charaters as the all the other filenames/' . - 'followed by a somewhat long file name after that.txt' - ) - ); - } - - /** - * @dataProvider pathProvider - */ - public function testSubDirs($path) { - $dirs = explode('/', $path); - $name = array_pop($dirs); - $fullPath = ''; - foreach ($dirs as $dir) { - $fullPath .= '/' . $dir; - $this->share->mkdir($this->root . $fullPath); - } - $txtFile = $this->getTextFile(); - $size = filesize($txtFile); - $this->share->put($txtFile, $this->root . $fullPath . '/' . $name); - unlink($txtFile); - $info = $this->share->stat($this->root . $fullPath . '/' . $name); - $this->assertEquals($size, $info->getSize()); - $this->assertFalse($info->isHidden()); - } - - public function testDelAfterStat() { - $name = 'foo.txt'; - $txtFile = $this->getTextFile(); - - $this->share->put($txtFile, $this->root . '/' . $name); - unlink($txtFile); - - $this->share->stat($this->root . '/' . $name); - $this->share->del($this->root . '/foo.txt'); - } - - /** - * @param $name - * @dataProvider nameProvider - */ - public function testDirPaths($name) { - $txtFile = $this->getTextFile(); - $this->share->mkdir($this->root . '/' . $name); - $this->share->put($txtFile, $this->root . '/' . $name . '/' . $name); - unlink($txtFile); - - $content = $this->share->dir($this->root . '/' . $name); - $this->assertCount(1, $content); - $this->assertEquals($name, $content[0]->getName()); - } - - public function testStatRoot() { - $info = $this->share->stat('/'); - $this->assertInstanceOf('\Icewind\SMB\IFileInfo', $info); - } -} diff --git a/apps/files_external/3rdparty/icewind/smb/tests/NativeShare.php b/apps/files_external/3rdparty/icewind/smb/tests/NativeShare.php deleted file mode 100644 index d8e10235c12..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/tests/NativeShare.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php -/** - * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> - * This file is licensed under the Licensed under the MIT license: - * http://opensource.org/licenses/MIT - */ - -namespace Icewind\SMB\Test; - -use Icewind\SMB\NativeServer; - -class NativeShare extends AbstractShare { - public function setUp() { - if (!function_exists('smbclient_state_new')) { - $this->markTestSkipped('libsmbclient php extension not installed'); - } - $this->config = json_decode(file_get_contents(__DIR__ . '/config.json')); - $this->server = new NativeServer($this->config->host, $this->config->user, $this->config->password); - $this->share = $this->server->getShare($this->config->share); - if ($this->config->root) { - $this->root = '/' . $this->config->root . '/' . uniqid(); - } else { - $this->root = '/' . uniqid(); - } - $this->share->mkdir($this->root); - } -} diff --git a/apps/files_external/3rdparty/icewind/smb/tests/NativeStream.php b/apps/files_external/3rdparty/icewind/smb/tests/NativeStream.php deleted file mode 100644 index 2d7b62fedeb..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/tests/NativeStream.php +++ /dev/null @@ -1,143 +0,0 @@ -<?php -/** - * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> - * This file is licensed under the Licensed under the MIT license: - * http://opensource.org/licenses/MIT - */ - -namespace Icewind\SMB\Test; - -use Icewind\SMB\NativeServer; - -class NativeStream extends \PHPUnit_Framework_TestCase { - /** - * @var \Icewind\SMB\Server $server - */ - protected $server; - - /** - * @var \Icewind\SMB\NativeShare $share - */ - protected $share; - - /** - * @var string $root - */ - protected $root; - - protected $config; - - public function setUp() { - if (!function_exists('smbclient_state_new')) { - $this->markTestSkipped('libsmbclient php extension not installed'); - } - $this->config = json_decode(file_get_contents(__DIR__ . '/config.json')); - $this->server = new NativeServer($this->config->host, $this->config->user, $this->config->password); - $this->share = $this->server->getShare($this->config->share); - if ($this->config->root) { - $this->root = '/' . $this->config->root . '/' . uniqid(); - } else { - $this->root = '/' . uniqid(); - } - $this->share->mkdir($this->root); - } - - private function getTextFile() { - $text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'; - $file = tempnam('/tmp', 'smb_test_'); - file_put_contents($file, $text); - return $file; - } - - public function testSeekTell() { - $sourceFile = $this->getTextFile(); - $this->share->put($sourceFile, $this->root . '/foobar'); - $fh = $this->share->read($this->root . '/foobar'); - $content = fread($fh, 3); - $this->assertEquals('Lor', $content); - - fseek($fh, -2, SEEK_CUR); - - $content = fread($fh, 3); - $this->assertEquals('ore', $content); - - fseek($fh, 3, SEEK_SET); - - $content = fread($fh, 3); - $this->assertEquals('em ', $content); - - fseek($fh, -3, SEEK_END); - - $content = fread($fh, 3); - $this->assertEquals('qua', $content); - - fseek($fh, -3, SEEK_END); - $this->assertEquals(120, ftell($fh)); - } - - public function testStat() { - $sourceFile = $this->getTextFile(); - $this->share->put($sourceFile, $this->root . '/foobar'); - $fh = $this->share->read($this->root . '/foobar'); - $stat = fstat($fh); - $this->assertEquals(filesize($sourceFile), $stat['size']); - unlink($sourceFile); - } - - public function testTruncate() { - if (version_compare(phpversion(), '5.4.0', '<')) { - $this->markTestSkipped('php <5.4 doesn\'t support truncate for stream wrappers'); - } - $fh = $this->share->write($this->root . '/foobar'); - fwrite($fh, 'foobar'); - ftruncate($fh, 3); - fclose($fh); - - $fh = $this->share->read($this->root . '/foobar'); - $this->assertEquals('foo', stream_get_contents($fh)); - } - - public function testEOF() { - if (version_compare(phpversion(), '5.4.0', '<')) { - $this->markTestSkipped('php <5.4 doesn\'t support truncate for stream wrappers'); - } - $fh = $this->share->write($this->root . '/foobar'); - fwrite($fh, 'foobar'); - fclose($fh); - - $fh = $this->share->read($this->root . '/foobar'); - fread($fh, 3); - $this->assertFalse(feof($fh)); - fread($fh, 5); - $this->assertTrue(feof($fh)); - } - - public function testLockUnsupported() { - $fh = $this->share->write($this->root . '/foobar'); - $this->assertFalse(flock($fh, LOCK_SH)); - } - - public function testSetOptionUnsupported() { - $fh = $this->share->write($this->root . '/foobar'); - $this->assertFalse(stream_set_blocking($fh, false)); - } - - public function tearDown() { - if ($this->share) { - $this->cleanDir($this->root); - } - unset($this->share); - } - - public function cleanDir($dir) { - $content = $this->share->dir($dir); - foreach ($content as $metadata) { - if ($metadata->isDirectory()) { - $this->cleanDir($metadata->getPath()); - } else { - $this->share->del($metadata->getPath()); - } - } - $this->share->rmdir($dir); - } -} diff --git a/apps/files_external/3rdparty/icewind/smb/tests/Parser.php b/apps/files_external/3rdparty/icewind/smb/tests/Parser.php deleted file mode 100644 index 5caa048a664..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/tests/Parser.php +++ /dev/null @@ -1,103 +0,0 @@ -<?php -/** - * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> - * This file is licensed under the Licensed under the MIT license: - * http://opensource.org/licenses/MIT - */ - -namespace Icewind\SMB\Test; - - -use Icewind\SMB\FileInfo; - -class Parser extends \PHPUnit_Framework_TestCase { - public function modeProvider() { - return array( - array('D', FileInfo::MODE_DIRECTORY), - array('A', FileInfo::MODE_ARCHIVE), - array('S', FileInfo::MODE_SYSTEM), - array('H', FileInfo::MODE_HIDDEN), - array('R', FileInfo::MODE_READONLY), - array('N', FileInfo::MODE_NORMAL), - array('RA', FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE), - array('RAH', FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE | FileInfo::MODE_HIDDEN) - ); - } - - /** - * @param string $timeZone - * @return \Icewind\SMB\TimeZoneProvider - */ - private function getTimeZoneProvider($timeZone) { - $mock = $this->getMockBuilder('\Icewind\SMB\TimeZoneProvider') - ->disableOriginalConstructor() - ->getMock(); - $mock->expects($this->any()) - ->method('get') - ->will($this->returnValue($timeZone)); - return $mock; - } - - /** - * @dataProvider modeProvider - */ - public function testParseMode($string, $mode) { - $parser = new \Icewind\SMB\Parser($this->getTimeZoneProvider('UTC')); - $this->assertEquals($mode, $parser->parseMode($string), 'Failed parsing ' . $string); - } - - public function statProvider() { - return array( - array( - array( - 'altname: test.txt', - 'create_time: Sat Oct 12 07:05:58 PM 2013 CEST', - 'access_time: Tue Oct 15 02:58:48 PM 2013 CEST', - 'write_time: Sat Oct 12 07:05:58 PM 2013 CEST', - 'change_time: Sat Oct 12 07:05:58 PM 2013 CEST', - 'attributes: (80)', - 'stream: [::$DATA], 29634 bytes' - ), - array( - 'mtime' => strtotime('12 Oct 2013 19:05:58 CEST'), - 'mode' => FileInfo::MODE_NORMAL, - 'size' => 29634 - ) - ) - ); - } - - /** - * @dataProvider statProvider - */ - public function testStat($output, $stat) { - $parser = new \Icewind\SMB\Parser($this->getTimeZoneProvider('UTC')); - $this->assertEquals($stat, $parser->parseStat($output)); - } - - public function dirProvider() { - return array( - array( - array( - ' . D 0 Tue Aug 26 19:11:56 2014', - ' .. DR 0 Sun Oct 28 15:24:02 2012', - ' c.pdf N 29634 Sat Oct 12 19:05:58 2013', - '', - ' 62536 blocks of size 8388608. 57113 blocks available' - ), - array( - new FileInfo('/c.pdf', 'c.pdf', 29634, strtotime('12 Oct 2013 19:05:58 CEST'), - FileInfo::MODE_NORMAL) - ) - ) - ); - } - - /** - * @dataProvider dirProvider - */ - public function testDir($output, $dir) { - $parser = new \Icewind\SMB\Parser($this->getTimeZoneProvider('CEST')); - $this->assertEquals($dir, $parser->parseDir($output, '')); - } -} diff --git a/apps/files_external/3rdparty/icewind/smb/tests/Server.php b/apps/files_external/3rdparty/icewind/smb/tests/Server.php deleted file mode 100644 index 9f62886654f..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/tests/Server.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php -/** - * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> - * This file is licensed under the Licensed under the MIT license: - * http://opensource.org/licenses/MIT - */ - -namespace Icewind\SMB\Test; - -class Server extends \PHPUnit_Framework_TestCase { - /** - * @var \Icewind\SMB\Server $server - */ - private $server; - - private $config; - - public function setUp() { - $this->config = json_decode(file_get_contents(__DIR__ . '/config.json')); - $this->server = new \Icewind\SMB\Server($this->config->host, $this->config->user, $this->config->password); - } - - public function testListShares() { - $shares = $this->server->listShares(); - foreach ($shares as $share) { - if ($share->getName() === $this->config->share) { - return; - } - } - $this->fail('Share "' . $this->config->share . '" not found'); - } - - /** - * @expectedException \Icewind\SMB\Exception\AuthenticationException - */ - public function testWrongUserName() { - $this->markTestSkipped('This fails for no reason on travis'); - $server = new \Icewind\SMB\Server($this->config->host, uniqid(), uniqid()); - $server->listShares(); - } - - /** - * @expectedException \Icewind\SMB\Exception\AuthenticationException - */ - public function testWrongPassword() { - $server = new \Icewind\SMB\Server($this->config->host, $this->config->user, uniqid()); - $server->listShares(); - } - - /** - * @expectedException \Icewind\SMB\Exception\InvalidHostException - */ - public function testWrongHost() { - $server = new \Icewind\SMB\Server(uniqid(), $this->config->user, $this->config->password); - $server->listShares(); - } -} diff --git a/apps/files_external/3rdparty/icewind/smb/tests/Share.php b/apps/files_external/3rdparty/icewind/smb/tests/Share.php deleted file mode 100644 index a629914d748..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/tests/Share.php +++ /dev/null @@ -1,24 +0,0 @@ -<?php -/** - * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> - * This file is licensed under the Licensed under the MIT license: - * http://opensource.org/licenses/MIT - */ - -namespace Icewind\SMB\Test; - -use Icewind\SMB\Server as NormalServer; - -class Share extends AbstractShare { - public function setUp() { - $this->config = json_decode(file_get_contents(__DIR__ . '/config.json')); - $this->server = new NormalServer($this->config->host, $this->config->user, $this->config->password); - $this->share = $this->server->getShare($this->config->share); - if ($this->config->root) { - $this->root = '/' . $this->config->root . '/' . uniqid(); - } else { - $this->root = '/' . uniqid(); - } - $this->share->mkdir($this->root); - } -} diff --git a/apps/files_external/3rdparty/icewind/smb/tests/config.json b/apps/files_external/3rdparty/icewind/smb/tests/config.json deleted file mode 100644 index 0ecd7e3715d..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/tests/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "host": "localhost", - "user": "test", - "password": "test", - "share": "test", - "root": "test" -} diff --git a/apps/files_external/3rdparty/icewind/smb/tests/phpunit.xml b/apps/files_external/3rdparty/icewind/smb/tests/phpunit.xml deleted file mode 100644 index 3ab244dd34f..00000000000 --- a/apps/files_external/3rdparty/icewind/smb/tests/phpunit.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<phpunit bootstrap="bootstrap.php"> - <testsuite name='SMB'> - <directory suffix='.php'>./</directory> - </testsuite> -</phpunit> diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php index 66897eba3d3..4bdfd316799 100644 --- a/apps/files_external/appinfo/app.php +++ b/apps/files_external/appinfo/app.php @@ -30,9 +30,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ -$app = new \OCA\Files_external\Appinfo\Application(); - -$l = \OC::$server->getL10N('files_external'); OC::$CLASSPATH['OC\Files\Storage\StreamWrapper'] = 'files_external/lib/streamwrapper.php'; OC::$CLASSPATH['OC\Files\Storage\FTP'] = 'files_external/lib/ftp.php'; @@ -50,6 +47,12 @@ OC::$CLASSPATH['OCA\Files\External\Api'] = 'files_external/lib/api.php'; require_once __DIR__ . '/../3rdparty/autoload.php'; +// register Application object singleton +\OC_Mount_Config::$app = new \OCA\Files_external\Appinfo\Application(); +$appContainer = \OC_Mount_Config::$app->getContainer(); + +$l = \OC::$server->getL10N('files_external'); + OCP\App::registerAdmin('files_external', 'settings'); if (OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes') == 'yes') { OCP\App::registerPersonal('files_external', 'personal'); @@ -74,6 +77,10 @@ OC_Mount_Config::registerBackend('\OC\Files\Storage\Local', [ 'datadir' => (string)$l->t('Location') ], ]); +// Local must only be visible to the admin +$appContainer->query('OCA\Files_External\Service\BackendService') + ->getBackend('\OC\Files\Storage\Local') + ->setAllowedVisibility(\OCA\Files_External\Service\BackendService::VISIBILITY_ADMIN); OC_Mount_Config::registerBackend('\OC\Files\Storage\AmazonS3', [ 'backend' => (string)$l->t('Amazon S3'), @@ -237,5 +244,5 @@ OC_Mount_Config::registerBackend('\OC\Files\Storage\SFTP_Key', [ 'custom' => 'sftp_key', ] ); -$mountProvider = new \OCA\Files_External\Config\ConfigAdapter(); +$mountProvider = $appContainer->query('OCA\Files_External\Config\ConfigAdapter'); \OC::$server->getMountProviderCollection()->registerProvider($mountProvider); diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php index d77a302466c..38b9e9b7c36 100644 --- a/apps/files_external/appinfo/application.php +++ b/apps/files_external/appinfo/application.php @@ -3,6 +3,7 @@ * @author Morris Jobke <hey@morrisjobke.de> * @author Ross Nicoll <jrn@jrn.me.uk> * @author Vincent Petry <pvince81@owncloud.com> + * @author Robin McCorkell <rmccorkell@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -26,6 +27,9 @@ namespace OCA\Files_External\AppInfo; use \OCA\Files_External\Controller\AjaxController; use \OCP\AppFramework\App; use \OCP\IContainer; +use \OCA\Files_External\Service\BackendService; +use \OCA\Files_External\Lib\BackendConfig; +use \OCA\Files_External\Lib\BackendParameter; /** * @package OCA\Files_External\Appinfo @@ -45,5 +49,33 @@ class Application extends App { $c->query('Request') ); }); + + $this->loadBackends(); + $this->loadAuthMechanisms(); + } + + /** + * Load storage backends provided by this app + */ + protected function loadBackends() { + $container = $this->getContainer(); + $service = $container->query('OCA\\Files_External\\Service\\BackendService'); + } + + /** + * Load authentication mechanisms provided by this app + */ + protected function loadAuthMechanisms() { + $container = $this->getContainer(); + $service = $container->query('OCA\\Files_External\\Service\\BackendService'); + + $service->registerAuthMechanisms([ + // AuthMechanism::SCHEME_NULL mechanism + $container->query('OCA\Files_External\Lib\Auth\NullMechanism'), + + // AuthMechanism::SCHEME_BUILTIN mechanism + $container->query('OCA\Files_External\Lib\Auth\Builtin'), + ]); } + } diff --git a/apps/files_external/appinfo/routes.php b/apps/files_external/appinfo/routes.php index bc4b0e98c91..213e7b28dc1 100644 --- a/apps/files_external/appinfo/routes.php +++ b/apps/files_external/appinfo/routes.php @@ -28,8 +28,7 @@ namespace OCA\Files_External\AppInfo; /** * @var $this \OC\Route\Router **/ -$application = new Application(); -$application->registerRoutes( +\OC_Mount_Config::$app->registerRoutes( $this, array( 'resources' => array( diff --git a/apps/files_external/controller/globalstoragescontroller.php b/apps/files_external/controller/globalstoragescontroller.php index 33f870d48e3..756a34fc5d4 100644 --- a/apps/files_external/controller/globalstoragescontroller.php +++ b/apps/files_external/controller/globalstoragescontroller.php @@ -63,7 +63,8 @@ class GlobalStoragesController extends StoragesController { * Create an external storage entry. * * @param string $mountPoint storage mount point - * @param string $backendClass backend class name + * @param string $backend backend identifier + * @param string $authMechanism authentication mechanism identifier * @param array $backendOptions backend-specific options * @param array $mountOptions mount-specific options * @param array $applicableUsers users for which to mount the storage @@ -74,21 +75,27 @@ class GlobalStoragesController extends StoragesController { */ public function create( $mountPoint, - $backendClass, + $backend, + $authMechanism, $backendOptions, $mountOptions, $applicableUsers, $applicableGroups, $priority ) { - $newStorage = new StorageConfig(); - $newStorage->setMountPoint($mountPoint); - $newStorage->setBackendClass($backendClass); - $newStorage->setBackendOptions($backendOptions); - $newStorage->setMountOptions($mountOptions); - $newStorage->setApplicableUsers($applicableUsers); - $newStorage->setApplicableGroups($applicableGroups); - $newStorage->setPriority($priority); + $newStorage = $this->createStorage( + $mountPoint, + $backend, + $authMechanism, + $backendOptions, + $mountOptions, + $applicableUsers, + $applicableGroups, + $priority + ); + if ($newStorage instanceof DataResponse) { + return $newStorage; + } $response = $this->validate($newStorage); if (!empty($response)) { @@ -110,7 +117,8 @@ class GlobalStoragesController extends StoragesController { * * @param int $id storage id * @param string $mountPoint storage mount point - * @param string $backendClass backend class name + * @param string $backend backend identifier + * @param string $authMechanism authentication mechansim identifier * @param array $backendOptions backend-specific options * @param array $mountOptions mount-specific options * @param array $applicableUsers users for which to mount the storage @@ -122,21 +130,28 @@ class GlobalStoragesController extends StoragesController { public function update( $id, $mountPoint, - $backendClass, + $backend, + $authMechanism, $backendOptions, $mountOptions, $applicableUsers, $applicableGroups, $priority ) { - $storage = new StorageConfig($id); - $storage->setMountPoint($mountPoint); - $storage->setBackendClass($backendClass); - $storage->setBackendOptions($backendOptions); - $storage->setMountOptions($mountOptions); - $storage->setApplicableUsers($applicableUsers); - $storage->setApplicableGroups($applicableGroups); - $storage->setPriority($priority); + $storage = $this->createStorage( + $mountPoint, + $backend, + $authMechanism, + $backendOptions, + $mountOptions, + $applicableUsers, + $applicableGroups, + $priority + ); + if ($storage instanceof DataResponse) { + return $storage; + } + $storage->setId($id); $response = $this->validate($storage); if (!empty($response)) { diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php index c09ceacc7d7..3d91af8bd8f 100644 --- a/apps/files_external/controller/storagescontroller.php +++ b/apps/files_external/controller/storagescontroller.php @@ -32,6 +32,10 @@ use \OCP\AppFramework\Http; use \OCA\Files_external\Service\StoragesService; use \OCA\Files_external\NotFoundException; use \OCA\Files_external\Lib\StorageConfig; +use \OCA\Files_External\Lib\Backend\Backend; +use \OCA\Files_External\Lib\Auth\AuthMechanism; +use \OCP\Files\StorageNotAvailableException; +use \OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; /** * Base class for storages controllers @@ -72,6 +76,51 @@ abstract class StoragesController extends Controller { } /** + * Create a storage from its parameters + * + * @param string $mountPoint storage mount point + * @param string $backend backend identifier + * @param string $authMechanism authentication mechanism identifier + * @param array $backendOptions backend-specific options + * @param array|null $mountOptions mount-specific options + * @param array|null $applicableUsers users for which to mount the storage + * @param array|null $applicableGroups groups for which to mount the storage + * @param int|null $priority priority + * + * @return StorageConfig|DataResponse + */ + protected function createStorage( + $mountPoint, + $backend, + $authMechanism, + $backendOptions, + $mountOptions = null, + $applicableUsers = null, + $applicableGroups = null, + $priority = null + ) { + try { + return $this->service->createStorage( + $mountPoint, + $backend, + $authMechanism, + $backendOptions, + $mountOptions, + $applicableUsers, + $applicableGroups, + $priority + ); + } catch (\InvalidArgumentException $e) { + return new DataResponse( + [ + 'message' => (string)$this->l10n->t('Invalid backend or authentication mechanism class') + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + } + + /** * Validate storage config * * @param StorageConfig $storage storage config @@ -89,18 +138,39 @@ abstract class StoragesController extends Controller { ); } - // TODO: validate that other attrs are set - - $backends = \OC_Mount_Config::getBackends(); - if (!isset($backends[$storage->getBackendClass()])) { + /** @var Backend */ + $backend = $storage->getBackend(); + /** @var AuthMechanism */ + $authMechanism = $storage->getAuthMechanism(); + if (!$backend || $backend->checkDependencies()) { // invalid backend return new DataResponse( array( - 'message' => (string)$this->l10n->t('Invalid storage backend "%s"', array($storage->getBackendClass())) + 'message' => (string)$this->l10n->t('Invalid storage backend "%s"', [ + $storage->getBackend()->getIdentifier() + ]) ), Http::STATUS_UNPROCESSABLE_ENTITY ); } + if (!$backend->validateStorage($storage)) { + // unsatisfied parameters + return new DataResponse( + array( + 'message' => (string)$this->l10n->t('Unsatisfied backend parameters') + ), + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + if (!$authMechanism->validateStorage($storage)) { + // unsatisfied parameters + return new DataResponse( + [ + 'message' => (string)$this->l10n->t('Unsatisfied authentication mechanism parameters') + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } return null; } @@ -114,14 +184,27 @@ abstract class StoragesController extends Controller { * @param StorageConfig $storage storage configuration */ protected function updateStorageStatus(StorageConfig &$storage) { - // update status (can be time-consuming) - $storage->setStatus( - \OC_Mount_Config::getBackendStatus( - $storage->getBackendClass(), - $storage->getBackendOptions(), - false - ) - ); + try { + /** @var AuthMechanism */ + $authMechanism = $storage->getAuthMechanism(); + $authMechanism->manipulateStorageConfig($storage); + /** @var Backend */ + $backend = $storage->getBackend(); + $backend->manipulateStorageConfig($storage); + + // update status (can be time-consuming) + $storage->setStatus( + \OC_Mount_Config::getBackendStatus( + $backend->getStorageClass(), + $storage->getBackendOptions(), + false + ) + ); + } catch (InsufficientDataForMeaningfulAnswerException $e) { + $storage->setStatus(\OC_Mount_Config::STATUS_INDETERMINATE); + } catch (StorageNotAvailableException $e) { + $storage->setStatus(\OC_Mount_Config::STATUS_ERROR); + } } /** diff --git a/apps/files_external/controller/userstoragescontroller.php b/apps/files_external/controller/userstoragescontroller.php index f5d22e5caa6..0d41e088a76 100644 --- a/apps/files_external/controller/userstoragescontroller.php +++ b/apps/files_external/controller/userstoragescontroller.php @@ -30,8 +30,10 @@ use \OCP\AppFramework\Http\DataResponse; use \OCP\AppFramework\Controller; use \OCP\AppFramework\Http; use \OCA\Files_external\Service\UserStoragesService; +use \OCA\Files_External\Service\BackendService; use \OCA\Files_external\NotFoundException; use \OCA\Files_external\Lib\StorageConfig; +use \OCA\Files_External\Lib\Backend\Backend; /** * User storages controller @@ -69,17 +71,20 @@ class UserStoragesController extends StoragesController { protected function validate(StorageConfig $storage) { $result = parent::validate($storage); - if ($result != null) { + if ($result !== null) { return $result; } // Verify that the mount point applies for the current user // Prevent non-admin users from mounting local storage and other disabled backends - $allowedBackends = \OC_Mount_Config::getPersonalBackends(); - if (!isset($allowedBackends[$storage->getBackendClass()])) { + /** @var Backend */ + $backend = $storage->getBackend(); + if (!$backend->isVisibleFor(BackendService::VISIBILITY_PERSONAL)) { return new DataResponse( array( - 'message' => (string)$this->l10n->t('Invalid storage backend "%s"', array($storage->getBackendClass())) + 'message' => (string)$this->l10n->t('Admin-only storage backend "%s"', [ + $storage->getBackend()->getIdentifier() + ]) ), Http::STATUS_UNPROCESSABLE_ENTITY ); @@ -103,7 +108,8 @@ class UserStoragesController extends StoragesController { * Create an external storage entry. * * @param string $mountPoint storage mount point - * @param string $backendClass backend class name + * @param string $backend backend identifier + * @param string $authMechanism authentication mechanism identifier * @param array $backendOptions backend-specific options * @param array $mountOptions backend-specific mount options * @@ -113,15 +119,21 @@ class UserStoragesController extends StoragesController { */ public function create( $mountPoint, - $backendClass, + $backend, + $authMechanism, $backendOptions, $mountOptions ) { - $newStorage = new StorageConfig(); - $newStorage->setMountPoint($mountPoint); - $newStorage->setBackendClass($backendClass); - $newStorage->setBackendOptions($backendOptions); - $newStorage->setMountOptions($mountOptions); + $newStorage = $this->createStorage( + $mountPoint, + $backend, + $authMechanism, + $backendOptions, + $mountOptions + ); + if ($newStorage instanceOf DataResponse) { + return $newStorage; + } $response = $this->validate($newStorage); if (!empty($response)) { @@ -142,7 +154,8 @@ class UserStoragesController extends StoragesController { * * @param int $id storage id * @param string $mountPoint storage mount point - * @param string $backendClass backend class name + * @param string $backend backend identifier + * @param string $authMechanism authentication mechanism identifier * @param array $backendOptions backend-specific options * @param array $mountOptions backend-specific mount options * @@ -153,15 +166,22 @@ class UserStoragesController extends StoragesController { public function update( $id, $mountPoint, - $backendClass, + $backend, + $authMechanism, $backendOptions, $mountOptions ) { - $storage = new StorageConfig($id); - $storage->setMountPoint($mountPoint); - $storage->setBackendClass($backendClass); - $storage->setBackendOptions($backendOptions); - $storage->setMountOptions($mountOptions); + $storage = $this->createStorage( + $mountPoint, + $backend, + $authMechanism, + $backendOptions, + $mountOptions + ); + if ($storage instanceOf DataResponse) { + return $storage; + } + $storage->setId($id); $response = $this->validate($storage); if (!empty($response)) { diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index 287b4664541..6bf0143f1c0 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -191,7 +191,8 @@ var StorageConfig = function(id) { StorageConfig.Status = { IN_PROGRESS: -1, SUCCESS: 0, - ERROR: 1 + ERROR: 1, + INDETERMINATE: 2 }; /** * @memberof OCA.External.Settings @@ -214,11 +215,18 @@ StorageConfig.prototype = { mountPoint: '', /** - * Backend class name + * Backend * * @type string */ - backendClass: null, + backend: null, + + /** + * Authentication mechanism + * + * @type string + */ + authMechanism: null, /** * Backend-specific configuration @@ -272,7 +280,8 @@ StorageConfig.prototype = { getData: function() { var data = { mountPoint: this.mountPoint, - backendClass: this.backendClass, + backend: this.backend, + authMechanism: this.authMechanism, backendOptions: this.backendOptions }; if (this.id) { @@ -579,6 +588,13 @@ MountConfigListView.prototype = { */ _allBackends: null, + /** + * List of all supported authentication mechanisms + * + * @type Object.<string,Object> + */ + _allAuthMechanisms: null, + _encryptionEnabled: false, /** @@ -605,6 +621,7 @@ MountConfigListView.prototype = { // read the backend config that was carefully crammed // into the data-configurations attribute of the select this._allBackends = this.$el.find('.selectBackend').data('configurations'); + this._allAuthMechanisms = this.$el.find('#addMountPoint .authentication').data('mechanisms'); //initialize hidden input field with list of users and groups this.$el.find('tr:not(#addMountPoint)').each(function(i,tr) { @@ -660,6 +677,7 @@ MountConfigListView.prototype = { }); this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this)); + this.$el.on('change', '.selectAuthMechanism', _.bind(this._onSelectAuthMechanism, this)); }, _onChange: function(event) { @@ -687,47 +705,34 @@ MountConfigListView.prototype = { $el.find('tbody').append($tr.clone()); $el.find('tbody tr').last().find('.mountPoint input').val(''); var selected = $target.find('option:selected').text(); - var backendClass = $target.val(); + var backend = $target.val(); $tr.find('.backend').text(selected); if ($tr.find('.mountPoint input').val() === '') { $tr.find('.mountPoint input').val(this._suggestMountPoint(selected)); } - $tr.addClass(backendClass); - $tr.find('.backend').data('class', backendClass); - var configurations = this._allBackends; - var $td = $tr.find('td.configuration'); - $.each(configurations, function(backend, parameters) { - if (backend === backendClass) { - $.each(parameters['configuration'], function(parameter, placeholder) { - var is_optional = false; - if (placeholder.indexOf('&') === 0) { - is_optional = true; - placeholder = placeholder.substring(1); - } - var newElement; - if (placeholder.indexOf('*') === 0) { - var class_string = is_optional ? ' optional' : ''; - newElement = $('<input type="password" class="added' + class_string + '" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />'); - } else if (placeholder.indexOf('!') === 0) { - newElement = $('<label><input type="checkbox" class="added" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>'); - } else if (placeholder.indexOf('#') === 0) { - newElement = $('<input type="hidden" class="added" data-parameter="'+parameter+'" />'); - } else { - var class_string = is_optional ? ' optional' : ''; - newElement = $('<input type="text" class="added' + class_string + '" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />'); - } - highlightInput(newElement); - $td.append(newElement); - }); - var priorityEl = $('<input type="hidden" class="priority" value="' + parameters['priority'] + '" />'); - $tr.append(priorityEl); - if (parameters['custom'] && $el.find('tbody tr.'+backendClass.replace(/\\/g, '\\\\')).length === 1) { - OC.addScript('files_external', parameters['custom']); - } - $td.children().not('[type=hidden]').first().focus(); - return false; + $tr.addClass(backend); + $tr.find('.backend').data('class', backend); + var backendConfiguration = this._allBackends[backend]; + + var selectAuthMechanism = $('<select class="selectAuthMechanism"></select>'); + $.each(this._allAuthMechanisms, function(authClass, authMechanism) { + if (backendConfiguration['authSchemes'][authMechanism['scheme']]) { + selectAuthMechanism.append( + $('<option value="'+authClass+'" data-scheme="'+authMechanism['scheme']+'">'+authMechanism['name']+'</option>') + ); } }); + $tr.find('td.authentication').append(selectAuthMechanism); + + var $td = $tr.find('td.configuration'); + $.each(backendConfiguration['configuration'], _.partial(this.writeParameterInput, $td)); + + selectAuthMechanism.trigger('change'); // generate configuration parameters for auth mechanism + + var priorityEl = $('<input type="hidden" class="priority" value="' + backendConfiguration['priority'] + '" />'); + $tr.append(priorityEl); + $td.children().not('[type=hidden]').first().focus(); + $tr.find('td').last().attr('class', 'remove'); $tr.find('td.mountOptionsToggle').removeClass('hidden'); $tr.find('td').last().removeAttr('style'); @@ -736,6 +741,41 @@ MountConfigListView.prototype = { addSelect2($tr.find('.applicableUsers'), this._userListLimit); }, + _onSelectAuthMechanism: function(event) { + var $target = $(event.target); + var $tr = $target.closest('tr'); + + var authMechanism = $target.val(); + var authMechanismConfiguration = this._allAuthMechanisms[authMechanism]; + var $td = $tr.find('td.configuration'); + $td.find('.auth-param').remove(); + + $.each(authMechanismConfiguration['configuration'], _.partial( + this.writeParameterInput, $td, _, _, ['auth-param'] + )); + }, + + writeParameterInput: function($td, parameter, placeholder, classes) { + classes = $.isArray(classes) ? classes : []; + classes.push('added'); + if (placeholder.indexOf('&') === 0) { + classes.push('optional'); + placeholder = placeholder.substring(1); + } + var newElement; + if (placeholder.indexOf('*') === 0) { + newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />'); + } else if (placeholder.indexOf('!') === 0) { + newElement = $('<label><input type="checkbox" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>'); + } else if (placeholder.indexOf('#') === 0) { + newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'); + } else { + newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />'); + } + highlightInput(newElement); + $td.append(newElement); + }, + /** * Gets the storage model from the given row * @@ -750,7 +790,8 @@ MountConfigListView.prototype = { } var storage = new this._storageConfigClass(storageId); storage.mountPoint = $tr.find('.mountPoint input').val(); - storage.backendClass = $tr.find('.backend').data('class'); + storage.backend = $tr.find('.backend').data('class'); + storage.authMechanism = $tr.find('.selectAuthMechanism').val(); var classOptions = {}; var configuration = $tr.find('.configuration input'); @@ -903,7 +944,7 @@ MountConfigListView.prototype = { */ updateStatus: function($tr, status) { var $statusSpan = $tr.find('.status span'); - $statusSpan.removeClass('success error loading-small'); + $statusSpan.removeClass('loading-small success indeterminate error'); switch (status) { case StorageConfig.Status.IN_PROGRESS: $statusSpan.addClass('loading-small'); @@ -911,6 +952,9 @@ MountConfigListView.prototype = { case StorageConfig.Status.SUCCESS: $statusSpan.addClass('success'); break; + case StorageConfig.Status.INDETERMINATE: + $statusSpan.addClass('indeterminate'); + break; default: $statusSpan.addClass('error'); } diff --git a/apps/files_external/lib/auth/authmechanism.php b/apps/files_external/lib/auth/authmechanism.php new file mode 100644 index 00000000000..11d99bb330d --- /dev/null +++ b/apps/files_external/lib/auth/authmechanism.php @@ -0,0 +1,116 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib\Auth; + +use \OCA\Files_External\Lib\StorageConfig; +use \OCA\Files_External\Lib\VisibilityTrait; +use \OCA\Files_External\Lib\IdentifierTrait; +use \OCA\Files_External\Lib\FrontendDefinitionTrait; +use \OCA\Files_External\Lib\StorageModifierTrait; + +/** + * Authentication mechanism + * + * An authentication mechanism can have services injected during construction, + * such as \OCP\IDB for database operations. This allows an authentication + * mechanism to perform advanced operations based on provided information. + * + * An authenication scheme defines the parameter interface, common to the + * storage implementation, the backend and the authentication mechanism. + * A storage implementation expects parameters according to the authentication + * scheme, which are provided from the authentication mechanism. + * + * This class uses the following traits: + * - VisibilityTrait + * Restrict usage to admin-only/none + * - FrontendDefinitionTrait + * Specify configuration parameters and other definitions + * - StorageModifierTrait + * Object can affect storage mounting + */ +class AuthMechanism implements \JsonSerializable { + + /** Standard authentication schemes */ + const SCHEME_NULL = 'null'; + const SCHEME_BUILTIN = 'builtin'; + const SCHEME_PASSWORD = 'password'; + const SCHEME_OAUTH1 = 'oauth1'; + const SCHEME_OAUTH2 = 'oauth2'; + const SCHEME_PUBLICKEY = 'publickey'; + const SCHEME_OPENSTACK = 'openstack'; + + use VisibilityTrait; + use FrontendDefinitionTrait; + use StorageModifierTrait; + use IdentifierTrait; + + /** @var string */ + protected $scheme; + + /** + * Get the authentication scheme implemented + * See self::SCHEME_* constants + * + * @return string + */ + public function getScheme() { + return $this->scheme; + } + + /** + * @param string $scheme + * @return self + */ + public function setScheme($scheme) { + $this->scheme = $scheme; + return $this; + } + + /** + * Serialize into JSON for client-side JS + * + * @return array + */ + public function jsonSerialize() { + $data = $this->jsonSerializeDefinition(); + $data['scheme'] = $this->getScheme(); + + return $data; + } + + /** + * Check if parameters are satisfied in a StorageConfig + * + * @param StorageConfig $storage + * @return bool + */ + public function validateStorage(StorageConfig $storage) { + // does the backend actually support this scheme + $supportedSchemes = $storage->getBackend()->getAuthSchemes(); + if (!isset($supportedSchemes[$this->getScheme()])) { + return false; + } + + return $this->validateStorageDefinition($storage); + } + +} diff --git a/apps/files_external/lib/auth/builtin.php b/apps/files_external/lib/auth/builtin.php new file mode 100644 index 00000000000..ee28a4e8a5c --- /dev/null +++ b/apps/files_external/lib/auth/builtin.php @@ -0,0 +1,41 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib\Auth; + +use \OCP\IL10N; +use \OCA\Files_External\Lib\Auth\AuthMechanism; +use \OCA\Files_external\Lib\StorageConfig; + +/** + * Builtin authentication mechanism, for legacy backends + */ +class Builtin extends AuthMechanism { + + public function __construct(IL10N $l) { + $this + ->setIdentifier('builtin::builtin') + ->setScheme(self::SCHEME_BUILTIN) + ->setText($l->t('Builtin')) + ; + } + +} diff --git a/apps/files_external/lib/auth/nullmechanism.php b/apps/files_external/lib/auth/nullmechanism.php new file mode 100644 index 00000000000..1765fc67396 --- /dev/null +++ b/apps/files_external/lib/auth/nullmechanism.php @@ -0,0 +1,41 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib\Auth; + +use \OCP\IL10N; +use \OCA\Files_External\Lib\Auth\AuthMechanism; +use \OCA\Files_external\Lib\StorageConfig; + +/** + * Null authentication mechanism + */ +class NullMechanism extends AuthMechanism { + + public function __construct(IL10N $l) { + $this + ->setIdentifier('null::null') + ->setScheme(self::SCHEME_NULL) + ->setText($l->t('None')) + ; + } + +} diff --git a/apps/files_external/lib/backend/backend.php b/apps/files_external/lib/backend/backend.php new file mode 100644 index 00000000000..90d5d38ed94 --- /dev/null +++ b/apps/files_external/lib/backend/backend.php @@ -0,0 +1,164 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib\Backend; + +use \OCA\Files_External\Lib\StorageConfig; +use \OCA\Files_External\Lib\VisibilityTrait; +use \OCA\Files_External\Lib\FrontendDefinitionTrait; +use \OCA\Files_External\Lib\PriorityTrait; +use \OCA\Files_External\Lib\DependencyTrait; +use \OCA\Files_External\Lib\StorageModifierTrait; +use \OCA\Files_External\Lib\IdentifierTrait; +use \OCA\Files_External\Lib\Auth\AuthMechanism; + +/** + * Storage backend + * + * A backend can have services injected during construction, + * such as \OCP\IDB for database operations. This allows a backend + * to perform advanced operations based on provided information. + * + * An authenication scheme defines the parameter interface, common to the + * storage implementation, the backend and the authentication mechanism. + * A storage implementation expects parameters according to the authentication + * scheme, which are provided from the authentication mechanism. + * + * This class uses the following traits: + * - VisibilityTrait + * Restrict usage to admin-only/none + * - FrontendDefinitionTrait + * Specify configuration parameters and other definitions + * - PriorityTrait + * Allow objects to prioritize over others with the same mountpoint + * - DependencyTrait + * The object requires certain dependencies to be met + * - StorageModifierTrait + * Object can affect storage mounting + */ +class Backend implements \JsonSerializable { + + use VisibilityTrait; + use FrontendDefinitionTrait; + use PriorityTrait; + use DependencyTrait; + use StorageModifierTrait; + use IdentifierTrait; + + /** @var string storage class */ + private $storageClass; + + /** @var array 'scheme' => true, supported authentication schemes */ + private $authSchemes = []; + + /** @var AuthMechanism|callable authentication mechanism fallback */ + private $legacyAuthMechanism; + + /** + * @return string + */ + public function getStorageClass() { + return $this->storageClass; + } + + /** + * @param string $class + * @return self + */ + public function setStorageClass($class) { + $this->storageClass = $class; + return $this; + } + + /** + * @return array + */ + public function getAuthSchemes() { + if (empty($this->authSchemes)) { + return [AuthMechanism::SCHEME_NULL => true]; + } + return $this->authSchemes; + } + + /** + * @param string $scheme + * @return self + */ + public function addAuthScheme($scheme) { + $this->authSchemes[$scheme] = true; + return $this; + } + + /** + * @param array $parameters storage parameters, for dynamic mechanism selection + * @return AuthMechanism + */ + public function getLegacyAuthMechanism(array $parameters = []) { + if (is_callable($this->legacyAuthMechanism)) { + return call_user_func($this->legacyAuthMechanism, $parameters); + } + return $this->legacyAuthMechanism; + } + + /** + * @param AuthMechanism $authMechanism + * @return self + */ + public function setLegacyAuthMechanism(AuthMechanism $authMechanism) { + $this->legacyAuthMechanism = $authMechanism; + return $this; + } + + /** + * @param callable $callback dynamic auth mechanism selection + * @return self + */ + public function setLegacyAuthMechanismCallback(callable $callback) { + $this->legacyAuthMechanism = $callback; + } + + /** + * Serialize into JSON for client-side JS + * + * @return array + */ + public function jsonSerialize() { + $data = $this->jsonSerializeDefinition(); + + $data['backend'] = $data['name']; // legacy compat + $data['priority'] = $this->getPriority(); + $data['authSchemes'] = $this->getAuthSchemes(); + + return $data; + } + + /** + * Check if parameters are satisfied in a StorageConfig + * + * @param StorageConfig $storage + * @return bool + */ + public function validateStorage(StorageConfig $storage) { + return $this->validateStorageDefinition($storage); + } + +} + diff --git a/apps/files_external/lib/backend/legacybackend.php b/apps/files_external/lib/backend/legacybackend.php new file mode 100644 index 00000000000..0f60c2caa47 --- /dev/null +++ b/apps/files_external/lib/backend/legacybackend.php @@ -0,0 +1,85 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib\Backend; + +use \OCA\Files_External\Lib\DefinitionParameter; +use \OCA\Files_External\Lib\Backend\Backend; +use \OCA\Files_External\Lib\Auth\Builtin; + +/** + * Legacy compatibility for OC_Mount_Config::registerBackend() + */ +class LegacyBackend extends Backend { + + /** + * @param string $class + * @param array $definition + * @param Builtin $authMechanism + */ + public function __construct($class, array $definition, Builtin $authMechanism) { + $this + ->setIdentifier($class) + ->setStorageClass($class) + ->setText($definition['backend']) + ->addAuthScheme(Builtin::SCHEME_BUILTIN) + ->setLegacyAuthMechanism($authMechanism) + ; + + foreach ($definition['configuration'] as $name => $placeholder) { + $flags = DefinitionParameter::FLAG_NONE; + $type = DefinitionParameter::VALUE_TEXT; + if ($placeholder[0] === '&') { + $flags = DefinitionParameter::FLAG_OPTIONAL; + $placeholder = substr($placeholder, 1); + } + switch ($placeholder[0]) { + case '!': + $type = DefinitionParameter::VALUE_BOOLEAN; + $placeholder = substr($placeholder, 1); + break; + case '*': + $type = DefinitionParameter::VALUE_PASSWORD; + $placeholder = substr($placeholder, 1); + break; + case '#': + $type = DefinitionParameter::VALUE_HIDDEN; + $placeholder = substr($placeholder, 1); + break; + } + $this->addParameter((new DefinitionParameter($name, $placeholder)) + ->setType($type) + ->setFlags($flags) + ); + } + + if (isset($definition['priority'])) { + $this->setPriority($definition['priority']); + } + if (isset($definition['custom'])) { + $this->setCustomJs($definition['custom']); + } + if (isset($definition['has_dependencies']) && $definition['has_dependencies']) { + $this->setDependencyCheck($class . '::checkDependencies'); + } + } + +} diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 8fcf39cc767..91c33ef10a5 100644 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -32,6 +32,11 @@ */ use phpseclib\Crypt\AES; +use \OCA\Files_External\Appinfo\Application; +use \OCA\Files_External\Lib\BackendConfig; +use \OCA\Files_External\Service\BackendService; +use \OCA\Files_External\Lib\Backend\LegacyBackend; +use \OCA\Files_External\Lib\StorageConfig; /** * Class to configure mount.json globally and for users @@ -47,75 +52,30 @@ class OC_Mount_Config { // getBackendStatus return types const STATUS_SUCCESS = 0; const STATUS_ERROR = 1; + const STATUS_INDETERMINATE = 2; // whether to skip backend test (for unit tests, as this static class is not mockable) public static $skipTest = false; - private static $backends = array(); + /** @var Application */ + public static $app; /** * @param string $class * @param array $definition * @return bool + * @deprecated 8.2.0 use \OCA\Files_External\Service\BackendService::registerBackend() */ public static function registerBackend($class, $definition) { - if (!isset($definition['backend'])) { - return false; - } - - OC_Mount_Config::$backends[$class] = $definition; - return true; - } - - /** - * Setup backends - * - * @return array of previously registered backends - */ - public static function setUp($backends = array()) { - $backup = self::$backends; - self::$backends = $backends; - - return $backup; - } - - /** - * Get details on each of the external storage backends, used for the mount config UI - * If a custom UI is needed, add the key 'custom' and a javascript file with that name will be loaded - * If the configuration parameter should be secret, add a '*' to the beginning of the value - * If the configuration parameter is a boolean, add a '!' to the beginning of the value - * If the configuration parameter is optional, add a '&' to the beginning of the value - * If the configuration parameter is hidden, add a '#' to the beginning of the value - * - * @return array - */ - public static function getBackends() { - $sortFunc = function ($a, $b) { - return strcasecmp($a['backend'], $b['backend']); - }; - - $backEnds = array(); - - foreach (OC_Mount_Config::$backends as $class => $backend) { - if (isset($backend['has_dependencies']) and $backend['has_dependencies'] === true) { - if (!method_exists($class, 'checkDependencies')) { - \OCP\Util::writeLog('files_external', - "Backend class $class has dependencies but doesn't provide method checkDependencies()", - \OCP\Util::DEBUG); - continue; - } elseif ($class::checkDependencies() !== true) { - continue; - } - } - $backEnds[$class] = $backend; - } + $backendService = self::$app->getContainer()->query('OCA\Files_External\Service\BackendService'); + $auth = self::$app->getContainer()->query('OCA\Files_External\Lib\Auth\Builtin'); - uasort($backEnds, $sortFunc); + $backendService->registerBackend(new LegacyBackend($class, $definition, $auth)); - return $backEnds; + return true; } - /** + /* * Hook that mounts the given user's visible mount points * * @param array $data @@ -152,331 +112,124 @@ class OC_Mount_Config { * Returns the mount points for the given user. * The mount point is relative to the data directory. * - * @param string $user user + * @param string $uid user * @return array of mount point string as key, mountpoint config as value + * + * @deprecated 8.2.0 use UserGlobalStoragesService::getAllStorages() and UserStoragesService::getAllStorages() */ - public static function getAbsoluteMountPoints($user) { + public static function getAbsoluteMountPoints($uid) { $mountPoints = array(); - $backends = self::getBackends(); - - // Load system mount points - $mountConfig = self::readData(); + $userGlobalStoragesService = self::$app->getContainer()->query('OCA\Files_External\Service\UserGlobalStoragesService'); + $userStoragesService = self::$app->getContainer()->query('OCA\Files_External\Service\UserStoragesService'); + $user = self::$app->getContainer()->query('OCP\IUserManager')->get($uid); - // Global mount points (is this redundant?) - if (isset($mountConfig[self::MOUNT_TYPE_GLOBAL])) { - foreach ($mountConfig[self::MOUNT_TYPE_GLOBAL] as $mountPoint => $options) { - $options['personal'] = false; - $options['options'] = self::decryptPasswords($options['options']); - if (!isset($options['priority'])) { - $options['priority'] = $backends[$options['class']]['priority']; - } + $userGlobalStoragesService->setUser($user); + $userStoragesService->setUser($user); - // Override if priority greater - if ((!isset($mountPoints[$mountPoint])) - || ($options['priority'] >= $mountPoints[$mountPoint]['priority']) - ) { - $options['priority_type'] = self::MOUNT_TYPE_GLOBAL; - $options['backend'] = $backends[$options['class']]['backend']; - $mountPoints[$mountPoint] = $options; - } + foreach ($userGlobalStoragesService->getAllStorages() as $storage) { + $mountPoint = '/'.$uid.'/files'.$storage->getMountPoint(); + $mountEntry = self::prepareMountPointEntry($storage, false); + foreach ($mountEntry['options'] as &$option) { + $option = self::setUserVars($uid, $option); } + $mountPoints[$mountPoint] = $mountEntry; } - // All user mount points - if (isset($mountConfig[self::MOUNT_TYPE_USER]) && isset($mountConfig[self::MOUNT_TYPE_USER]['all'])) { - $mounts = $mountConfig[self::MOUNT_TYPE_USER]['all']; - foreach ($mounts as $mountPoint => $options) { - $mountPoint = self::setUserVars($user, $mountPoint); - foreach ($options as &$option) { - $option = self::setUserVars($user, $option); - } - $options['personal'] = false; - $options['options'] = self::decryptPasswords($options['options']); - if (!isset($options['priority'])) { - $options['priority'] = $backends[$options['class']]['priority']; - } - // Override if priority greater - if ((!isset($mountPoints[$mountPoint])) - || ($options['priority'] >= $mountPoints[$mountPoint]['priority']) - ) { - $options['priority_type'] = self::MOUNT_TYPE_GLOBAL; - $options['backend'] = $backends[$options['class']]['backend']; - $mountPoints[$mountPoint] = $options; - } + foreach ($userStoragesService->getAllStorages() as $storage) { + $mountPoint = '/'.$uid.'/files'.$storage->getMountPoint(); + $mountEntry = self::prepareMountPointEntry($storage, true); + foreach ($mountEntry['options'] as &$option) { + $option = self::setUserVars($uid, $option); } + $mountPoints[$mountPoint] = $mountEntry; } - // Group mount points - if (isset($mountConfig[self::MOUNT_TYPE_GROUP])) { - foreach ($mountConfig[self::MOUNT_TYPE_GROUP] as $group => $mounts) { - if (\OC_Group::inGroup($user, $group)) { - foreach ($mounts as $mountPoint => $options) { - $mountPoint = self::setUserVars($user, $mountPoint); - foreach ($options as &$option) { - $option = self::setUserVars($user, $option); - } - $options['personal'] = false; - $options['options'] = self::decryptPasswords($options['options']); - if (!isset($options['priority'])) { - $options['priority'] = $backends[$options['class']]['priority']; - } - - // Override if priority greater or if priority type different - if ((!isset($mountPoints[$mountPoint])) - || ($options['priority'] >= $mountPoints[$mountPoint]['priority']) - || ($mountPoints[$mountPoint]['priority_type'] !== self::MOUNT_TYPE_GROUP) - ) { - $options['priority_type'] = self::MOUNT_TYPE_GROUP; - $options['backend'] = $backends[$options['class']]['backend']; - $mountPoints[$mountPoint] = $options; - } - } - } - } - } - // User mount points - if (isset($mountConfig[self::MOUNT_TYPE_USER])) { - foreach ($mountConfig[self::MOUNT_TYPE_USER] as $mountUser => $mounts) { - if (strtolower($mountUser) === strtolower($user)) { - foreach ($mounts as $mountPoint => $options) { - $mountPoint = self::setUserVars($user, $mountPoint); - foreach ($options as &$option) { - $option = self::setUserVars($user, $option); - } - $options['personal'] = false; - $options['options'] = self::decryptPasswords($options['options']); - if (!isset($options['priority'])) { - $options['priority'] = $backends[$options['class']]['priority']; - } - - // Override if priority greater or if priority type different - if ((!isset($mountPoints[$mountPoint])) - || ($options['priority'] >= $mountPoints[$mountPoint]['priority']) - || ($mountPoints[$mountPoint]['priority_type'] !== self::MOUNT_TYPE_USER) - ) { - $options['priority_type'] = self::MOUNT_TYPE_USER; - $options['backend'] = $backends[$options['class']]['backend']; - $mountPoints[$mountPoint] = $options; - } - } - } - } - } - - $personalBackends = self::getPersonalBackends(); - // Load personal mount points - $mountConfig = self::readData($user); - if (isset($mountConfig[self::MOUNT_TYPE_USER][$user])) { - foreach ($mountConfig[self::MOUNT_TYPE_USER][$user] as $mountPoint => $options) { - if (isset($personalBackends[$options['class']])) { - $options['personal'] = true; - $options['options'] = self::decryptPasswords($options['options']); - - // Always override previous config - $options['priority_type'] = self::MOUNT_TYPE_PERSONAL; - $options['backend'] = $backends[$options['class']]['backend']; - $mountPoints[$mountPoint] = $options; - } - } - } + $userGlobalStoragesService->resetUser(); + $userStoragesService->resetUser(); return $mountPoints; } /** - * fill in the correct values for $user + * Get the system mount points * - * @param string $user user value - * @param string|array $input - * @return string + * @return array + * + * @deprecated 8.2.0 use GlobalStoragesService::getAllStorages() */ - private static function setUserVars($user, $input) { - if (is_array($input)) { - foreach ($input as &$value) { - if (is_string($value)) { - $value = str_replace('$user', $user, $value); - } - } - } else { - $input = str_replace('$user', $user, $input); + public static function getSystemMountPoints() { + $mountPoints = []; + $service = self::$app->getContainer()->query('OCA\Files_External\Service\GlobalStoragesService'); + + foreach ($service->getAllStorages() as $storage) { + $mountPoints[] = self::prepareMountPointEntry($storage, false); } - return $input; } - /** - * Get details on each of the external storage backends, used for the mount config UI - * Some backends are not available as a personal backend, f.e. Local and such that have - * been disabled by the admin. - * - * If a custom UI is needed, add the key 'custom' and a javascript file with that name will be loaded - * If the configuration parameter should be secret, add a '*' to the beginning of the value - * If the configuration parameter is a boolean, add a '!' to the beginning of the value - * If the configuration parameter is optional, add a '&' to the beginning of the value - * If the configuration parameter is hidden, add a '#' to the beginning of the value + * Get the personal mount points of the current user * * @return array + * + * @deprecated 8.2.0 use UserStoragesService::getAllStorages() */ - public static function getPersonalBackends() { - - // Check whether the user has permissions to add personal storage backends - // return an empty array if this is not the case - if (OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes') !== 'yes') { - return array(); - } - - $backEnds = self::getBackends(); - - // Remove local storage and other disabled storages - unset($backEnds['\OC\Files\Storage\Local']); + public static function getPersonalMountPoints() { + $mountPoints = []; + $service = self::$app->getContainer()->query('OCA\Files_External\Service\UserStoragesService'); - $allowedBackEnds = explode(',', OCP\Config::getAppValue('files_external', 'user_mounting_backends', '')); - foreach ($backEnds as $backend => $null) { - if (!in_array($backend, $allowedBackEnds)) { - unset($backEnds[$backend]); - } + foreach ($service->getAllStorages() as $storage) { + $mountPoints[] = self::prepareMountPointEntry($storage, true); } - - return $backEnds; } /** - * Get the system mount points - * The returned array is not in the same format as getUserMountPoints() + * Convert a StorageConfig to the legacy mountPoints array format + * There's a lot of extra information in here, to satisfy all of the legacy functions * + * @param StorageConfig $storage + * @param bool $isPersonal * @return array */ - public static function getSystemMountPoints() { - $mountPoints = self::readData(); - $backends = self::getBackends(); - $system = array(); - if (isset($mountPoints[self::MOUNT_TYPE_GROUP])) { - foreach ($mountPoints[self::MOUNT_TYPE_GROUP] as $group => $mounts) { - foreach ($mounts as $mountPoint => $mount) { - // Update old classes to new namespace - if (strpos($mount['class'], 'OC_Filestorage_') !== false) { - $mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15); - } - $mount['options'] = self::decryptPasswords($mount['options']); - if (!isset($mount['priority'])) { - $mount['priority'] = $backends[$mount['class']]['priority']; - } - // Remove '/$user/files/' from mount point - $mountPoint = substr($mountPoint, 13); - - $config = array( - 'class' => $mount['class'], - 'mountpoint' => $mountPoint, - 'backend' => $backends[$mount['class']]['backend'], - 'priority' => $mount['priority'], - 'options' => $mount['options'], - 'applicable' => array('groups' => array($group), 'users' => array()) - ); - if (isset($mount['id'])) { - $config['id'] = (int)$mount['id']; - } - if (isset($mount['storage_id'])) { - $config['storage_id'] = (int)$mount['storage_id']; - } - if (isset($mount['mountOptions'])) { - $config['mountOptions'] = $mount['mountOptions']; - } - $hash = self::makeConfigHash($config); - // If an existing config exists (with same class, mountpoint and options) - if (isset($system[$hash])) { - // add the groups into that config - $system[$hash]['applicable']['groups'] - = array_merge($system[$hash]['applicable']['groups'], array($group)); - } else { - $system[$hash] = $config; - } - } - } - } - if (isset($mountPoints[self::MOUNT_TYPE_USER])) { - foreach ($mountPoints[self::MOUNT_TYPE_USER] as $user => $mounts) { - foreach ($mounts as $mountPoint => $mount) { - // Update old classes to new namespace - if (strpos($mount['class'], 'OC_Filestorage_') !== false) { - $mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15); - } - $mount['options'] = self::decryptPasswords($mount['options']); - if (!isset($mount['priority'])) { - $mount['priority'] = $backends[$mount['class']]['priority']; - } - // Remove '/$user/files/' from mount point - $mountPoint = substr($mountPoint, 13); - $config = array( - 'class' => $mount['class'], - 'mountpoint' => $mountPoint, - 'backend' => $backends[$mount['class']]['backend'], - 'priority' => $mount['priority'], - 'options' => $mount['options'], - 'applicable' => array('groups' => array(), 'users' => array($user)) - ); - if (isset($mount['id'])) { - $config['id'] = (int)$mount['id']; - } - if (isset($mount['storage_id'])) { - $config['storage_id'] = (int)$mount['storage_id']; - } - if (isset($mount['mountOptions'])) { - $config['mountOptions'] = $mount['mountOptions']; - } - $hash = self::makeConfigHash($config); - // If an existing config exists (with same class, mountpoint and options) - if (isset($system[$hash])) { - // add the users into that config - $system[$hash]['applicable']['users'] - = array_merge($system[$hash]['applicable']['users'], array($user)); - } else { - $system[$hash] = $config; - } - } - } - } - return array_values($system); + private static function prepareMountPointEntry(StorageConfig $storage, $isPersonal) { + $mountEntry = []; + + $mountEntry['mountpoint'] = substr($storage->getMountPoint(), 1); // remove leading slash + $mountEntry['class'] = $storage->getBackend()->getIdentifier(); + $mountEntry['backend'] = $storage->getBackend()->getText(); + $mountEntry['authMechanism'] = $storage->getAuthMechanism()->getIdentifier(); + $mountEntry['personal'] = $isPersonal; + $mountEntry['options'] = self::decryptPasswords($storage->getBackendOptions()); + $mountEntry['mountOptions'] = $storage->getMountOptions(); + $mountEntry['priority'] = $storage->getPriority(); + $mountEntry['applicable'] = [ + 'groups' => $storage->getApplicableGroups(), + 'users' => $storage->getApplicableUsers(), + ]; + $mountEntry['id'] = $storage->getId(); + // $mountEntry['storage_id'] = null; // we don't store this! + + return $mountEntry; } /** - * Get the personal mount points of the current user - * The returned array is not in the same format as getUserMountPoints() + * fill in the correct values for $user * - * @return array + * @param string $user user value + * @param string|array $input + * @return string */ - public static function getPersonalMountPoints() { - $mountPoints = self::readData(OCP\User::getUser()); - $backEnds = self::getBackends(); - $uid = OCP\User::getUser(); - $personal = array(); - if (isset($mountPoints[self::MOUNT_TYPE_USER][$uid])) { - foreach ($mountPoints[self::MOUNT_TYPE_USER][$uid] as $mountPoint => $mount) { - // Update old classes to new namespace - if (strpos($mount['class'], 'OC_Filestorage_') !== false) { - $mount['class'] = '\OC\Files\Storage\\' . substr($mount['class'], 15); - } - $mount['options'] = self::decryptPasswords($mount['options']); - $config = array( - 'class' => $mount['class'], - // Remove '/uid/files/' from mount point - 'mountpoint' => substr($mountPoint, strlen($uid) + 8), - 'backend' => $backEnds[$mount['class']]['backend'], - 'options' => $mount['options'] - ); - if (isset($mount['id'])) { - $config['id'] = (int)$mount['id']; - } - if (isset($mount['storage_id'])) { - $config['storage_id'] = (int)$mount['storage_id']; - } - if (isset($mount['mountOptions'])) { - $config['mountOptions'] = $mount['mountOptions']; + public static function setUserVars($user, $input) { + if (is_array($input)) { + foreach ($input as &$value) { + if (is_string($value)) { + $value = str_replace('$user', $user, $value); } - $personal[] = $config; } + } else { + $input = str_replace('$user', $user, $input); } - return $personal; + return $input; } /** @@ -515,163 +268,6 @@ class OC_Mount_Config { } /** - * Add a mount point to the filesystem - * - * @param string $mountPoint Mount point - * @param string $class Backend class - * @param array $classOptions Backend parameters for the class - * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER - * @param string $applicable User or group to apply mount to - * @param bool $isPersonal Personal or system mount point i.e. is this being called from the personal or admin page - * @param int|null $priority Mount point priority, null for default - * @return boolean - * - * @deprecated use StoragesService#addStorage() instead - */ - public static function addMountPoint($mountPoint, - $class, - $classOptions, - $mountType, - $applicable, - $isPersonal = false, - $priority = null) { - $backends = self::getBackends(); - $mountPoint = OC\Files\Filesystem::normalizePath($mountPoint); - $relMountPoint = $mountPoint; - if ($mountPoint === '' || $mountPoint === '/') { - // can't mount at root folder - return false; - } - - if (!isset($backends[$class])) { - // invalid backend - return false; - } - if ($isPersonal) { - // Verify that the mount point applies for the current user - // Prevent non-admin users from mounting local storage and other disabled backends - $allowed_backends = self::getPersonalBackends(); - if ($applicable != OCP\User::getUser() || !isset($allowed_backends[$class])) { - return false; - } - $mountPoint = '/' . $applicable . '/files/' . ltrim($mountPoint, '/'); - } else { - $mountPoint = '/$user/files/' . ltrim($mountPoint, '/'); - } - - $mount = array($applicable => array( - $mountPoint => array( - 'class' => $class, - 'options' => self::encryptPasswords($classOptions)) - ) - ); - if (!$isPersonal && !is_null($priority)) { - $mount[$applicable][$mountPoint]['priority'] = $priority; - } - - $mountPoints = self::readData($isPersonal ? OCP\User::getUser() : null); - // who else loves multi-dimensional array ? - $isNew = !isset($mountPoints[$mountType]) || - !isset($mountPoints[$mountType][$applicable]) || - !isset($mountPoints[$mountType][$applicable][$mountPoint]); - $mountPoints = self::mergeMountPoints($mountPoints, $mount, $mountType); - - // Set default priority if none set - if (!isset($mountPoints[$mountType][$applicable][$mountPoint]['priority'])) { - if (isset($backends[$class]['priority'])) { - $mountPoints[$mountType][$applicable][$mountPoint]['priority'] - = $backends[$class]['priority']; - } else { - $mountPoints[$mountType][$applicable][$mountPoint]['priority'] - = 100; - } - } - - self::writeData($isPersonal ? OCP\User::getUser() : null, $mountPoints); - - $result = self::getBackendStatus($class, $classOptions, $isPersonal); - if ($result === self::STATUS_SUCCESS && $isNew) { - \OC_Hook::emit( - \OC\Files\Filesystem::CLASSNAME, - \OC\Files\Filesystem::signal_create_mount, - array( - \OC\Files\Filesystem::signal_param_path => $relMountPoint, - \OC\Files\Filesystem::signal_param_mount_type => $mountType, - \OC\Files\Filesystem::signal_param_users => $applicable, - ) - ); - } - return $result; - } - - /** - * - * @param string $mountPoint Mount point - * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER - * @param string $applicable User or group to remove mount from - * @param bool $isPersonal Personal or system mount point - * @return bool - * - * @deprecated use StoragesService#removeStorage() instead - */ - public static function removeMountPoint($mountPoint, $mountType, $applicable, $isPersonal = false) { - // Verify that the mount point applies for the current user - $relMountPoints = $mountPoint; - if ($isPersonal) { - if ($applicable != OCP\User::getUser()) { - return false; - } - $mountPoint = '/' . $applicable . '/files/' . ltrim($mountPoint, '/'); - } else { - $mountPoint = '/$user/files/' . ltrim($mountPoint, '/'); - } - $mountPoint = \OC\Files\Filesystem::normalizePath($mountPoint); - $mountPoints = self::readData($isPersonal ? OCP\User::getUser() : null); - // Remove mount point - unset($mountPoints[$mountType][$applicable][$mountPoint]); - // Unset parent arrays if empty - if (empty($mountPoints[$mountType][$applicable])) { - unset($mountPoints[$mountType][$applicable]); - if (empty($mountPoints[$mountType])) { - unset($mountPoints[$mountType]); - } - } - self::writeData($isPersonal ? OCP\User::getUser() : null, $mountPoints); - \OC_Hook::emit( - \OC\Files\Filesystem::CLASSNAME, - \OC\Files\Filesystem::signal_delete_mount, - array( - \OC\Files\Filesystem::signal_param_path => $relMountPoints, - \OC\Files\Filesystem::signal_param_mount_type => $mountType, - \OC\Files\Filesystem::signal_param_users => $applicable, - ) - ); - return true; - } - - /** - * - * @param string $mountPoint Mount point - * @param string $target The new mount point - * @param string $mountType MOUNT_TYPE_GROUP | MOUNT_TYPE_USER - * @return bool - */ - public static function movePersonalMountPoint($mountPoint, $target, $mountType) { - $mountPoint = rtrim($mountPoint, '/'); - $user = OCP\User::getUser(); - $mountPoints = self::readData($user); - if (!isset($mountPoints[$mountType][$user][$mountPoint])) { - return false; - } - $mountPoints[$mountType][$user][$target] = $mountPoints[$mountType][$user][$mountPoint]; - // Remove old mount point - unset($mountPoints[$mountType][$user][$mountPoint]); - - self::writeData($user, $mountPoints); - return true; - } - - /** * Read the mount points in the config file into an array * * @param string|null $user If not null, personal for $user, otherwise system @@ -721,74 +317,35 @@ class OC_Mount_Config { } /** - * check dependencies + * Get backend dependency message + * TODO: move into AppFramework along with templates + * + * @param BackendConfig[] $backends + * @return string */ - public static function checkDependencies() { - $dependencies = array(); - foreach (OC_Mount_Config::$backends as $class => $backend) { - if (isset($backend['has_dependencies']) and $backend['has_dependencies'] === true) { - $result = $class::checkDependencies(); - if ($result !== true) { - if (!is_array($result)) { - $result = array($result); - } - foreach ($result as $key => $value) { - if (is_numeric($key)) { - OC_Mount_Config::addDependency($dependencies, $value, $backend['backend']); - } else { - OC_Mount_Config::addDependency($dependencies, $key, $backend['backend'], $value); - } - } - } - } - } - - if (count($dependencies) > 0) { - return OC_Mount_Config::generateDependencyMessage($dependencies); - } - return ''; - } - - private static function addDependency(&$dependencies, $module, $backend, $message = null) { - if (!isset($dependencies[$module])) { - $dependencies[$module] = array(); - } - - if ($message === null) { - $dependencies[$module][] = $backend; - } else { - $dependencies[$module][] = array('backend' => $backend, 'message' => $message); - } - } - - private static function generateDependencyMessage($dependencies) { + public static function dependencyMessage($backends) { $l = new \OC_L10N('files_external'); - $dependencyMessage = ''; - foreach ($dependencies as $module => $backends) { - $dependencyGroup = array(); - foreach ($backends as $backend) { - if (is_array($backend)) { - $dependencyMessage .= '<br />' . $l->t('<b>Note:</b> ') . $backend['message']; + $message = ''; + $dependencyGroups = []; + + foreach ($backends as $backend) { + foreach ($backend->checkDependencies() as $dependency) { + if ($message = $dependency->getMessage()) { + $message .= '<br />' . $l->t('<b>Note:</b> ') . $message; } else { - $dependencyGroup[] = $backend; + $dependencyGroups[$dependency->getDependency()][] = $backend; } } + } - $dependencyGroupCount = count($dependencyGroup); - if ($dependencyGroupCount > 0) { - $backends = ''; - for ($i = 0; $i < $dependencyGroupCount; $i++) { - if ($i > 0 && $i === $dependencyGroupCount - 1) { - $backends .= ' ' . $l->t('and') . ' '; - } elseif ($i > 0) { - $backends .= ', '; - } - $backends .= '<i>' . $dependencyGroup[$i] . '</i>'; - } - $dependencyMessage .= '<br />' . OC_Mount_Config::getSingleDependencyMessage($l, $module, $backends); - } + foreach ($dependencyGroups as $module => $dependants) { + $backends = implode(', ', array_map(function($backend) { + return '<i>' . $backend->getText() . '</i>'; + }, $dependants)); + $message .= '<br />' . OC_Mount_Config::getSingleDependencyMessage($l, $module, $backends); } - return $dependencyMessage; + + return $message; } /** @@ -918,7 +475,8 @@ class OC_Mount_Config { public static function makeConfigHash($config) { $data = json_encode( array( - 'c' => $config['class'], + 'c' => $config['backend'], + 'a' => $config['authMechanism'], 'm' => $config['mountpoint'], 'o' => $config['options'], 'p' => isset($config['priority']) ? $config['priority'] : -1, @@ -967,7 +525,8 @@ class OC_Mount_Config { return false; } - $class = $options['class']; + $service = self::$app->getContainer()->query('OCA\Files_External\Service\BackendService'); + $class = $service->getBackend($options['backend'])->getStorageClass(); try { /** @var \OC\Files\Storage\Storage $storage */ $storage = new $class($options['options']); diff --git a/apps/files_external/lib/config/configadapter.php b/apps/files_external/lib/config/configadapter.php index b5c2ba4fc92..a15d9e06a5f 100644 --- a/apps/files_external/lib/config/configadapter.php +++ b/apps/files_external/lib/config/configadapter.php @@ -2,6 +2,7 @@ /** * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <rmccorkell@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -22,16 +23,81 @@ namespace OCA\Files_External\Config; +use OCP\Files\Storage; use OC\Files\Mount\MountPoint; use OCP\Files\Storage\IStorageFactory; -use OCA\Files_External\PersonalMount; +use OCA\Files_External\Lib\PersonalMount; use OCP\Files\Config\IMountProvider; use OCP\IUser; +use OCA\Files_external\Service\UserStoragesService; +use OCA\Files_External\Service\UserGlobalStoragesService; +use OCA\Files_External\Lib\StorageConfig; +use OCP\Files\StorageNotAvailableException; +use OCA\Files_External\Lib\FailedStorage; /** * Make the old files_external config work with the new public mount config api */ class ConfigAdapter implements IMountProvider { + + /** @var UserStoragesService */ + private $userStoragesService; + + /** @var UserGlobalStoragesService */ + private $userGlobalStoragesService; + + /** + * @param UserStoragesService $userStoragesService + * @param UserGlobalStoragesService $userGlobalStoragesService + */ + public function __construct( + UserStoragesService $userStoragesService, + UserGlobalStoragesService $userGlobalStoragesService + ) { + $this->userStoragesService = $userStoragesService; + $this->userGlobalStoragesService = $userGlobalStoragesService; + } + + /** + * Process storage ready for mounting + * + * @param StorageConfig $storage + * @param IUser $user + */ + private function prepareStorageConfig(StorageConfig &$storage, IUser $user) { + foreach ($storage->getBackendOptions() as $option => $value) { + $storage->setBackendOption($option, \OC_Mount_Config::setUserVars( + $user->getUID(), $value + )); + } + + $objectStore = $storage->getBackendOption('objectstore'); + if ($objectStore) { + $objectClass = $objectStore['class']; + $storage->setBackendOption('objectstore', new $objectClass($objectStore)); + } + + $storage->getAuthMechanism()->manipulateStorageConfig($storage); + $storage->getBackend()->manipulateStorageConfig($storage); + } + + /** + * Construct the storage implementation + * + * @param StorageConfig $storageConfig + * @return Storage + */ + private function constructStorage(StorageConfig $storageConfig) { + $class = $storageConfig->getBackend()->getStorageClass(); + $storage = new $class($storageConfig->getBackendOptions()); + + // auth mechanism should fire first + $storage = $storageConfig->getBackend()->wrapStorage($storage); + $storage = $storageConfig->getAuthMechanism()->wrapStorage($storage); + + return $storage; + } + /** * Get all mountpoints applicable for the user * @@ -40,20 +106,54 @@ class ConfigAdapter implements IMountProvider { * @return \OCP\Files\Mount\IMountPoint[] */ public function getMountsForUser(IUser $user, IStorageFactory $loader) { - $mountPoints = \OC_Mount_Config::getAbsoluteMountPoints($user->getUID()); - $mounts = array(); - foreach ($mountPoints as $mountPoint => $options) { - if (isset($options['options']['objectstore'])) { - $objectClass = $options['options']['objectstore']['class']; - $options['options']['objectstore'] = new $objectClass($options['options']['objectstore']); + $mounts = []; + + $this->userStoragesService->setUser($user); + $this->userGlobalStoragesService->setUser($user); + + foreach ($this->userGlobalStoragesService->getAllStorages() as $storage) { + try { + $this->prepareStorageConfig($storage, $user); + $impl = $this->constructStorage($storage); + } catch (\Exception $e) { + // propagate exception into filesystem + $impl = new FailedStorage(['exception' => $e]); } - $mountOptions = isset($options['mountOptions']) ? $options['mountOptions'] : []; - if (isset($options['personal']) && $options['personal']) { - $mounts[] = new PersonalMount($options['class'], $mountPoint, $options['options'], $loader, $mountOptions); - } else { - $mounts[] = new MountPoint($options['class'], $mountPoint, $options['options'], $loader, $mountOptions); + + $mount = new MountPoint( + $impl, + '/'.$user->getUID().'/files' . $storage->getMountPoint(), + null, + $loader, + $storage->getMountOptions() + ); + $mounts[$storage->getMountPoint()] = $mount; + } + + foreach ($this->userStoragesService->getAllStorages() as $storage) { + try { + $this->prepareStorageConfig($storage, $user); + $impl = $this->constructStorage($storage); + } catch (\Exception $e) { + // propagate exception into filesystem + $impl = new FailedStorage(['exception' => $e]); } + + $mount = new PersonalMount( + $this->userStoragesService, + $storage->getId(), + $impl, + '/'.$user->getUID().'/files' . $storage->getMountPoint(), + null, + $loader, + $storage->getMountOptions() + ); + $mounts[$storage->getMountPoint()] = $mount; } + + $this->userStoragesService->resetUser(); + $this->userGlobalStoragesService->resetUser(); + return $mounts; } } diff --git a/apps/files_external/lib/definitionparameter.php b/apps/files_external/lib/definitionparameter.php new file mode 100644 index 00000000000..4b560908b69 --- /dev/null +++ b/apps/files_external/lib/definitionparameter.php @@ -0,0 +1,179 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +/** + * Parameter for an external storage definition + */ +class DefinitionParameter implements \JsonSerializable { + + /** Value constants */ + const VALUE_TEXT = 0; + const VALUE_BOOLEAN = 1; + const VALUE_PASSWORD = 2; + const VALUE_HIDDEN = 3; + + /** Flag constants */ + const FLAG_NONE = 0; + const FLAG_OPTIONAL = 1; + + /** @var string name of parameter */ + private $name; + + /** @var string human-readable parameter text */ + private $text; + + /** @var int value type, see self::VALUE_* constants */ + private $type = self::VALUE_TEXT; + + /** @var int flags, see self::FLAG_* constants */ + private $flags = self::FLAG_NONE; + + /** + * @param string $name + * @param string $text + */ + public function __construct($name, $text) { + $this->name = $name; + $this->text = $text; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @return string + */ + public function getText() { + return $this->text; + } + + /** + * Get value type + * + * @return int + */ + public function getType() { + return $this->type; + } + + /** + * Set value type + * + * @param int $type + * @return self + */ + public function setType($type) { + $this->type = $type; + return $this; + } + + /** + * @return int + */ + public function getFlags() { + return $this->flags; + } + + /** + * @param int $flags + * @return self + */ + public function setFlags($flags) { + $this->flags = $flags; + return $this; + } + + /** + * @param int $flag + * @return self + */ + public function setFlag($flag) { + $this->flags |= $flag; + return $this; + } + + /** + * @param int $flag + * @return bool + */ + public function isFlagSet($flag) { + return (bool) $this->flags & $flag; + } + + /** + * Serialize into JSON for client-side JS + * + * @return string + */ + public function jsonSerialize() { + $prefix = ''; + switch ($this->getType()) { + case self::VALUE_BOOLEAN: + $prefix = '!'; + break; + case self::VALUE_PASSWORD: + $prefix = '*'; + break; + case self::VALUE_HIDDEN: + $prefix = '#'; + break; + } + + switch ($this->getFlags()) { + case self::FLAG_OPTIONAL: + $prefix = '&' . $prefix; + break; + } + + return $prefix . $this->getText(); + } + + /** + * Validate a parameter value against this + * + * @param mixed $value Value to check + * @return bool success + */ + public function validateValue($value) { + if ($this->getFlags() & self::FLAG_OPTIONAL) { + return true; + } + switch ($this->getType()) { + case self::VALUE_BOOLEAN: + if (!is_bool($value)) { + return false; + } + break; + default: + if (empty($value)) { + return false; + } + break; + } + return true; + } +} diff --git a/apps/files_external/lib/dependencytrait.php b/apps/files_external/lib/dependencytrait.php new file mode 100644 index 00000000000..116421eab14 --- /dev/null +++ b/apps/files_external/lib/dependencytrait.php @@ -0,0 +1,86 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +use \OCA\Files_External\Lib\MissingDependency; + +/** + * Trait for objects that have dependencies for use + */ +trait DependencyTrait { + + /** @var callable|null dependency check */ + private $dependencyCheck = null; + + /** + * @return bool + */ + public function hasDependencies() { + return !is_null($this->dependencyCheck); + } + + /** + * @param callable $dependencyCheck + * @return self + */ + public function setDependencyCheck(callable $dependencyCheck) { + $this->dependencyCheck = $dependencyCheck; + return $this; + } + + /** + * Check if object is valid for use + * + * @return MissingDependency[] Unsatisfied dependencies + */ + public function checkDependencies() { + $ret = []; + + if ($this->hasDependencies()) { + $result = call_user_func($this->dependencyCheck); + if ($result !== true) { + if (!is_array($result)) { + $result = [$result]; + } + foreach ($result as $key => $value) { + if (!($value instanceof MissingDependency)) { + $module = null; + $message = null; + if (is_numeric($key)) { + $module = $value; + } else { + $module = $key; + $message = $value; + } + $value = new MissingDependency($module, $this); + $value->setMessage($message); + } + $ret[] = $value; + } + } + } + + return $ret; + } + +} + diff --git a/apps/files_external/lib/failedstorage.php b/apps/files_external/lib/failedstorage.php new file mode 100644 index 00000000000..6afa98052c2 --- /dev/null +++ b/apps/files_external/lib/failedstorage.php @@ -0,0 +1,200 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +use \OCP\Lock\ILockingProvider; +use \OC\Files\Storage\Common; +use \OCP\Files\StorageNotAvailableException; + +/** + * Storage placeholder to represent a missing precondition, storage unavailable + */ +class FailedStorage extends Common { + + /** @var \Exception */ + protected $e; + + /** + * @param array $params ['exception' => \Exception] + */ + public function __construct($params) { + $this->e = $params['exception']; + } + + public function getId() { + // we can't return anything sane here + return 'failedstorage'; + } + + public function mkdir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function rmdir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function opendir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function is_dir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function is_file($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function stat($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filetype($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filesize($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isCreatable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isReadable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isUpdatable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isDeletable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isSharable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getPermissions($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_exists($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filemtime($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_get_contents($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_put_contents($path, $data) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function unlink($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function rename($path1, $path2) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function copy($path1, $path2) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function fopen($path, $mode) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getMimeType($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function hash($type, $path, $raw = false) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function free_space($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function search($query) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function touch($path, $mtime = null) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getLocalFile($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getLocalFolder($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function hasUpdated($path, $time) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getETag($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getDirectDownload($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function verifyPath($path, $fileName) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function acquireLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function releaseLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function changeLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + +} diff --git a/apps/files_external/lib/frontenddefinitiontrait.php b/apps/files_external/lib/frontenddefinitiontrait.php new file mode 100644 index 00000000000..4b826372d2f --- /dev/null +++ b/apps/files_external/lib/frontenddefinitiontrait.php @@ -0,0 +1,147 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +use \OCA\Files_External\Lib\DefinitionParameter; +use \OCA\Files_External\Lib\StorageConfig; + +/** + * Trait for objects that have a frontend representation + */ +trait FrontendDefinitionTrait { + + /** @var string human-readable mechanism name */ + private $text; + + /** @var DefinitionParameter[] parameters for mechanism */ + private $parameters = []; + + /** @var string|null custom JS */ + private $customJs = null; + + /** + * @return string + */ + public function getText() { + return $this->text; + } + + /** + * @param string $text + * @return self + */ + public function setText($text) { + $this->text = $text; + return $this; + } + + /** + * @param FrontendDefinitionTrait $a + * @param FrontendDefinitionTrait $b + * @return int + */ + public static function lexicalCompare(FrontendDefinitionTrait $a, FrontendDefinitionTrait $b) { + return strcmp($a->getText(), $b->getText()); + } + + /** + * @return DefinitionParameter[] + */ + public function getParameters() { + return $this->parameters; + } + + /** + * @param DefinitionParameter[] $parameters + * @return self + */ + public function addParameters(array $parameters) { + foreach ($parameters as $parameter) { + $this->addParameter($parameter); + } + return $this; + } + + /** + * @param DefinitionParameter $parameter + * @return self + */ + public function addParameter(DefinitionParameter $parameter) { + $this->parameters[$parameter->getName()] = $parameter; + return $this; + } + + /** + * @return string|null + */ + public function getCustomJs() { + return $this->customJs; + } + + /** + * @param string $custom + * @return self + */ + public function setCustomJs($custom) { + $this->customJs = $custom; + return $this; + } + + /** + * Serialize into JSON for client-side JS + * + * @return array + */ + public function jsonSerializeDefinition() { + $configuration = []; + foreach ($this->getParameters() as $parameter) { + $configuration[$parameter->getName()] = $parameter; + } + + $data = [ + 'name' => $this->getText(), + 'configuration' => $configuration, + ]; + if (isset($this->customJs)) { + $data['custom'] = $this->getCustomJs(); + } + return $data; + } + + /** + * Check if parameters are satisfied in a StorageConfig + * + * @param StorageConfig $storage + * @return bool + */ + public function validateStorageDefinition(StorageConfig $storage) { + $options = $storage->getBackendOptions(); + foreach ($this->getParameters() as $name => $parameter) { + $value = isset($options[$name]) ? $options[$name] : null; + if (!$parameter->validateValue($value)) { + return false; + } + } + return true; + } + +} diff --git a/apps/files_external/lib/identifiertrait.php b/apps/files_external/lib/identifiertrait.php new file mode 100644 index 00000000000..139911580fc --- /dev/null +++ b/apps/files_external/lib/identifiertrait.php @@ -0,0 +1,68 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +/** + * Trait for objects requiring an identifier (and/or identifier aliases) + */ +trait IdentifierTrait { + + /** @var string */ + protected $identifier; + + /** @var string[] */ + protected $identifierAliases = []; + + /** + * @return string + */ + public function getIdentifier() { + return $this->identifier; + } + + /** + * @param string $identifier + * @return self + */ + public function setIdentifier($identifier) { + $this->identifier = $identifier; + $this->identifierAliases[] = $identifier; + return $this; + } + + /** + * @return string[] + */ + public function getIdentifierAliases() { + return $this->identifierAliases; + } + + /** + * @param string $alias + * @return self + */ + public function addIdentifierAlias($alias) { + $this->identifierAliases[] = $alias; + return $this; + } + +} diff --git a/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php b/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php new file mode 100644 index 00000000000..dd4cd75df12 --- /dev/null +++ b/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php @@ -0,0 +1,30 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +use \OCP\Files\StorageNotAvailableException; + +/** + * Authentication mechanism or backend has insufficient data + */ +class InsufficientDataForMeaningfulAnswerException extends StorageNotAvailableException { +} diff --git a/apps/files_external/lib/missingdependency.php b/apps/files_external/lib/missingdependency.php new file mode 100644 index 00000000000..9b25aeacc9b --- /dev/null +++ b/apps/files_external/lib/missingdependency.php @@ -0,0 +1,64 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +/** + * External storage backend dependency + */ +class MissingDependency { + + /** @var string */ + private $dependency; + + /** @var string|null Custom message */ + private $message = null; + + /** + * @param string $dependency + */ + public function __construct($dependency) { + $this->dependency = $dependency; + } + + /** + * @return string + */ + public function getDependency() { + return $this->dependency; + } + + /** + * @return string|null + */ + public function getMessage() { + return $this->message; + } + + /** + * @param string $message + * @return self + */ + public function setMessage($message) { + $this->message = $message; + return $this; + } +} diff --git a/apps/files_external/lib/personalmount.php b/apps/files_external/lib/personalmount.php index bbffc958641..d177f1a1ad0 100644 --- a/apps/files_external/lib/personalmount.php +++ b/apps/files_external/lib/personalmount.php @@ -20,15 +20,45 @@ * */ -namespace OCA\Files_External; +namespace OCA\Files_External\Lib; use OC\Files\Mount\MountPoint; use OC\Files\Mount\MoveableMount; +use OCA\Files_External\Service\UserStoragesService; /** * Person mount points can be moved by the user */ class PersonalMount extends MountPoint implements MoveableMount { + /** @var UserStoragesService */ + protected $storagesService; + + /** @var int */ + protected $storageId; + + /** + * @param UserStoragesService $storagesService + * @param int $storageId + * @param string|\OC\Files\Storage\Storage $storage + * @param string $mountpoint + * @param array $arguments (optional) configuration for the storage backend + * @param \OCP\Files\Storage\IStorageFactory $loader + * @param array $mountOptions mount specific options + */ + public function __construct( + UserStoragesService $storagesService, + $storageId, + $storage, + $mountpoint, + $arguments = null, + $loader = null, + $mountOptions = null + ) { + parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions); + $this->storagesService = $storagesService; + $this->storageId = $storageId; + } + /** * Move the mount point to $target * @@ -36,9 +66,13 @@ class PersonalMount extends MountPoint implements MoveableMount { * @return bool */ public function moveMount($target) { - $result = \OC_Mount_Config::movePersonalMountPoint($this->getMountPoint(), $target, \OC_Mount_Config::MOUNT_TYPE_USER); + $storage = $this->storagesService->getStorage($this->storageId); + // remove "/$user/files" prefix + $targetParts = explode('/', trim($target, '/'), 3); + $storage->setMountPoint($targetParts[2]); + $this->storagesService->updateStorage($storage); $this->setMountPoint($target); - return $result; + return true; } /** @@ -47,8 +81,7 @@ class PersonalMount extends MountPoint implements MoveableMount { * @return bool */ public function removeMount() { - $user = \OCP\User::getUser(); - $relativeMountPoint = substr($this->getMountPoint(), strlen('/' . $user . '/files/')); - return \OC_Mount_Config::removeMountPoint($relativeMountPoint, \OC_Mount_Config::MOUNT_TYPE_USER, $user , true); + $this->storagesService->removeStorage($this->storageId); + return true; } } diff --git a/apps/files_external/lib/prioritytrait.php b/apps/files_external/lib/prioritytrait.php new file mode 100644 index 00000000000..22f9fe275d8 --- /dev/null +++ b/apps/files_external/lib/prioritytrait.php @@ -0,0 +1,60 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +use \OCA\Files_External\Service\BackendService; + +/** + * Trait to implement priority mechanics for a configuration class + */ +trait PriorityTrait { + + /** @var int initial priority */ + protected $priority = BackendService::PRIORITY_DEFAULT; + + /** + * @return int + */ + public function getPriority() { + return $this->priority; + } + + /** + * @param int $priority + * @return self + */ + public function setPriority($priority) { + $this->priority = $priority; + return $this; + } + + /** + * @param PriorityTrait $a + * @param PriorityTrait $b + * @return int + */ + public static function priorityCompare(PriorityTrait $a, PriorityTrait $b) { + return ($a->getPriority() - $b->getPriority()); + } + +} + diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php index 92c27701d80..aeb8f527078 100644 --- a/apps/files_external/lib/storageconfig.php +++ b/apps/files_external/lib/storageconfig.php @@ -21,6 +21,9 @@ namespace OCA\Files_external\Lib; +use \OCA\Files_External\Lib\Backend\Backend; +use \OCA\Files_External\Lib\Auth\AuthMechanism; + /** * External storage configuration */ @@ -34,11 +37,18 @@ class StorageConfig implements \JsonSerializable { private $id; /** - * Backend class name + * Backend * - * @var string + * @var Backend */ - private $backendClass; + private $backend; + + /** + * Authentication mechanism + * + * @var AuthMechanism + */ + private $authMechanism; /** * Backend options @@ -138,21 +148,31 @@ class StorageConfig implements \JsonSerializable { } /** - * Returns the external storage backend class name - * - * @return string external storage backend class name + * @return Backend */ - public function getBackendClass() { - return $this->backendClass; + public function getBackend() { + return $this->backend; } /** - * Sets the external storage backend class name - * - * @param string external storage backend class name + * @param Backend + */ + public function setBackend(Backend $backend) { + $this->backend= $backend; + } + + /** + * @return AuthMechanism */ - public function setBackendClass($backendClass) { - $this->backendClass = $backendClass; + public function getAuthMechanism() { + return $this->authMechanism; + } + + /** + * @param AuthMechanism + */ + public function setAuthMechanism(AuthMechanism $authMechanism) { + $this->authMechanism = $authMechanism; } /** @@ -174,6 +194,25 @@ class StorageConfig implements \JsonSerializable { } /** + * @param string $key + * @return mixed + */ + public function getBackendOption($key) { + if (isset($this->backendOptions[$key])) { + return $this->backendOptions[$key]; + } + return null; + } + + /** + * @param string $key + * @param mixed $value + */ + public function setBackendOption($key, $value) { + $this->backendOptions[$key] = $value; + } + + /** * Returns the mount priority * * @return int priority @@ -283,7 +322,8 @@ class StorageConfig implements \JsonSerializable { $result['id'] = $this->id; } $result['mountPoint'] = $this->mountPoint; - $result['backendClass'] = $this->backendClass; + $result['backend'] = $this->backend->getIdentifier(); + $result['authMechanism'] = $this->authMechanism->getIdentifier(); $result['backendOptions'] = $this->backendOptions; if (!is_null($this->priority)) { $result['priority'] = $this->priority; diff --git a/apps/files_external/lib/storagemodifiertrait.php b/apps/files_external/lib/storagemodifiertrait.php new file mode 100644 index 00000000000..3af0bb234d9 --- /dev/null +++ b/apps/files_external/lib/storagemodifiertrait.php @@ -0,0 +1,67 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +use \OCP\Files\Storage; +use \OCA\Files_External\Lib\StorageConfig; +use \OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use \OCP\Files\StorageNotAvailableException; + +/** + * Trait for objects that can modify StorageConfigs and wrap Storages + * + * When a storage implementation is being prepared for use, the StorageConfig + * is passed through manipulateStorageConfig() to update any parameters as + * necessary. After the storage implementation has been constructed, it is + * passed through wrapStorage(), potentially replacing the implementation with + * a wrapped storage that changes its behaviour. + * + * Certain configuration options need to be set before the implementation is + * constructed, while others are retrieved directly from the storage + * implementation and so need a wrapper to be modified. + */ +trait StorageModifierTrait { + + /** + * Modify a StorageConfig parameters + * + * @param StorageConfig $storage + * @throws InsufficientDataForMeaningfulAnswerException + * @throws StorageNotAvailableException + */ + public function manipulateStorageConfig(StorageConfig &$storage) { + } + + /** + * Wrap a Storage if necessary + * + * @param Storage $storage + * @return Storage + * @throws InsufficientDataForMeaningfulAnswerException + * @throws StorageNotAvailableException + */ + public function wrapStorage(Storage $storage) { + return $storage; + } + +} + diff --git a/apps/files_external/lib/visibilitytrait.php b/apps/files_external/lib/visibilitytrait.php new file mode 100644 index 00000000000..dfd2d323ca6 --- /dev/null +++ b/apps/files_external/lib/visibilitytrait.php @@ -0,0 +1,136 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Lib; + +use \OCA\Files_External\Service\BackendService; + +/** + * Trait to implement visibility mechanics for a configuration class + * + * The standard visibility defines which users/groups can use or see the + * object. The allowed visibility defines the maximum visibility allowed to be + * set on the object. The standard visibility is often set dynamically by + * stored configuration parameters that can be modified by the administrator, + * while the allowed visibility is set directly by the object and cannot be + * modified by the administrator. + */ +trait VisibilityTrait { + + /** @var int visibility */ + protected $visibility = BackendService::VISIBILITY_DEFAULT; + + /** @var int allowed visibilities */ + protected $allowedVisibility = BackendService::VISIBILITY_DEFAULT; + + /** + * @return int + */ + public function getVisibility() { + return $this->visibility; + } + + /** + * Check if the backend is visible for a user type + * + * @param int $visibility + * @return bool + */ + public function isVisibleFor($visibility) { + if ($this->visibility & $visibility) { + return true; + } + return false; + } + + /** + * @param int $visibility + * @return self + */ + public function setVisibility($visibility) { + $this->visibility = $visibility; + $this->allowedVisibility |= $visibility; + return $this; + } + + /** + * @param int $visibility + * @return self + */ + public function addVisibility($visibility) { + return $this->setVisibility($this->visibility | $visibility); + } + + /** + * @param int $visibility + * @return self + */ + public function removeVisibility($visibility) { + return $this->setVisibility($this->visibility & ~$visibility); + } + + /** + * @return int + */ + public function getAllowedVisibility() { + return $this->allowedVisibility; + } + + /** + * Check if the backend is allowed to be visible for a user type + * + * @param int $allowedVisibility + * @return bool + */ + public function isAllowedVisibleFor($allowedVisibility) { + if ($this->allowedVisibility & $allowedVisibility) { + return true; + } + return false; + } + + /** + * @param int $allowedVisibility + * @return self + */ + public function setAllowedVisibility($allowedVisibility) { + $this->allowedVisibility = $allowedVisibility; + $this->visibility &= $allowedVisibility; + return $this; + } + + /** + * @param int $allowedVisibility + * @return self + */ + public function addAllowedVisibility($allowedVisibility) { + return $this->setAllowedVisibility($this->allowedVisibility | $allowedVisibility); + } + + /** + * @param int $allowedVisibility + * @return self + */ + public function removeAllowedVisibility($allowedVisibility) { + return $this->setAllowedVisibility($this->allowedVisibility & ~$allowedVisibility); + } + +} diff --git a/apps/files_external/personal.php b/apps/files_external/personal.php index 1ac0f65a1f0..8717d91d4f1 100644 --- a/apps/files_external/personal.php +++ b/apps/files_external/personal.php @@ -24,34 +24,34 @@ * */ +use \OCA\Files_External\Service\BackendService; + +// we must use the same container +$appContainer = \OC_Mount_Config::$app->getContainer(); +$backendService = $appContainer->query('OCA\Files_External\Service\BackendService'); +$userStoragesService = $appContainer->query('OCA\Files_external\Service\UserStoragesService'); + OCP\Util::addScript('files_external', 'settings'); OCP\Util::addStyle('files_external', 'settings'); -$backends = OC_Mount_Config::getPersonalBackends(); -$mounts = OC_Mount_Config::getPersonalMountPoints(); -$hasId = true; -foreach ($mounts as $mount) { - if (!isset($mount['id'])) { - // some mount points are missing ids - $hasId = false; - break; +$backends = $backendService->getBackendsVisibleFor(BackendService::VISIBILITY_PERSONAL); +$authMechanisms = $backendService->getAuthMechanismsVisibleFor(BackendService::VISIBILITY_PERSONAL); +foreach ($backends as $backend) { + if ($backend->getCustomJs()) { + \OCP\Util::addScript('files_external', $backend->getCustomJs()); } } - -if (!$hasId) { - $service = new \OCA\Files_external\Service\UserStoragesService(\OC::$server->getUserSession()); - // this will trigger the new storage code which will automatically - // generate storage config ids - $service->getAllStorages(); - // re-read updated config - $mounts = OC_Mount_Config::getPersonalMountPoints(); - // TODO: use the new storage config format in the template +foreach ($authMechanisms as $authMechanism) { + if ($authMechanism->getCustomJs()) { + \OCP\Util::addScript('files_external', $authMechanism->getCustomJs()); + } } $tmpl = new OCP\Template('files_external', 'settings'); $tmpl->assign('encryptionEnabled', \OC::$server->getEncryptionManager()->isEnabled()); $tmpl->assign('isAdminPage', false); -$tmpl->assign('mounts', $mounts); -$tmpl->assign('dependencies', OC_Mount_Config::checkDependencies()); +$tmpl->assign('storages', $userStoragesService->getAllStorages()); +$tmpl->assign('dependencies', OC_Mount_Config::dependencyMessage($backendService->getBackends())); $tmpl->assign('backends', $backends); +$tmpl->assign('authMechanisms', $authMechanisms); return $tmpl->fetchPage(); diff --git a/apps/files_external/service/backendservice.php b/apps/files_external/service/backendservice.php new file mode 100644 index 00000000000..bee08ecbd15 --- /dev/null +++ b/apps/files_external/service/backendservice.php @@ -0,0 +1,275 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Service; + +use \OCP\IConfig; + +use \OCA\Files_External\Lib\Backend\Backend; +use \OCA\Files_External\Lib\Auth\AuthMechanism; + +/** + * Service class to manage backend definitions + */ +class BackendService { + + /** Visibility constants for VisibilityTrait */ + const VISIBILITY_NONE = 0; + const VISIBILITY_PERSONAL = 1; + const VISIBILITY_ADMIN = 2; + //const VISIBILITY_ALIENS = 4; + + const VISIBILITY_DEFAULT = 3; // PERSONAL | ADMIN + + /** Priority constants for PriorityTrait */ + const PRIORITY_DEFAULT = 100; + + /** @var IConfig */ + protected $config; + + /** @var bool */ + private $userMountingAllowed = true; + + /** @var string[] */ + private $userMountingBackends = []; + + /** @var Backend[] */ + private $backends = []; + + /** @var AuthMechanism[] */ + private $authMechanisms = []; + + /** + * @param IConfig $config + */ + public function __construct( + IConfig $config + ) { + $this->config = $config; + + // Load config values + if ($this->config->getAppValue('files_external', 'allow_user_mounting', 'yes') !== 'yes') { + $this->userMountingAllowed = false; + } + $this->userMountingBackends = explode(',', + $this->config->getAppValue('files_external', 'user_mounting_backends', '') + ); + } + + /** + * Register a backend + * + * @param Backend $backend + */ + public function registerBackend(Backend $backend) { + if (!$this->isAllowedUserBackend($backend)) { + $backend->removeVisibility(BackendService::VISIBILITY_PERSONAL); + } + foreach ($backend->getIdentifierAliases() as $alias) { + $this->backends[$alias] = $backend; + } + } + + /** + * @param Backend[] $backends + */ + public function registerBackends(array $backends) { + foreach ($backends as $backend) { + $this->registerBackend($backend); + } + } + /** + * Register an authentication mechanism + * + * @param AuthMechanism $authMech + */ + public function registerAuthMechanism(AuthMechanism $authMech) { + if (!$this->isAllowedAuthMechanism($authMech)) { + $authMech->removeVisibility(BackendService::VISIBILITY_PERSONAL); + } + foreach ($authMech->getIdentifierAliases() as $alias) { + $this->authMechanisms[$alias] = $authMech; + } + } + + /** + * @param AuthMechanism[] $mechanisms + */ + public function registerAuthMechanisms(array $mechanisms) { + foreach ($mechanisms as $mechanism) { + $this->registerAuthMechanism($mechanism); + } + } + + /** + * Get all backends + * + * @return Backend[] + */ + public function getBackends() { + // only return real identifiers, no aliases + $backends = []; + foreach ($this->backends as $backend) { + $backends[$backend->getIdentifier()] = $backend; + } + return $backends; + } + + /** + * Get all available backends + * + * @return Backend[] + */ + public function getAvailableBackends() { + return array_filter($this->getBackends(), function($backend) { + return empty($backend->checkDependencies()); + }); + } + + /** + * Get backends visible for $visibleFor + * + * @param int $visibleFor + * @return Backend[] + */ + public function getBackendsVisibleFor($visibleFor) { + return array_filter($this->getAvailableBackends(), function($backend) use ($visibleFor) { + return $backend->isVisibleFor($visibleFor); + }); + } + + /** + * Get backends allowed to be visible for $visibleFor + * + * @param int $visibleFor + * @return Backend[] + */ + public function getBackendsAllowedVisibleFor($visibleFor) { + return array_filter($this->getAvailableBackends(), function($backend) use ($visibleFor) { + return $backend->isAllowedVisibleFor($visibleFor); + }); + } + + /** + * @param string $identifier + * @return Backend|null + */ + public function getBackend($identifier) { + if (isset($this->backends[$identifier])) { + return $this->backends[$identifier]; + } + return null; + } + + /** + * Get all authentication mechanisms + * + * @return AuthMechanism[] + */ + public function getAuthMechanisms() { + // only return real identifiers, no aliases + $mechanisms = []; + foreach ($this->authMechanisms as $mechanism) { + $mechanisms[$mechanism->getIdentifier()] = $mechanism; + } + return $mechanisms; + } + + /** + * Get all authentication mechanisms for schemes + * + * @param string[] $schemes + * @return AuthMechanism[] + */ + public function getAuthMechanismsByScheme(array $schemes) { + return array_filter($this->getAuthMechanisms(), function($authMech) use ($schemes) { + return in_array($authMech->getScheme(), $schemes, true); + }); + } + + /** + * Get authentication mechanisms visible for $visibleFor + * + * @param int $visibleFor + * @return AuthMechanism[] + */ + public function getAuthMechanismsVisibleFor($visibleFor) { + return array_filter($this->getAuthMechanisms(), function($authMechanism) use ($visibleFor) { + return $authMechanism->isVisibleFor($visibleFor); + }); + } + + /** + * Get authentication mechanisms allowed to be visible for $visibleFor + * + * @param int $visibleFor + * @return AuthMechanism[] + */ + public function getAuthMechanismsAllowedVisibleFor($visibleFor) { + return array_filter($this->getAuthMechanisms(), function($authMechanism) use ($visibleFor) { + return $authMechanism->isAllowedVisibleFor($visibleFor); + }); + } + + + /** + * @param string $identifier + * @return AuthMechanism|null + */ + public function getAuthMechanism($identifier) { + if (isset($this->authMechanisms[$identifier])) { + return $this->authMechanisms[$identifier]; + } + return null; + } + + /** + * @return bool + */ + public function isUserMountingAllowed() { + return $this->userMountingAllowed; + } + + /** + * Check a backend if a user is allowed to mount it + * + * @param Backend $backend + * @return bool + */ + protected function isAllowedUserBackend(Backend $backend) { + if ($this->userMountingAllowed && + !empty(array_intersect($backend->getIdentifierAliases(), $this->userMountingBackends)) + ) { + return true; + } + return false; + } + + /** + * Check an authentication mechanism if a user is allowed to use it + * + * @param AuthMechanism $authMechanism + * @return bool + */ + protected function isAllowedAuthMechanism(AuthMechanism $authMechanism) { + return true; // not implemented + } +} diff --git a/apps/files_external/service/globalstoragesservice.php b/apps/files_external/service/globalstoragesservice.php index 04445127b34..0e2d3f2b9c1 100644 --- a/apps/files_external/service/globalstoragesservice.php +++ b/apps/files_external/service/globalstoragesservice.php @@ -92,7 +92,7 @@ class GlobalStoragesService extends StoragesService { $storageConfig->setBackendOptions($oldBackendOptions); } - \OC_Mount_Config::writeData(null, $mountPoints); + $this->writeLegacyConfig($mountPoints); } /** diff --git a/apps/files_external/service/storagesservice.php b/apps/files_external/service/storagesservice.php index 930f994455e..e89af6bc756 100644 --- a/apps/files_external/service/storagesservice.php +++ b/apps/files_external/service/storagesservice.php @@ -28,12 +28,23 @@ use \OC\Files\Filesystem; use \OCA\Files_external\Lib\StorageConfig; use \OCA\Files_external\NotFoundException; +use \OCA\Files_External\Service\BackendService; /** * Service class to manage external storages */ abstract class StoragesService { + /** @var BackendService */ + protected $backendService; + + /** + * @param BackendService $backendService + */ + public function __construct(BackendService $backendService) { + $this->backendService = $backendService; + } + /** * Read legacy config data * @@ -45,6 +56,16 @@ abstract class StoragesService { } /** + * Write legacy config data + * + * @param array $mountPoints + */ + protected function writeLegacyConfig(array $mountPoints) { + // write global config + \OC_Mount_Config::writeData(null, $mountPoints); + } + + /** * Copy legacy storage options into the given storage config object. * * @param StorageConfig $storageConfig storage config to populate @@ -60,14 +81,31 @@ abstract class StoragesService { $applicable, $storageOptions ) { - $storageConfig->setBackendClass($storageOptions['class']); + $backend = $this->backendService->getBackend($storageOptions['backend']); + if (!$backend) { + throw new \UnexpectedValueException('Invalid backend '.$storageOptions['backend']); + } + $storageConfig->setBackend($backend); + + if (isset($storageOptions['authMechanism'])) { + $authMechanism = $this->backendService->getAuthMechanism($storageOptions['authMechanism']); + } else { + $authMechanism = $backend->getLegacyAuthMechanism($storageOptions); + $storageOptions['authMechanism'] = 'null'; // to make error handling easier + } + if (!$authMechanism) { + throw new \UnexpectedValueException('Invalid authentication mechanism '.$storageOptions['authMechanism']); + } + $storageConfig->setAuthMechanism($authMechanism); + $storageConfig->setBackendOptions($storageOptions['options']); if (isset($storageOptions['mountOptions'])) { $storageConfig->setMountOptions($storageOptions['mountOptions']); } - if (isset($storageOptions['priority'])) { - $storageConfig->setPriority($storageOptions['priority']); + if (!isset($storageOptions['priority'])) { + $storageOptions['priority'] = $backend->getPriority(); } + $storageConfig->setPriority($storageOptions['priority']); if ($mountType === \OC_Mount_Config::MOUNT_TYPE_USER) { $applicableUsers = $storageConfig->getApplicableUsers(); @@ -102,8 +140,10 @@ abstract class StoragesService { * - $mountPath is the mount point path (where the storage must be mounted) * - $storageOptions is a map of storage options: * - "priority": storage priority - * - "backend": backend class name + * - "backend": backend identifier + * - "class": LEGACY backend class name * - "options": backend-specific options + * - "authMechanism": authentication mechanism identifier * - "mountOptions": mount-specific options (ex: disable previews, scanner, etc) */ @@ -147,6 +187,13 @@ abstract class StoragesService { // options might be needed for the config hash $storageOptions['options'] = \OC_Mount_Config::decryptPasswords($storageOptions['options']); + if (!isset($storageOptions['backend'])) { + $storageOptions['backend'] = $storageOptions['class']; // legacy compat + } + if (!isset($storageOptions['authMechanism'])) { + $storageOptions['authMechanism'] = null; // ensure config hash works + } + if (isset($storageOptions['id'])) { $configId = (int)$storageOptions['id']; if (isset($storages[$configId])) { @@ -188,21 +235,31 @@ abstract class StoragesService { // process storages with config hash, they must get a real id if (!empty($storagesWithConfigHash)) { - $nextId = $this->generateNextId($storages); - foreach ($storagesWithConfigHash as $storage) { - $storage->setId($nextId); - $storages[$nextId] = $storage; - $nextId++; - } - - // re-save the config with the generated ids - $this->writeConfig($storages); + $this->setRealStorageIds($storages, $storagesWithConfigHash); } return $storages; } /** + * Replace config hash ID with real IDs, for migrating legacy storages + * + * @param StorageConfig[] $storages Storages with real IDs + * @param StorageConfig[] $storagesWithConfigHash Storages with config hash IDs + */ + protected function setRealStorageIds(array &$storages, array $storagesWithConfigHash) { + $nextId = $this->generateNextId($storages); + foreach ($storagesWithConfigHash as $storage) { + $storage->setId($nextId); + $storages[$nextId] = $storage; + $nextId++; + } + + // re-save the config with the generated ids + $this->writeConfig($storages); + } + + /** * Add mount point into the messy mount point structure * * @param array $mountPoints messy array of mount points @@ -222,7 +279,9 @@ abstract class StoragesService { $options = [ 'id' => $storageConfig->getId(), - 'class' => $storageConfig->getBackendClass(), + 'backend' => $storageConfig->getBackend()->getIdentifier(), + //'class' => $storageConfig->getBackend()->getClass(), + 'authMechanism' => $storageConfig->getAuthMechanism()->getIdentifier(), 'options' => $storageConfig->getBackendOptions(), ]; @@ -297,6 +356,59 @@ abstract class StoragesService { } /** + * Create a storage from its parameters + * + * @param string $mountPoint storage mount point + * @param string $backendIdentifier backend identifier + * @param string $authMechanismIdentifier authentication mechanism identifier + * @param array $backendOptions backend-specific options + * @param array|null $mountOptions mount-specific options + * @param array|null $applicableUsers users for which to mount the storage + * @param array|null $applicableGroups groups for which to mount the storage + * @param int|null $priority priority + * + * @return StorageConfig + */ + public function createStorage( + $mountPoint, + $backendIdentifier, + $authMechanismIdentifier, + $backendOptions, + $mountOptions = null, + $applicableUsers = null, + $applicableGroups = null, + $priority = null + ) { + $backend = $this->backendService->getBackend($backendIdentifier); + if (!$backend) { + throw new \InvalidArgumentException('Unable to get backend for '.$backendIdentifier); + } + $authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier); + if (!$authMechanism) { + throw new \InvalidArgumentException('Unable to get authentication mechanism for '.$authMechanismIdentifier); + } + $newStorage = new StorageConfig(); + $newStorage->setMountPoint($mountPoint); + $newStorage->setBackend($backend); + $newStorage->setAuthMechanism($authMechanism); + $newStorage->setBackendOptions($backendOptions); + if (isset($mountOptions)) { + $newStorage->setMountOptions($mountOptions); + } + if (isset($applicableUsers)) { + $newStorage->setApplicableUsers($applicableUsers); + } + if (isset($applicableGroups)) { + $newStorage->setApplicableGroups($applicableGroups); + } + if (isset($priority)) { + $newStorage->setPriority($priority); + } + + return $newStorage; + } + + /** * Triggers the given hook signal for all the applicables given * * @param string $signal signal diff --git a/apps/files_external/service/userglobalstoragesservice.php b/apps/files_external/service/userglobalstoragesservice.php new file mode 100644 index 00000000000..78520419556 --- /dev/null +++ b/apps/files_external/service/userglobalstoragesservice.php @@ -0,0 +1,107 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Service; + +use \OCA\Files_external\Service\GlobalStoragesService; +use \OCA\Files_External\Service\BackendService; +use \OCP\IUserSession; +use \OCP\IGroupManager; +use \OCA\Files_External\Service\UserTrait; + +/** + * Service class to read global storages applicable to the user + * Read-only access available, attempting to write will throw DomainException + */ +class UserGlobalStoragesService extends GlobalStoragesService { + + use UserTrait; + + /** @var IGroupManager */ + protected $groupManager; + + /** + * @param BackendService $backendService + * @param IUserSession $userSession + * @param IGroupManager $groupManager + */ + public function __construct( + BackendService $backendService, + IUserSession $userSession, + IGroupManager $groupManager + ) { + parent::__construct($backendService); + $this->userSession = $userSession; + $this->groupManager = $groupManager; + } + + /** + * Replace config hash ID with real IDs, for migrating legacy storages + * + * @param StorageConfig[] $storages Storages with real IDs + * @param StorageConfig[] $storagesWithConfigHash Storages with config hash IDs + */ + protected function setRealStorageIds(array &$storages, array $storagesWithConfigHash) { + // as a read-only view, storage IDs don't need to be real + foreach ($storagesWithConfigHash as $storage) { + $storages[$storage->getId()] = $storage; + } + } + + /** + * Read legacy config data + * + * @return array list of mount configs + */ + protected function readLegacyConfig() { + // read global config + $data = parent::readLegacyConfig(); + $userId = $this->getUser()->getUID(); + + if (isset($data[\OC_Mount_Config::MOUNT_TYPE_USER])) { + $data[\OC_Mount_Config::MOUNT_TYPE_USER] = array_filter( + $data[\OC_Mount_Config::MOUNT_TYPE_USER], function($key) use ($userId) { + return (strtolower($key) === strtolower($userId) || $key === 'all'); + }, ARRAY_FILTER_USE_KEY + ); + } + + if (isset($data[\OC_Mount_Config::MOUNT_TYPE_GROUP])) { + $data[\OC_Mount_Config::MOUNT_TYPE_GROUP] = array_filter( + $data[\OC_Mount_Config::MOUNT_TYPE_GROUP], function($key) use ($userId) { + return ($this->groupManager->isInGroup($userId, $key)); + }, ARRAY_FILTER_USE_KEY + ); + } + + return $data; + } + + /** + * Write legacy config data + * + * @param array $mountPoints + */ + protected function writeLegacyConfig(array $mountPoints) { + throw new \DomainException('UserGlobalStoragesService writing disallowed'); + } + +} diff --git a/apps/files_external/service/userstoragesservice.php b/apps/files_external/service/userstoragesservice.php index 2f2556043fe..c69b8d4f51e 100644 --- a/apps/files_external/service/userstoragesservice.php +++ b/apps/files_external/service/userstoragesservice.php @@ -26,6 +26,8 @@ use \OC\Files\Filesystem; use \OCA\Files_external\Lib\StorageConfig; use \OCA\Files_external\NotFoundException; +use \OCA\Files_External\Service\BackendService; +use \OCA\Files_External\Service\UserTrait; /** * Service class to manage user external storages @@ -33,22 +35,20 @@ use \OCA\Files_external\NotFoundException; */ class UserStoragesService extends StoragesService { - /** - * User session - * - * @var IUserSession - */ - private $userSession; + use UserTrait; /** * Create a user storages service * + * @param BackendService $backendService * @param IUserSession $userSession user session */ public function __construct( + BackendService $backendService, IUserSession $userSession ) { $this->userSession = $userSession; + parent::__construct($backendService); } /** @@ -58,17 +58,28 @@ class UserStoragesService extends StoragesService { */ protected function readLegacyConfig() { // read user config - $user = $this->userSession->getUser()->getUID(); + $user = $this->getUser()->getUID(); return \OC_Mount_Config::readData($user); } /** + * Write legacy config data + * + * @param array $mountPoints + */ + protected function writeLegacyConfig(array $mountPoints) { + // write user config + $user = $this->getUser()->getUID(); + \OC_Mount_Config::writeData($user, $mountPoints); + } + + /** * Read the external storages config * * @return array map of storage id to storage config */ protected function readConfig() { - $user = $this->userSession->getUser()->getUID(); + $user = $this->getUser()->getUID(); // TODO: in the future don't rely on the global config reading code $storages = parent::readConfig(); @@ -95,7 +106,7 @@ class UserStoragesService extends StoragesService { * @param array $storages map of storage id to storage config */ public function writeConfig($storages) { - $user = $this->userSession->getUser()->getUID(); + $user = $this->getUser()->getUID(); // let the horror begin $mountPoints = []; @@ -123,7 +134,7 @@ class UserStoragesService extends StoragesService { $storageConfig->setBackendOptions($oldBackendOptions); } - \OC_Mount_Config::writeData($user, $mountPoints); + $this->writeLegacyConfig($mountPoints); } /** @@ -134,7 +145,7 @@ class UserStoragesService extends StoragesService { * @param string $signal signal to trigger */ protected function triggerHooks(StorageConfig $storage, $signal) { - $user = $this->userSession->getUser()->getUID(); + $user = $this->getUser()->getUID(); // trigger hook for the current user $this->triggerApplicableHooks( diff --git a/apps/files_external/service/usertrait.php b/apps/files_external/service/usertrait.php new file mode 100644 index 00000000000..4f84543565c --- /dev/null +++ b/apps/files_external/service/usertrait.php @@ -0,0 +1,74 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Service; + +use \OCP\IUserSession; +use \OCP\IUser; + +/** + * Trait for getting user information in a service + */ +trait UserTrait { + + /** @var IUserSession */ + protected $userSession; + + /** + * User override + * + * @var IUser|null + */ + private $user = null; + + /** + * @return IUser|null + */ + protected function getUser() { + if ($this->user) { + return $this->user; + } + return $this->userSession->getUser(); + } + + /** + * Override the user from the session + * Unset with ->resetUser() when finished! + * + * @param IUser + * @return self + */ + public function setUser(IUser $user) { + $this->user = $user; + return $this; + } + + /** + * Reset the user override + * + * @return self + */ + public function resetUser() { + $this->user = null; + return $this; + } +} + diff --git a/apps/files_external/settings.php b/apps/files_external/settings.php index 6db68713d98..9cecc0c6a49 100644 --- a/apps/files_external/settings.php +++ b/apps/files_external/settings.php @@ -26,54 +26,41 @@ * */ +use \OCA\Files_External\Service\BackendService; + OC_Util::checkAdminUser(); +// we must use the same container +$appContainer = \OC_Mount_Config::$app->getContainer(); +$backendService = $appContainer->query('OCA\Files_External\Service\BackendService'); +$globalStoragesService = $appContainer->query('OCA\Files_external\Service\GlobalStoragesService'); + OCP\Util::addScript('files_external', 'settings'); OCP\Util::addStyle('files_external', 'settings'); \OC_Util::addVendorScript('select2/select2'); \OC_Util::addVendorStyle('select2/select2'); -$backends = OC_Mount_Config::getBackends(); -$personal_backends = array(); -$enabled_backends = explode(',', OCP\Config::getAppValue('files_external', 'user_mounting_backends', '')); -foreach ($backends as $class => $backend) -{ - if ($class != '\OC\Files\Storage\Local') - { - $personal_backends[$class] = array( - 'backend' => $backend['backend'], - 'enabled' => in_array($class, $enabled_backends), - ); +$backends = $backendService->getBackendsVisibleFor(BackendService::VISIBILITY_ADMIN); +$authMechanisms = $backendService->getAuthMechanismsVisibleFor(BackendService::VISIBILITY_ADMIN); +foreach ($backends as $backend) { + if ($backend->getCustomJs()) { + \OCP\Util::addScript('files_external', $backend->getCustomJs()); } } - -$mounts = OC_Mount_Config::getSystemMountPoints(); -$hasId = true; -foreach ($mounts as $mount) { - if (!isset($mount['id'])) { - // some mount points are missing ids - $hasId = false; - break; +foreach ($authMechanisms as $authMechanism) { + if ($authMechanism->getCustomJs()) { + \OCP\Util::addScript('files_external', $authMechanism->getCustomJs()); } } -if (!$hasId) { - $service = new \OCA\Files_external\Service\GlobalStoragesService(); - // this will trigger the new storage code which will automatically - // generate storage config ids - $service->getAllStorages(); - // re-read updated config - $mounts = OC_Mount_Config::getSystemMountPoints(); - // TODO: use the new storage config format in the template -} - $tmpl = new OCP\Template('files_external', 'settings'); $tmpl->assign('encryptionEnabled', \OC::$server->getEncryptionManager()->isEnabled()); $tmpl->assign('isAdminPage', true); -$tmpl->assign('mounts', $mounts); +$tmpl->assign('storages', $globalStoragesService->getAllStorages()); $tmpl->assign('backends', $backends); -$tmpl->assign('personal_backends', $personal_backends); -$tmpl->assign('dependencies', OC_Mount_Config::checkDependencies()); -$tmpl->assign('allowUserMounting', OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes')); +$tmpl->assign('authMechanisms', $authMechanisms); +$tmpl->assign('userBackends', $backendService->getBackendsAllowedVisibleFor(BackendService::VISIBILITY_PERSONAL)); +$tmpl->assign('dependencies', OC_Mount_Config::dependencyMessage($backendService->getBackends())); +$tmpl->assign('allowUserMounting', $backendService->isUserMountingAllowed()); return $tmpl->fetchPage(); diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php index b886c2e1b1b..611c5807a5f 100644 --- a/apps/files_external/templates/settings.php +++ b/apps/files_external/templates/settings.php @@ -1,3 +1,58 @@ +<?php + use \OCA\Files_External\Lib\Backend\Backend; + use \OCA\Files_External\Lib\DefinitionParameter; + use \OCA\Files_External\Service\BackendService; + + function writeParameterInput($parameter, $options, $classes = []) { + $value = ''; + if (isset($options[$parameter->getName()])) { + $value = $options[$parameter->getName()]; + } + $placeholder = $parameter->getText(); + $is_optional = $parameter->isFlagSet(DefinitionParameter::FLAG_OPTIONAL); + + switch ($parameter->getType()) { + case DefinitionParameter::VALUE_PASSWORD: ?> + <?php if ($is_optional) { $classes[] = 'optional'; } ?> + <input type="password" + <?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?> + data-parameter="<?php p($parameter->getName()); ?>" + value="<?php p($value); ?>" + placeholder="<?php p($placeholder); ?>" + /> + <?php + break; + case DefinitionParameter::VALUE_BOOLEAN: ?> + <label> + <input type="checkbox" + <?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?> + data-parameter="<?php p($parameter->getName()); ?>" + <?php if ($value == 'true'): ?> checked="checked"<?php endif; ?> + /> + <?php p($placeholder); ?> + </label> + <?php + break; + case DefinitionParameter::VALUE_HIDDEN: ?> + <input type="hidden" + <?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?> + data-parameter="<?php p($parameter->getName()); ?>" + value="<?php p($value); ?>" + /> + <?php + break; + default: ?> + <?php if ($is_optional) { $classes[] = 'optional'; } ?> + <input type="text" + <?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?> + data-parameter="<?php p($parameter->getName()); ?>" + value="<?php p($value); ?>" + placeholder="<?php p($placeholder); ?>" + /> + <?php + } + } +?> <form id="files_external" class="section" data-encryption-enabled="<?php echo $_['encryptionEnabled']?'true': 'false'; ?>"> <h2><?php p($l->t('External Storage')); ?></h2> <?php if (isset($_['dependencies']) and ($_['dependencies']<>'')) print_unescaped(''.$_['dependencies'].''); ?> @@ -7,6 +62,7 @@ <th></th> <th><?php p($l->t('Folder name')); ?></th> <th><?php p($l->t('External storage')); ?></th> + <th><?php p($l->t('Authentication')); ?></th> <th><?php p($l->t('Configuration')); ?></th> <?php if ($_['isAdminPage']) print_unescaped('<th>'.$l->t('Available for').'</th>'); ?> <th> </th> @@ -14,103 +70,120 @@ </tr> </thead> <tbody> - <?php $_['mounts'] = array_merge($_['mounts'], array('' => array('id' => ''))); ?> - <?php foreach ($_['mounts'] as $mount): ?> - <tr <?php print_unescaped(isset($mount['mountpoint']) ? 'class="'.OC_Util::sanitizeHTML($mount['class']).'"' : 'id="addMountPoint"'); ?> data-id="<?php p($mount['id']) ?>"> + <?php foreach ($_['storages'] as $storage): ?> + <tr class="<?php p($storage->getBackend()->getIdentifier()); ?>" data-id="<?php p($storage->getId()); ?>"> <td class="status"> <span></span> </td> <td class="mountPoint"><input type="text" name="mountPoint" - value="<?php p(isset($mount['mountpoint']) ? $mount['mountpoint'] : ''); ?>" - data-mountpoint="<?php p(isset($mount['mountpoint']) ? $mount['mountpoint'] : ''); ?>" + value="<?php p(ltrim($storage->getMountPoint(), '/')); ?>" + data-mountpoint="<?php p(ltrim($storage->getMountPoint(), '/')); ?>" placeholder="<?php p($l->t('Folder name')); ?>" /> </td> - <?php if (!isset($mount['mountpoint'])): ?> - <td class="backend"> - <select id="selectBackend" class="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'> - <option value="" disabled selected - style="display:none;"><?php p($l->t('Add storage')); ?></option> - <?php foreach ($_['backends'] as $class => $backend): ?> - <option value="<?php p($class); ?>"><?php p($backend['backend']); ?></option> - <?php endforeach; ?> - </select> - </td> - <?php else: ?> - <td class="backend" data-class="<?php p($mount['class']); ?>"><?php p($mount['backend']); ?> - </td> - <?php endif; ?> - <td class ="configuration"> - <?php if (isset($mount['options'])): ?> - <?php foreach ($mount['options'] as $parameter => $value): ?> - <?php if (isset($_['backends'][$mount['class']]['configuration'][$parameter])): ?> - <?php - $placeholder = $_['backends'][$mount['class']]['configuration'][$parameter]; - $is_optional = FALSE; - if (strpos($placeholder, '&') === 0) { - $is_optional = TRUE; - $placeholder = substr($placeholder, 1); - } - ?> - <?php if (strpos($placeholder, '*') === 0): ?> - <input type="password" - <?php if ($is_optional): ?> class="optional"<?php endif; ?> - data-parameter="<?php p($parameter); ?>" - value="<?php p($value); ?>" - placeholder="<?php p(substr($placeholder, 1)); ?>" /> - <?php elseif (strpos($placeholder, '!') === 0): ?> - <label><input type="checkbox" - data-parameter="<?php p($parameter); ?>" - <?php if ($value == 'true'): ?> checked="checked"<?php endif; ?> - /><?php p(substr($placeholder, 1)); ?></label> - <?php elseif (strpos($placeholder, '#') === 0): ?> - <input type="hidden" - data-parameter="<?php p($parameter); ?>" - value="<?php p($value); ?>" /> - <?php else: ?> - <input type="text" - <?php if ($is_optional): ?> class="optional"<?php endif; ?> - data-parameter="<?php p($parameter); ?>" - value="<?php p($value); ?>" - placeholder="<?php p($placeholder); ?>" /> - <?php endif; ?> - <?php endif; ?> + <td class="backend" data-class="<?php p($storage->getBackend()->getIdentifier()); ?>"><?php p($storage->getBackend()->getText()); ?> + </td> + <td class="authentication"> + <select class="selectAuthMechanism"> + <?php + $authSchemes = $storage->getBackend()->getAuthSchemes(); + $authMechanisms = array_filter($_['authMechanisms'], function($mech) use ($authSchemes) { + return isset($authSchemes[$mech->getScheme()]); + }); + ?> + <?php foreach ($authMechanisms as $mech): ?> + <option value="<?php p($mech->getIdentifier()); ?>" data-scheme="<?php p($mech->getScheme());?>" + <?php if ($mech->getIdentifier() === $storage->getAuthMechanism()->getIdentifier()): ?>selected<?php endif; ?> + ><?php p($mech->getText()); ?></option> <?php endforeach; ?> - <?php if (isset($_['backends'][$mount['class']]['custom'])): ?> - <?php OCP\Util::addScript('files_external', $_['backends'][$mount['class']]['custom']); ?> - <?php endif; ?> - <?php endif; ?> + </select> </td> - <?php if ($_['isAdminPage']): ?> - <td class="applicable" - align="right" - data-applicable-groups='<?php if (isset($mount['applicable']['groups'])) - print_unescaped(json_encode($mount['applicable']['groups'])); ?>' - data-applicable-users='<?php if (isset($mount['applicable']['users'])) - print_unescaped(json_encode($mount['applicable']['users'])); ?>'> - <input type="hidden" class="applicableUsers" style="width:20em;" value=""/> + <td class="configuration"> + <?php + $options = $storage->getBackendOptions(); + foreach ($storage->getBackend()->getParameters() as $parameter) { + writeParameterInput($parameter, $options); + } + foreach ($storage->getAuthMechanism()->getParameters() as $parameter) { + writeParameterInput($parameter, $options, ['auth-param']); + } + ?> </td> + <?php if ($_['isAdminPage']): ?> + <td class="applicable" + align="right" + data-applicable-groups='<?php print_unescaped(json_encode($storage->getApplicableGroups())); ?>' + data-applicable-users='<?php print_unescaped(json_encode($storage->getApplicableUsers())); ?>'> + <input type="hidden" class="applicableUsers" style="width:20em;" value=""/> + </td> <?php endif; ?> - <td class="mountOptionsToggle <?php if (!isset($mount['mountpoint'])) { p('hidden'); } ?>" - ><img + <td class="mountOptionsToggle"> + <img class="svg action" title="<?php p($l->t('Advanced settings')); ?>" alt="<?php p($l->t('Advanced settings')); ?>" - src="<?php print_unescaped(image_path('core', 'actions/settings.svg')); ?>" /> - <input type="hidden" class="mountOptions" value="<?php isset($mount['mountOptions']) ? p(json_encode($mount['mountOptions'])) : '' ?>" /> + src="<?php print_unescaped(image_path('core', 'actions/settings.svg')); ?>" + /> + <input type="hidden" class="mountOptions" value="<?php p(json_encode($storage->getMountOptions())); ?>" /> <?php if ($_['isAdminPage']): ?> - <?php if (isset($mount['priority'])): ?> - <input type="hidden" class="priority" value="<?php p($mount['priority']) ?>" /> - <?php endif; ?> + <input type="hidden" class="priority" value="<?php p($storage->getPriority()); ?>" /> <?php endif; ?> </td> - <td <?php if (isset($mount['mountpoint'])): ?>class="remove" - <?php else: ?>class="hidden" - <?php endif ?>><img alt="<?php p($l->t('Delete')); ?>" - title="<?php p($l->t('Delete')); ?>" - class="svg action" - src="<?php print_unescaped(image_path('core', 'actions/delete.svg')); ?>" /></td> + <td class="remove"> + <img alt="<?php p($l->t('Delete')); ?>" + title="<?php p($l->t('Delete')); ?>" + class="svg action" + src="<?php print_unescaped(image_path('core', 'actions/delete.svg')); ?>" + /> + </td> </tr> <?php endforeach; ?> + <tr id="addMountPoint"> + <td class="status"> + <span></span> + </td> + <td class="mountPoint"><input type="text" name="mountPoint" value="" + placeholder="<?php p($l->t('Folder name')); ?>"> + </td> + <td class="backend"> + <select id="selectBackend" class="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'> + <option value="" disabled selected + style="display:none;"> + <?php p($l->t('Add storage')); ?> + </option> + <?php + $sortedBackends = $_['backends']; + uasort($sortedBackends, function($a, $b) { + return strcasecmp($a->getText(), $b->getText()); + }); + ?> + <?php foreach ($sortedBackends as $backend): ?> + <option value="<?php p($backend->getIdentifier()); ?>"><?php p($backend->getText()); ?></option> + <?php endforeach; ?> + </select> + </td> + <td class="authentication" data-mechanisms='<?php p(json_encode($_['authMechanisms'])); ?>'></td> + <td class="configuration"></td> + <?php if ($_['isAdminPage']): ?> + <td class="applicable" align="right"> + <input type="hidden" class="applicableUsers" style="width:20em;" value="" /> + </td> + <?php endif; ?> + <td class="mountOptionsToggle hidden"> + <img class="svg action" + title="<?php p($l->t('Advanced settings')); ?>" + alt="<?php p($l->t('Advanced settings')); ?>" + src="<?php print_unescaped(image_path('core', 'actions/settings.svg')); ?>" + /> + <input type="hidden" class="mountOptions" value="" /> + </td> + <td class="hidden"> + <img class="svg action" + alt="<?php p($l->t('Delete')); ?>" + title="<?php p($l->t('Delete')); ?>" + src="<?php print_unescaped(image_path('core', 'actions/delete.svg')); ?>" + /> + </td> + </tr> </tbody> </table> <br /> @@ -123,9 +196,9 @@ <p id="userMountingBackends"<?php if ($_['allowUserMounting'] != 'yes'): ?> class="hidden"<?php endif; ?>> <?php p($l->t('Allow users to mount the following external storage')); ?><br /> - <?php $i = 0; foreach ($_['personal_backends'] as $class => $backend): ?> - <input type="checkbox" id="allowUserMountingBackends<?php p($i); ?>" name="allowUserMountingBackends[]" value="<?php p($class); ?>" <?php if ($backend['enabled']) print_unescaped(' checked="checked"'); ?> /> - <label for="allowUserMountingBackends<?php p($i); ?>"><?php p($backend['backend']); ?></label> <br /> + <?php $i = 0; foreach ($_['userBackends'] as $backend): ?> + <input type="checkbox" id="allowUserMountingBackends<?php p($i); ?>" name="allowUserMountingBackends[]" value="<?php p($backend->getIdentifier()); ?>" <?php if ($backend->isVisibleFor(BackendService::VISIBILITY_PERSONAL)) print_unescaped(' checked="checked"'); ?> /> + <label for="allowUserMountingBackends<?php p($i); ?>"><?php p($backend->getText()); ?></label> <br /> <?php $i++; ?> <?php endforeach; ?> </p> diff --git a/apps/files_external/tests/auth/authmechanismtest.php b/apps/files_external/tests/auth/authmechanismtest.php new file mode 100644 index 00000000000..b09d65a02df --- /dev/null +++ b/apps/files_external/tests/auth/authmechanismtest.php @@ -0,0 +1,80 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Tests\Auth; + +class AuthMechanismTest extends \Test\TestCase { + + public function testJsonSerialization() { + $mechanism = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism') + ->setMethods(['jsonSerializeDefinition']) + ->getMock(); + $mechanism->expects($this->once()) + ->method('jsonSerializeDefinition') + ->willReturn(['foo' => 'bar']); + + $mechanism->setScheme('scheme'); + + $json = $mechanism->jsonSerialize(); + $this->assertEquals('bar', $json['foo']); + $this->assertEquals('scheme', $json['scheme']); + } + + public function validateStorageProvider() { + return [ + [true, 'scheme', true], + [false, 'scheme', false], + [true, 'foobar', true], + [false, 'barfoo', true], + ]; + } + + /** + * @dataProvider validateStorageProvider + */ + public function testValidateStorage($expectedSuccess, $scheme, $definitionSuccess) { + $mechanism = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism') + ->setMethods(['validateStorageDefinition']) + ->getMock(); + $mechanism->expects($this->atMost(1)) + ->method('validateStorageDefinition') + ->willReturn($definitionSuccess); + + $mechanism->setScheme($scheme); + + $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->disableOriginalConstructor() + ->getMock(); + $backend->expects($this->once()) + ->method('getAuthSchemes') + ->willReturn(['scheme' => true, 'foobar' => true]); + + $storageConfig = $this->getMockBuilder('\OCA\Files_External\Lib\StorageConfig') + ->disableOriginalConstructor() + ->getMock(); + $storageConfig->expects($this->once()) + ->method('getBackend') + ->willReturn($backend); + + $this->assertEquals($expectedSuccess, $mechanism->validateStorage($storageConfig)); + } + +} diff --git a/apps/files_external/tests/backend/backendtest.php b/apps/files_external/tests/backend/backendtest.php new file mode 100644 index 00000000000..4956a923e94 --- /dev/null +++ b/apps/files_external/tests/backend/backendtest.php @@ -0,0 +1,89 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Tests\Backend; + +use \OCA\Files_External\Lib\Backend\Backend; + +class BackendTest extends \Test\TestCase { + + public function testJsonSerialization() { + $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->setMethods(['jsonSerializeDefinition']) + ->getMock(); + $backend->expects($this->once()) + ->method('jsonSerializeDefinition') + ->willReturn(['foo' => 'bar', 'name' => 'abc']); + + $backend->setPriority(57); + $backend->addAuthScheme('foopass'); + $backend->addAuthScheme('barauth'); + + $json = $backend->jsonSerialize(); + $this->assertEquals('bar', $json['foo']); + $this->assertEquals('abc', $json['name']); + $this->assertEquals($json['name'], $json['backend']); + $this->assertEquals(57, $json['priority']); + + $this->assertContains('foopass', $json['authSchemes']); + $this->assertContains('barauth', $json['authSchemes']); + } + + public function validateStorageProvider() { + return [ + [true, true], + [false, false], + ]; + } + + /** + * @dataProvider validateStorageProvider + */ + public function testValidateStorage($expectedSuccess, $definitionSuccess) { + $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->setMethods(['validateStorageDefinition']) + ->getMock(); + $backend->expects($this->atMost(1)) + ->method('validateStorageDefinition') + ->willReturn($definitionSuccess); + + $storageConfig = $this->getMockBuilder('\OCA\Files_External\Lib\StorageConfig') + ->disableOriginalConstructor() + ->getMock(); + + $this->assertEquals($expectedSuccess, $backend->validateStorage($storageConfig)); + } + + public function testLegacyAuthMechanismCallback() { + $backend = new Backend(); + $backend->setLegacyAuthMechanismCallback(function(array $params) { + if (isset($params['ping'])) { + return 'pong'; + } + return 'foobar'; + }); + + $this->assertEquals('pong', $backend->getLegacyAuthMechanism(['ping' => true])); + $this->assertEquals('foobar', $backend->getLegacyAuthMechanism(['other' => true])); + $this->assertEquals('foobar', $backend->getLegacyAuthMechanism()); + } + +} diff --git a/apps/files_external/tests/backend/legacybackendtest.php b/apps/files_external/tests/backend/legacybackendtest.php new file mode 100644 index 00000000000..44cb16a4986 --- /dev/null +++ b/apps/files_external/tests/backend/legacybackendtest.php @@ -0,0 +1,81 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Tests\Backend; + +use \OCA\Files_External\Lib\Backend\LegacyBackend; +use \OCA\Files_External\Lib\DefinitionParameter; + +class LegacyBackendTest extends \Test\TestCase { + + public function testConstructor() { + $auth = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\Builtin') + ->disableOriginalConstructor() + ->getMock(); + + $class = '\OC\Files\Storage\SMB'; + $definition = [ + 'configuration' => [ + 'textfield' => 'Text field', + 'passwordfield' => '*Password field', + 'checkbox' => '!Checkbox', + 'hiddenfield' => '#Hidden field', + 'optionaltext' => '&Optional text field', + 'optionalpassword' => '&*Optional password field', + ], + 'backend' => 'Backend text', + 'priority' => 123, + 'custom' => 'foo/bar.js', + 'has_dependencies' => true, + ]; + + $backend = new LegacyBackend($class, $definition, $auth); + + $this->assertEquals('\OC\Files\Storage\SMB', $backend->getStorageClass()); + $this->assertEquals('Backend text', $backend->getText()); + $this->assertEquals(123, $backend->getPriority()); + $this->assertEquals('foo/bar.js', $backend->getCustomJs()); + $this->assertEquals(true, $backend->hasDependencies()); + $this->assertArrayHasKey('builtin', $backend->getAuthSchemes()); + $this->assertEquals($auth, $backend->getLegacyAuthMechanism()); + + $parameters = $backend->getParameters(); + $this->assertEquals('Text field', $parameters['textfield']->getText()); + $this->assertEquals(DefinitionParameter::VALUE_TEXT, $parameters['textfield']->getType()); + $this->assertEquals(DefinitionParameter::FLAG_NONE, $parameters['textfield']->getFlags()); + $this->assertEquals('Password field', $parameters['passwordfield']->getText()); + $this->assertEquals(DefinitionParameter::VALUE_PASSWORD, $parameters['passwordfield']->getType()); + $this->assertEquals(DefinitionParameter::FLAG_NONE, $parameters['passwordfield']->getFlags()); + $this->assertEquals('Checkbox', $parameters['checkbox']->getText()); + $this->assertEquals(DefinitionParameter::VALUE_BOOLEAN, $parameters['checkbox']->getType()); + $this->assertEquals(DefinitionParameter::FLAG_NONE, $parameters['checkbox']->getFlags()); + $this->assertEquals('Hidden field', $parameters['hiddenfield']->getText()); + $this->assertEquals(DefinitionParameter::VALUE_HIDDEN, $parameters['hiddenfield']->getType()); + $this->assertEquals(DefinitionParameter::FLAG_NONE, $parameters['hiddenfield']->getFlags()); + $this->assertEquals('Optional text field', $parameters['optionaltext']->getText()); + $this->assertEquals(DefinitionParameter::VALUE_TEXT, $parameters['optionaltext']->getType()); + $this->assertEquals(DefinitionParameter::FLAG_OPTIONAL, $parameters['optionaltext']->getFlags()); + $this->assertEquals('Optional password field', $parameters['optionalpassword']->getText()); + $this->assertEquals(DefinitionParameter::VALUE_PASSWORD, $parameters['optionalpassword']->getType()); + $this->assertEquals(DefinitionParameter::FLAG_OPTIONAL, $parameters['optionalpassword']->getFlags()); + } + +} diff --git a/apps/files_external/tests/controller/globalstoragescontrollertest.php b/apps/files_external/tests/controller/globalstoragescontrollertest.php index fc58743454a..e1bfad8caf6 100644 --- a/apps/files_external/tests/controller/globalstoragescontrollertest.php +++ b/apps/files_external/tests/controller/globalstoragescontrollertest.php @@ -28,7 +28,9 @@ use \OCA\Files_external\NotFoundException; class GlobalStoragesControllerTest extends StoragesControllerTest { public function setUp() { parent::setUp(); - $this->service = $this->getMock('\OCA\Files_external\Service\GlobalStoragesService'); + $this->service = $this->getMockBuilder('\OCA\Files_external\Service\GlobalStoragesService') + ->disableOriginalConstructor() + ->getMock(); $this->controller = new GlobalStoragesController( 'files_external', diff --git a/apps/files_external/tests/controller/storagescontrollertest.php b/apps/files_external/tests/controller/storagescontrollertest.php index 86874ef9786..2b0ee7a14ea 100644 --- a/apps/files_external/tests/controller/storagescontrollertest.php +++ b/apps/files_external/tests/controller/storagescontrollertest.php @@ -1,6 +1,7 @@ <?php /** * @author Vincent Petry <pvince81@owncloud.com> + * @author Robin McCorkell <rmccorkell@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -47,17 +48,56 @@ abstract class StoragesControllerTest extends \Test\TestCase { \OC_Mount_Config::$skipTest = false; } + protected function getBackendMock($class = '\OCA\Files_External\Lib\Backend\SMB', $storageClass = '\OC\Files\Storage\SMB') { + $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->disableOriginalConstructor() + ->getMock(); + $backend->method('getStorageClass') + ->willReturn($storageClass); + $backend->method('getIdentifier') + ->willReturn('identifier:'.$class); + return $backend; + } + + protected function getAuthMechMock($scheme = 'null', $class = '\OCA\Files_External\Lib\Auth\NullMechanism') { + $authMech = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism') + ->disableOriginalConstructor() + ->getMock(); + $authMech->method('getScheme') + ->willReturn($scheme); + $authMech->method('getIdentifier') + ->willReturn('identifier:'.$class); + + return $authMech; + } + public function testAddStorage() { + $authMech = $this->getAuthMechMock(); + $authMech->method('validateStorage') + ->willReturn(true); + $backend = $this->getBackendMock(); + $backend->method('validateStorage') + ->willReturn(true); + $backend->method('isVisibleFor') + ->willReturn(true); + $storageConfig = new StorageConfig(1); $storageConfig->setMountPoint('mount'); + $storageConfig->setBackend($backend); + $storageConfig->setAuthMechanism($authMech); + $storageConfig->setBackendOptions([]); $this->service->expects($this->once()) + ->method('createStorage') + ->will($this->returnValue($storageConfig)); + $this->service->expects($this->once()) ->method('addStorage') ->will($this->returnValue($storageConfig)); $response = $this->controller->create( 'mount', '\OC\Files\Storage\SMB', + '\OCA\Files_External\Lib\Auth\NullMechanism', array(), [], [], @@ -66,15 +106,30 @@ abstract class StoragesControllerTest extends \Test\TestCase { ); $data = $response->getData(); - $this->assertEquals($storageConfig, $data); $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertEquals($storageConfig, $data); } public function testUpdateStorage() { + $authMech = $this->getAuthMechMock(); + $authMech->method('validateStorage') + ->willReturn(true); + $backend = $this->getBackendMock(); + $backend->method('validateStorage') + ->willReturn(true); + $backend->method('isVisibleFor') + ->willReturn(true); + $storageConfig = new StorageConfig(1); $storageConfig->setMountPoint('mount'); + $storageConfig->setBackend($backend); + $storageConfig->setAuthMechanism($authMech); + $storageConfig->setBackendOptions([]); $this->service->expects($this->once()) + ->method('createStorage') + ->will($this->returnValue($storageConfig)); + $this->service->expects($this->once()) ->method('updateStorage') ->will($this->returnValue($storageConfig)); @@ -82,6 +137,7 @@ abstract class StoragesControllerTest extends \Test\TestCase { 1, 'mount', '\OC\Files\Storage\SMB', + '\OCA\Files_External\Lib\Auth\NullMechanism', array(), [], [], @@ -90,8 +146,8 @@ abstract class StoragesControllerTest extends \Test\TestCase { ); $data = $response->getData(); - $this->assertEquals($storageConfig, $data); $this->assertEquals(Http::STATUS_OK, $response->getStatus()); + $this->assertEquals($storageConfig, $data); } function mountPointNamesProvider() { @@ -106,6 +162,15 @@ abstract class StoragesControllerTest extends \Test\TestCase { * @dataProvider mountPointNamesProvider */ public function testAddOrUpdateStorageInvalidMountPoint($mountPoint) { + $storageConfig = new StorageConfig(1); + $storageConfig->setMountPoint($mountPoint); + $storageConfig->setBackend($this->getBackendMock()); + $storageConfig->setAuthMechanism($this->getAuthMechMock()); + $storageConfig->setBackendOptions([]); + + $this->service->expects($this->exactly(2)) + ->method('createStorage') + ->will($this->returnValue($storageConfig)); $this->service->expects($this->never()) ->method('addStorage'); $this->service->expects($this->never()) @@ -114,6 +179,7 @@ abstract class StoragesControllerTest extends \Test\TestCase { $response = $this->controller->create( $mountPoint, '\OC\Files\Storage\SMB', + '\OCA\Files_External\Lib\Auth\NullMechanism', array(), [], [], @@ -127,6 +193,7 @@ abstract class StoragesControllerTest extends \Test\TestCase { 1, $mountPoint, '\OC\Files\Storage\SMB', + '\OCA\Files_External\Lib\Auth\NullMechanism', array(), [], [], @@ -138,6 +205,9 @@ abstract class StoragesControllerTest extends \Test\TestCase { } public function testAddOrUpdateStorageInvalidBackend() { + $this->service->expects($this->exactly(2)) + ->method('createStorage') + ->will($this->throwException(new \InvalidArgumentException())); $this->service->expects($this->never()) ->method('addStorage'); $this->service->expects($this->never()) @@ -146,6 +216,7 @@ abstract class StoragesControllerTest extends \Test\TestCase { $response = $this->controller->create( 'mount', '\OC\Files\Storage\InvalidStorage', + '\OCA\Files_External\Lib\Auth\NullMechanism', array(), [], [], @@ -159,6 +230,7 @@ abstract class StoragesControllerTest extends \Test\TestCase { 1, 'mount', '\OC\Files\Storage\InvalidStorage', + '\OCA\Files_External\Lib\Auth\NullMechanism', array(), [], [], @@ -170,6 +242,24 @@ abstract class StoragesControllerTest extends \Test\TestCase { } public function testUpdateStorageNonExisting() { + $authMech = $this->getAuthMechMock(); + $authMech->method('validateStorage') + ->willReturn(true); + $backend = $this->getBackendMock(); + $backend->method('validateStorage') + ->willReturn(true); + $backend->method('isVisibleFor') + ->willReturn(true); + + $storageConfig = new StorageConfig(255); + $storageConfig->setMountPoint('mount'); + $storageConfig->setBackend($backend); + $storageConfig->setAuthMechanism($authMech); + $storageConfig->setBackendOptions([]); + + $this->service->expects($this->once()) + ->method('createStorage') + ->will($this->returnValue($storageConfig)); $this->service->expects($this->once()) ->method('updateStorage') ->will($this->throwException(new NotFoundException())); @@ -178,6 +268,7 @@ abstract class StoragesControllerTest extends \Test\TestCase { 255, 'mount', '\OC\Files\Storage\SMB', + '\OCA\Files_External\Lib\Auth\NullMechanism', array(), [], [], @@ -206,9 +297,12 @@ abstract class StoragesControllerTest extends \Test\TestCase { } public function testGetStorage() { + $backend = $this->getBackendMock(); + $authMech = $this->getAuthMechMock(); $storageConfig = new StorageConfig(1); $storageConfig->setMountPoint('test'); - $storageConfig->setBackendClass('\OC\Files\Storage\SMB'); + $storageConfig->setBackend($backend); + $storageConfig->setAuthMechanism($authMech); $storageConfig->setBackendOptions(['user' => 'test', 'password', 'password123']); $storageConfig->setMountOptions(['priority' => false]); @@ -221,4 +315,66 @@ abstract class StoragesControllerTest extends \Test\TestCase { $this->assertEquals(Http::STATUS_OK, $response->getStatus()); $this->assertEquals($storageConfig, $response->getData()); } + + public function validateStorageProvider() { + return [ + [true, true, true], + [false, true, false], + [true, false, false], + [false, false, false] + ]; + } + + /** + * @dataProvider validateStorageProvider + */ + public function testValidateStorage($backendValidate, $authMechValidate, $expectSuccess) { + $backend = $this->getBackendMock(); + $backend->method('validateStorage') + ->willReturn($backendValidate); + $backend->method('isVisibleFor') + ->willReturn(true); + + $authMech = $this->getAuthMechMock(); + $authMech->method('validateStorage') + ->will($this->returnValue($authMechValidate)); + + $storageConfig = new StorageConfig(); + $storageConfig->setMountPoint('mount'); + $storageConfig->setBackend($backend); + $storageConfig->setAuthMechanism($authMech); + $storageConfig->setBackendOptions([]); + + $this->service->expects($this->once()) + ->method('createStorage') + ->will($this->returnValue($storageConfig)); + + if ($expectSuccess) { + $this->service->expects($this->once()) + ->method('addStorage') + ->with($storageConfig) + ->will($this->returnValue($storageConfig)); + } else { + $this->service->expects($this->never()) + ->method('addStorage'); + } + + $response = $this->controller->create( + 'mount', + '\OC\Files\Storage\SMB', + '\OCA\Files_External\Lib\Auth\NullMechanism', + array(), + [], + [], + [], + null + ); + + if ($expectSuccess) { + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + } else { + $this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus()); + } + } + } diff --git a/apps/files_external/tests/controller/userstoragescontrollertest.php b/apps/files_external/tests/controller/userstoragescontrollertest.php index f9b4c5b2681..9f1a8df8d2e 100644 --- a/apps/files_external/tests/controller/userstoragescontrollertest.php +++ b/apps/files_external/tests/controller/userstoragescontrollertest.php @@ -24,6 +24,8 @@ use \OCA\Files_external\Controller\UserStoragesController; use \OCA\Files_external\Service\UserStoragesService; use \OCP\AppFramework\Http; use \OCA\Files_external\NotFoundException; +use \OCA\Files_External\Lib\StorageConfig; +use \OCA\Files_External\Service\BackendService; class UserStoragesControllerTest extends StoragesControllerTest { @@ -44,41 +46,24 @@ class UserStoragesControllerTest extends StoragesControllerTest { $this->getMock('\OCP\IL10N'), $this->service ); - - $config = \OC::$server->getConfig(); - - $this->oldAllowedBackends = $config->getAppValue( - 'files_external', - 'user_mounting_backends', - '' - ); - $config->setAppValue( - 'files_external', - 'user_mounting_backends', - '\OC\Files\Storage\SMB' - ); } - public function tearDown() { - $config = \OC::$server->getConfig(); - $config->setAppValue( - 'files_external', - 'user_mounting_backends', - $this->oldAllowedBackends - ); - parent::tearDown(); - } + public function testAddOrUpdateStorageDisallowedBackend() { + $backend = $this->getBackendMock(); + $backend->method('isVisibleFor') + ->with(BackendService::VISIBILITY_PERSONAL) + ->willReturn(false); + $authMech = $this->getAuthMechMock(); - function disallowedBackendClassProvider() { - return array( - array('\OC\Files\Storage\Local'), - array('\OC\Files\Storage\FTP'), - ); - } - /** - * @dataProvider disallowedBackendClassProvider - */ - public function testAddOrUpdateStorageDisallowedBackend($backendClass) { + $storageConfig = new StorageConfig(1); + $storageConfig->setMountPoint('mount'); + $storageConfig->setBackend($backend); + $storageConfig->setAuthMechanism($authMech); + $storageConfig->setBackendOptions([]); + + $this->service->expects($this->exactly(2)) + ->method('createStorage') + ->will($this->returnValue($storageConfig)); $this->service->expects($this->never()) ->method('addStorage'); $this->service->expects($this->never()) @@ -86,7 +71,8 @@ class UserStoragesControllerTest extends StoragesControllerTest { $response = $this->controller->create( 'mount', - $backendClass, + '\OC\Files\Storage\SMB', + '\Auth\Mechanism', array(), [], [], @@ -99,7 +85,8 @@ class UserStoragesControllerTest extends StoragesControllerTest { $response = $this->controller->update( 1, 'mount', - $backendClass, + '\OC\Files\Storage\SMB', + '\Auth\Mechanism', array(), [], [], diff --git a/apps/files_external/tests/definitionparameterttest.php b/apps/files_external/tests/definitionparameterttest.php new file mode 100644 index 00000000000..6be00508698 --- /dev/null +++ b/apps/files_external/tests/definitionparameterttest.php @@ -0,0 +1,70 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Tests; + +use \OCA\Files_External\Lib\DefinitionParameter as Param; + +class DefinitionParameterTest extends \Test\TestCase { + + public function testJsonSerialization() { + $param = new Param('foo', 'bar'); + $this->assertEquals('bar', $param->jsonSerialize()); + + $param->setType(Param::VALUE_BOOLEAN); + $this->assertEquals('!bar', $param->jsonSerialize()); + + $param->setType(Param::VALUE_PASSWORD); + $param->setFlag(Param::FLAG_OPTIONAL); + $this->assertEquals('&*bar', $param->jsonSerialize()); + + $param->setType(Param::VALUE_HIDDEN); + $param->setFlags(Param::FLAG_NONE); + $this->assertEquals('#bar', $param->jsonSerialize()); + } + + public function validateValueProvider() { + return [ + [Param::VALUE_TEXT, Param::FLAG_NONE, 'abc', true], + [Param::VALUE_TEXT, Param::FLAG_NONE, '', false], + [Param::VALUE_TEXT, Param::FLAG_OPTIONAL, '', true], + + [Param::VALUE_BOOLEAN, Param::FLAG_NONE, false, true], + [Param::VALUE_BOOLEAN, Param::FLAG_NONE, 123, false], + + [Param::VALUE_PASSWORD, Param::FLAG_NONE, 'foobar', true], + [Param::VALUE_PASSWORD, Param::FLAG_NONE, '', false], + + [Param::VALUE_HIDDEN, Param::FLAG_NONE, '', false] + ]; + } + + /** + * @dataProvider validateValueProvider + */ + public function testValidateValue($type, $flags, $value, $success) { + $param = new Param('foo', 'bar'); + $param->setType($type); + $param->setFlags($flags); + + $this->assertEquals($success, $param->validateValue($value)); + } +} diff --git a/apps/files_external/tests/dependencytraittest.php b/apps/files_external/tests/dependencytraittest.php new file mode 100644 index 00000000000..5706d97053d --- /dev/null +++ b/apps/files_external/tests/dependencytraittest.php @@ -0,0 +1,45 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Tests; + +use \OCA\Files_External\Lib\MissingDependency; + +class DependencyTraitTest extends \Test\TestCase { + + public function testCheckDependencies() { + $trait = $this->getMockForTrait('\OCA\Files_External\Lib\DependencyTrait'); + $trait->setDependencyCheck(function() { + return [ + (new MissingDependency('dependency'))->setMessage('missing dependency'), + (new MissingDependency('program'))->setMessage('cannot find program'), + ]; + }); + + $dependencies = $trait->checkDependencies(); + $this->assertCount(2, $dependencies); + $this->assertEquals('dependency', $dependencies[0]->getDependency()); + $this->assertEquals('missing dependency', $dependencies[0]->getMessage()); + $this->assertEquals('program', $dependencies[1]->getDependency()); + $this->assertEquals('cannot find program', $dependencies[1]->getMessage()); + } + +} diff --git a/apps/files_external/tests/dynamicmountconfig.php b/apps/files_external/tests/dynamicmountconfig.php deleted file mode 100644 index 48791ca89a5..00000000000 --- a/apps/files_external/tests/dynamicmountconfig.php +++ /dev/null @@ -1,104 +0,0 @@ -<?php -/** - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -require_once __DIR__ . '/../../../lib/base.php'; - -/** - * Class Test_Mount_Config_Dummy_Backend - */ -class Test_Mount_Config_Dummy_Backend { - public static $checkDependencies = true; - - public static function checkDependencies() { - return self::$checkDependencies; - } -} - -/** - * Class Test_Dynamic_Mount_Config - */ -class Test_Dynamic_Mount_Config extends \Test\TestCase { - - private $backup; - - public function testRegistration() { - - // second registration shall return false - $result = OC_Mount_Config::registerBackend('Test_Mount_Config_Dummy_Backend', array( - 'backend' => 'Test Dummy', - 'configuration' => array(), - 'has_dependencies' => true)); - - $this->assertTrue($result); - } - - public function testDependencyGetBackend() { - - // is the backend listed? - Test_Mount_Config_Dummy_Backend::$checkDependencies = true; - $backEnds = OC_Mount_Config::getBackends(); - $this->assertArrayHasKey('Test_Mount_Config_Dummy_Backend', $backEnds); - - // backend shall not be listed - Test_Mount_Config_Dummy_Backend::$checkDependencies = false; - - $backEnds = OC_Mount_Config::getBackends(); - $this->assertArrayNotHasKey('Test_Mount_Config_Dummy_Backend', $backEnds); - - } - - public function testCheckDependencies() { - - Test_Mount_Config_Dummy_Backend::$checkDependencies = true; - $message = OC_Mount_Config::checkDependencies(); - $this->assertEmpty($message); - - // backend shall not be listed - Test_Mount_Config_Dummy_Backend::$checkDependencies = array('dummy'); - - $message = OC_Mount_Config::checkDependencies(); - $this->assertEquals('<br /><b>Note:</b> "dummy" is not installed. Mounting of <i>Test Dummy</i> is not possible. Please ask your system administrator to install it.', - $message); - - } - - protected function setUp() { - parent::setUp(); - - $this->backup = OC_Mount_Config::setUp(); - - // register dummy backend - $result = OC_Mount_Config::registerBackend('Test_Mount_Config_Dummy_Backend', array( - 'backend' => 'Test Dummy', - 'configuration' => array(), - 'has_dependencies' => true)); - - $this->assertTrue($result); - } - - protected function tearDown() - { - OC_Mount_Config::setUp($this->backup); - parent::tearDown(); - } -} diff --git a/apps/files_external/tests/frontenddefinitiontraittest.php b/apps/files_external/tests/frontenddefinitiontraittest.php new file mode 100644 index 00000000000..871a87d4c52 --- /dev/null +++ b/apps/files_external/tests/frontenddefinitiontraittest.php @@ -0,0 +1,83 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Tests; + +class FrontendDefinitionTraitTest extends \Test\TestCase { + + public function testJsonSerialization() { + $param = $this->getMockBuilder('\OCA\Files_External\Lib\DefinitionParameter') + ->disableOriginalConstructor() + ->getMock(); + $param->method('getName')->willReturn('foo'); + + $trait = $this->getMockForTrait('\OCA\Files_External\Lib\FrontendDefinitionTrait'); + $trait->setText('test'); + $trait->addParameters([$param]); + $trait->setCustomJs('foo/bar.js'); + + $json = $trait->jsonSerializeDefinition(); + + $this->assertEquals('test', $json['name']); + $this->assertEquals('foo/bar.js', $json['custom']); + + $configuration = $json['configuration']; + $this->assertArrayHasKey('foo', $configuration); + } + + public function validateStorageProvider() { + return [ + [true, ['foo' => true, 'bar' => true, 'baz' => true]], + [false, ['foo' => true, 'bar' => false]] + ]; + } + + /** + * @dataProvider validateStorageProvider + */ + public function testValidateStorage($expectedSuccess, $params) { + $backendParams = []; + foreach ($params as $name => $valid) { + $param = $this->getMockBuilder('\OCA\Files_External\Lib\DefinitionParameter') + ->disableOriginalConstructor() + ->getMock(); + $param->method('getName') + ->willReturn($name); + $param->expects($this->once()) + ->method('validateValue') + ->willReturn($valid); + $backendParams[] = $param; + } + + $storageConfig = $this->getMockBuilder('\OCA\Files_External\Lib\StorageConfig') + ->disableOriginalConstructor() + ->getMock(); + $storageConfig->expects($this->once()) + ->method('getBackendOptions') + ->willReturn([]); + + $trait = $this->getMockForTrait('\OCA\Files_External\Lib\FrontendDefinitionTrait'); + $trait->setText('test'); + $trait->addParameters($backendParams); + + $this->assertEquals($expectedSuccess, $trait->validateStorageDefinition($storageConfig)); + } +} diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js index 7cb86d7270b..67a81277124 100644 --- a/apps/files_external/tests/js/settingsSpec.js +++ b/apps/files_external/tests/js/settingsSpec.js @@ -39,6 +39,7 @@ describe('OCA.External.Settings tests', function() { '<option value="\\OC\\AnotherTestBackend">Another Test Backend</option>' + '</select>' + '</td>' + + '<td class="authentication"></td>' + '<td class="configuration"></td>' + '<td class="applicable">' + '<input type="hidden" class="applicableUsers">' + @@ -58,6 +59,9 @@ describe('OCA.External.Settings tests', function() { 'field1': 'Display Name 1', 'field2': '&Display Name 2' }, + 'authSchemes': { + 'builtin': true, + }, 'priority': 11 }, '\\OC\\AnotherTestBackend': { @@ -66,10 +70,23 @@ describe('OCA.External.Settings tests', function() { 'field1': 'Display Name 1', 'field2': '&Display Name 2' }, + 'authSchemes': { + 'builtin': true, + }, 'priority': 12 } } ); + + $('#externalStorage #addMountPoint .authentication:first').data('mechanisms', { + 'mechanism1': { + 'name': 'Mechanism 1', + 'configuration': { + }, + 'scheme': 'builtin', + }, + }); + }); afterEach(function() { select2Stub.restore(); @@ -80,7 +97,7 @@ describe('OCA.External.Settings tests', function() { var view; function selectBackend(backendName) { - view.$el.find('.selectBackend:first').val('\\OC\\TestBackend').trigger('change'); + view.$el.find('.selectBackend:first').val(backendName).trigger('change'); } beforeEach(function() { @@ -139,7 +156,8 @@ describe('OCA.External.Settings tests', function() { var request = fakeServer.requests[0]; expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_external/globalstorages'); expect(JSON.parse(request.requestBody)).toEqual({ - backendClass: '\\OC\\TestBackend', + backend: '\\OC\\TestBackend', + authMechanism: 'mechanism1', backendOptions: { 'field1': 'test', 'field2': '' diff --git a/apps/files_external/tests/mountconfig.php b/apps/files_external/tests/mountconfig.php deleted file mode 100644 index b76ba0a39a6..00000000000 --- a/apps/files_external/tests/mountconfig.php +++ /dev/null @@ -1,1157 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Vincent Petry <pvince81@owncloud.com> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -class Test_Mount_Config_Dummy_Storage extends \OC\Files\Storage\Common { - public function __construct($params) { - if (isset($params['simulateFail']) && $params['simulateFail'] == true) { - throw new \Exception('Simulated config validation fail'); - } - } - - public function getId() { - return 'dummy_storage'; - } - - public function mkdir($path) { - return false; - } - - public function rmdir($path) { - return false; - } - - public function opendir($path) { - return false; - } - - public function filetype($path) { - return false; - } - - public function file_exists($path) { - return false; - } - - public function unlink($path) { - return false; - } - - public function fopen($path, $mode) { - return false; - } - - public function touch($path, $mtime = null) { - return false; - } - - public function stat($path) { - return false; - } - - public function test() { - return true; - } -} - -class Test_Mount_Config_Storage_No_Personal extends Test_Mount_Config_Dummy_Storage { -} - -class Test_Mount_Config_Hook_Test { - static $signal; - static $params; - - public static function setUpHooks() { - self::clear(); - \OCP\Util::connectHook( - \OC\Files\Filesystem::CLASSNAME, - \OC\Files\Filesystem::signal_create_mount, - '\Test_Mount_Config_Hook_Test', 'createHookCallback'); - \OCP\Util::connectHook( - \OC\Files\Filesystem::CLASSNAME, - \OC\Files\Filesystem::signal_delete_mount, - '\Test_Mount_Config_Hook_Test', 'deleteHookCallback'); - } - - public static function clear() { - self::$signal = null; - self::$params = null; - } - - public static function createHookCallback($params) { - self::$signal = \OC\Files\Filesystem::signal_create_mount; - self::$params = $params; - } - - public static function deleteHookCallback($params) { - self::$signal = \OC\Files\Filesystem::signal_delete_mount; - self::$params = $params; - } - - public static function getLastCall() { - return array(self::$signal, self::$params); - } -} - -/** - * Class Test_Mount_Config - */ -class Test_Mount_Config extends \Test\TestCase { - - private $dataDir; - private $userHome; - private $oldAllowedBackends; - - const TEST_USER1 = 'user1'; - const TEST_USER2 = 'user2'; - const TEST_GROUP1 = 'group1'; - const TEST_GROUP1B = 'group1b'; - const TEST_GROUP2 = 'group2'; - const TEST_GROUP2B = 'group2b'; - - protected function setUp() { - parent::setUp(); - - OC_Mount_Config::registerBackend('Test_Mount_Config_Dummy_Storage', array( - 'backend' => 'dummy', - 'priority' => 150, - 'configuration' => array() - ) - ); - OC_Mount_Config::registerBackend('Test_Mount_Config_Storage_No_Personal', array( - 'backend' => 'dummy no personal', - 'priority' => 150, - 'configuration' => array() - ) - ); - - \OC_User::createUser(self::TEST_USER1, self::TEST_USER1); - \OC_User::createUser(self::TEST_USER2, self::TEST_USER2); - - \OC_Group::createGroup(self::TEST_GROUP1); - \OC_Group::createGroup(self::TEST_GROUP1B); - \OC_Group::addToGroup(self::TEST_USER1, self::TEST_GROUP1); - \OC_Group::addToGroup(self::TEST_USER1, self::TEST_GROUP1B); - \OC_Group::createGroup(self::TEST_GROUP2); - \OC_Group::createGroup(self::TEST_GROUP2B); - \OC_Group::addToGroup(self::TEST_USER2, self::TEST_GROUP2); - \OC_Group::addToGroup(self::TEST_USER2, self::TEST_GROUP2B); - - \OC_User::setUserId(self::TEST_USER1); - $this->userHome = \OC_User::getHome(self::TEST_USER1); - @mkdir($this->userHome); - - $this->dataDir = \OC_Config::getValue( - 'datadirectory', - \OC::$SERVERROOT . '/data/' - ); - $this->oldAllowedBackends = OCP\Config::getAppValue( - 'files_external', - 'user_mounting_backends', - '' - ); - OCP\Config::setAppValue( - 'files_external', - 'user_mounting_backends', - 'Test_Mount_Config_Dummy_Storage' - ); - - OC_Mount_Config::$skipTest = true; - Test_Mount_Config_Hook_Test::setupHooks(); - } - - protected function tearDown() { - Test_Mount_Config_Hook_Test::clear(); - OC_Mount_Config::$skipTest = false; - - \OC_User::deleteUser(self::TEST_USER2); - \OC_User::deleteUser(self::TEST_USER1); - \OC_Group::deleteGroup(self::TEST_GROUP1); - \OC_Group::deleteGroup(self::TEST_GROUP1B); - \OC_Group::deleteGroup(self::TEST_GROUP2); - \OC_Group::deleteGroup(self::TEST_GROUP2B); - - @unlink($this->dataDir . '/mount.json'); - - OCP\Config::setAppValue( - 'files_external', - 'user_mounting_backends', - $this->oldAllowedBackends - ); - - parent::tearDown(); - } - - /** - * Reads the global config, for checking - */ - private function readGlobalConfig() { - $configFile = $this->dataDir . '/mount.json'; - return json_decode(file_get_contents($configFile), true); - } - - private function writeGlobalConfig($config) { - $configFile = $this->dataDir . '/mount.json'; - file_put_contents($configFile, json_encode($config)); - } - - /** - * Reads the user config, for checking - */ - private function readUserConfig() { - $configFile = $this->userHome . '/mount.json'; - return json_decode(file_get_contents($configFile), true); - } - - /** - * Write the user config, to simulate existing files - */ - private function writeUserConfig($config) { - $configFile = $this->userHome . '/mount.json'; - file_put_contents($configFile, json_encode($config)); - } - - /** - * Test mount point validation - */ - public function testAddMountPointValidation() { - $storageClass = 'Test_Mount_Config_Dummy_Storage'; - $mountType = 'user'; - $applicable = 'all'; - $isPersonal = false; - $this->assertFalse(OC_Mount_Config::addMountPoint('', $storageClass, array(), $mountType, $applicable, $isPersonal)); - $this->assertFalse(OC_Mount_Config::addMountPoint('/', $storageClass, array(), $mountType, $applicable, $isPersonal)); - } - - /** - * Test adding a global mount point - */ - public function testAddGlobalMountPoint() { - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $applicable = 'all'; - $isPersonal = false; - - $storageOptions = array( - 'host' => 'localhost', - 'user' => 'testuser', - 'password' => '12345', - ); - - $this->assertEquals(0, OC_Mount_Config::addMountPoint('/ext', 'Test_Mount_Config_Dummy_Storage', $storageOptions, $mountType, $applicable, $isPersonal)); - - $config = $this->readGlobalConfig(); - $this->assertEquals(1, count($config)); - $this->assertTrue(isset($config[$mountType])); - $this->assertTrue(isset($config[$mountType][$applicable])); - $this->assertTrue(isset($config[$mountType][$applicable]['/$user/files/ext'])); - $this->assertEquals( - 'Test_Mount_Config_Dummy_Storage', - $config[$mountType][$applicable]['/$user/files/ext']['class'] - ); - } - - /** - * Test adding a personal mount point - */ - public function testAddMountPointSingleUser() { - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $applicable = self::TEST_USER1; - $isPersonal = true; - - $storageOptions = array( - 'host' => 'localhost', - 'user' => 'testuser', - 'password' => '12345', - ); - - $this->assertEquals(0, OC_Mount_Config::addMountPoint('/ext', 'Test_Mount_Config_Dummy_Storage', $storageOptions, $mountType, $applicable, $isPersonal)); - - $config = $this->readUserConfig(); - $this->assertEquals(1, count($config)); - $this->assertTrue(isset($config[$mountType])); - $this->assertTrue(isset($config[$mountType][$applicable])); - $this->assertTrue(isset($config[$mountType][$applicable]['/' . self::TEST_USER1 . '/files/ext'])); - $this->assertEquals( - 'Test_Mount_Config_Dummy_Storage', - $config[$mountType][$applicable]['/' . self::TEST_USER1 . '/files/ext']['class'] - ); - } - - /** - * Test adding a personal mount point using disallowed backend - */ - public function testAddDisallowedBackendMountPointSingleUser() { - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $applicable = self::TEST_USER1; - $isPersonal = true; - - // local - $this->assertFalse(OC_Mount_Config::addMountPoint('/ext', '\OC\Files\Storage\Local', array(), $mountType, $applicable, $isPersonal)); - - $storageOptions = array( - 'host' => 'localhost', - 'user' => 'testuser', - 'password' => '12345', - ); - - // non-local but forbidden - $this->assertFalse(OC_Mount_Config::addMountPoint('/ext', 'Test_Mount_Config_Storage_No_Personal', $storageOptions, $mountType, $applicable, $isPersonal)); - - $this->assertFalse(file_exists($this->userHome . '/mount.json')); - } - - /** - * Test adding a mount point with an non-existant backend - */ - public function testAddMountPointUnexistClass() { - $storageClass = 'Unexist_Storage'; - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $applicable = self::TEST_USER1; - $isPersonal = false; - $this->assertFalse(OC_Mount_Config::addMountPoint('/ext', $storageClass, array(), $mountType, $applicable, $isPersonal)); - - } - - /** - * Provider for testing configurations with different - * "applicable" values (all, user, groups) - */ - public function applicableConfigProvider() { - return array( - // applicable to "all" - array( - OC_Mount_Config::MOUNT_TYPE_USER, - 'all', - array( - 'users' => array('all'), - 'groups' => array() - ) - ), - // applicable to single user - array( - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER1, - array( - 'users' => array(self::TEST_USER1), - 'groups' => array() - ) - ), - // applicable to single group - array( - OC_Mount_Config::MOUNT_TYPE_GROUP, - self::TEST_GROUP1, - array( - 'users' => array(), - 'groups' => array(self::TEST_GROUP1) - ) - ), - ); - } - - /** - * Test reading and writing global config - * - * @dataProvider applicableConfigProvider - */ - public function testReadWriteGlobalConfig($mountType, $applicable, $expectApplicableArray) { - - $mountType = $mountType; - $applicable = $applicable; - $isPersonal = false; - $options = array( - 'host' => 'smbhost', - 'user' => 'smbuser', - 'password' => 'smbpassword', - 'share' => 'smbshare', - 'root' => 'smbroot' - ); - - // write config - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $options, - $mountType, - $applicable, - $isPersonal - ) - ); - - // re-read config - $config = OC_Mount_Config::getSystemMountPoints(); - $this->assertEquals(1, count($config)); - $this->assertEquals('Test_Mount_Config_Dummy_Storage', $config[0]['class']); - $this->assertEquals('ext', $config[0]['mountpoint']); - $this->assertEquals($expectApplicableArray, $config[0]['applicable']); - $savedOptions = $config[0]['options']; - $this->assertEquals($options, $savedOptions); - // key order needs to be preserved for the UI... - $this->assertEquals(array_keys($options), array_keys($savedOptions)); - } - - /** - * Test reading and writing config - */ - public function testReadWritePersonalConfig() { - - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $applicable = self::TEST_USER1; - $isPersonal = true; - $options = array( - 'host' => 'smbhost', - 'user' => 'smbuser', - 'password' => 'smbpassword', - 'share' => 'smbshare', - 'root' => 'smbroot' - ); - - // write config - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $options, - $mountType, - $applicable, - $isPersonal - ) - ); - - // re-read config - $config = OC_Mount_Config::getPersonalMountPoints(); - $this->assertEquals(1, count($config)); - $this->assertEquals('Test_Mount_Config_Dummy_Storage', $config[0]['class']); - $this->assertEquals('ext', $config[0]['mountpoint']); - $savedOptions = $config[0]['options']; - $this->assertEquals($options, $savedOptions); - // key order needs to be preserved for the UI... - $this->assertEquals(array_keys($options), array_keys($savedOptions)); - } - - public function testHooks() { - $mountPoint = '/test'; - $mountType = 'user'; - $applicable = 'all'; - $isPersonal = false; - - $mountConfig = array( - 'host' => 'smbhost', - 'user' => 'smbuser', - 'password' => 'smbpassword', - 'share' => 'smbshare', - 'root' => 'smbroot' - ); - - // write config - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - $mountPoint, - 'Test_Mount_Config_Dummy_Storage', - $mountConfig, - $mountType, - $applicable, - $isPersonal - ) - ); - - list($hookName, $params) = Test_Mount_Config_Hook_Test::getLastCall(); - $this->assertEquals( - \OC\Files\Filesystem::signal_create_mount, - $hookName - ); - $this->assertEquals( - $mountPoint, - $params[\OC\Files\Filesystem::signal_param_path] - ); - $this->assertEquals( - $mountType, - $params[\OC\Files\Filesystem::signal_param_mount_type] - ); - $this->assertEquals( - $applicable, - $params[\OC\Files\Filesystem::signal_param_users] - ); - - Test_Mount_Config_Hook_Test::clear(); - - // edit - $mountConfig['host'] = 'anothersmbhost'; - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - $mountPoint, - 'Test_Mount_Config_Dummy_Storage', - $mountConfig, - $mountType, - $applicable, - $isPersonal - ) - ); - - // hook must not be called on edit - list($hookName, $params) = Test_Mount_Config_Hook_Test::getLastCall(); - $this->assertEquals( - null, - $hookName - ); - - Test_Mount_Config_Hook_Test::clear(); - - $this->assertTrue( - OC_Mount_Config::removeMountPoint( - $mountPoint, - $mountType, - $applicable, - $isPersonal - ) - ); - - list($hookName, $params) = Test_Mount_Config_Hook_Test::getLastCall(); - $this->assertEquals( - \OC\Files\Filesystem::signal_delete_mount, - $hookName - ); - $this->assertEquals( - $mountPoint, - $params[\OC\Files\Filesystem::signal_param_path] - ); - $this->assertEquals( - $mountType, - $params[\OC\Files\Filesystem::signal_param_mount_type] - ); - $this->assertEquals( - $applicable, - $params[\OC\Files\Filesystem::signal_param_users] - ); - } - - /** - * Test password obfuscation - */ - public function testPasswordObfuscation() { - - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $applicable = self::TEST_USER1; - $isPersonal = true; - $mountConfig = array( - 'host' => 'smbhost', - 'user' => 'smbuser', - 'password' => 'smbpassword', - 'share' => 'smbshare', - 'root' => 'smbroot' - ); - - // write config - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $mountConfig, - $mountType, - $applicable, - $isPersonal - ) - ); - - // note: password re-reading is covered by testReadWritePersonalConfig - - // check that password inside the file is NOT in plain text - $config = $this->readUserConfig(); - $savedConfig = $config[$mountType][$applicable]['/' . self::TEST_USER1 . '/files/ext']['options']; - - // no more clear text password in file (kept because of key order) - $this->assertEquals('', $savedConfig['password']); - - // encrypted password is present - $this->assertNotEquals($mountConfig['password'], $savedConfig['password_encrypted']); - } - - /** - * Test read legacy passwords - */ - public function testReadLegacyPassword() { - - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $applicable = self::TEST_USER1; - $isPersonal = true; - $mountConfig = array( - 'host' => 'smbhost', - 'user' => 'smbuser', - 'password' => 'smbpassword', - 'share' => 'smbshare', - 'root' => 'smbroot' - ); - - // write config - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $mountConfig, - $mountType, - $applicable, - $isPersonal - ) - ); - - $config = $this->readUserConfig(); - // simulate non-encrypted password situation - $config[$mountType][$applicable]['/' . self::TEST_USER1 . '/files/ext']['options']['password'] = 'smbpasswd'; - - $this->writeUserConfig($config); - - // re-read config, password was read correctly - $config = OC_Mount_Config::getPersonalMountPoints(); - $savedMountConfig = $config[0]['options']; - $this->assertEquals($mountConfig, $savedMountConfig); - } - - public function testVariableSubstitution() { - $legacyBackendOptions = [ - 'user' => 'someuser', - 'password' => 'somepassword', - 'replacethis' => '$user', - ]; - $legacyBackendOptions = \OC_Mount_Config::encryptPasswords($legacyBackendOptions); - - $legacyConfig = [ - 'class' => '\OC\Files\Storage\SMB', - 'options' => $legacyBackendOptions, - 'mountOptions' => ['preview' => false, 'int' => 1], - ]; - // different mount options - $legacyConfig2 = [ - 'class' => '\OC\Files\Storage\SMB', - 'options' => $legacyBackendOptions, - 'mountOptions' => ['preview' => true, 'string' => 'abc'], - ]; - - $json = [ - 'user' => [ - self::TEST_USER1 => [ - '/$user/files/somemount' => $legacyConfig, - '/$user/files/anothermount' => $legacyConfig2, - ], - ], - ]; - - $this->writeGlobalConfig($json); - - // re-read config, password was read correctly - $config = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER1); - - $config1 = $config['/' . self::TEST_USER1 . '/files/somemount']; - $config2 = $config['/' . self::TEST_USER1 . '/files/anothermount']; - - $this->assertSame(self::TEST_USER1, $config1['options']['replacethis']); - $this->assertSame(self::TEST_USER1, $config1['options']['replacethis']); - $this->assertSame(1, $config1['mountOptions']['int']); - $this->assertSame(true, $config2['mountOptions']['preview']); - $this->assertSame('abc', $config2['mountOptions']['string']); - } - - - public function mountDataProvider() { - return array( - // Tests for visible mount points - // system mount point for all users - array( - false, - OC_Mount_Config::MOUNT_TYPE_USER, - 'all', - self::TEST_USER1, - true, - ), - // system mount point for a specific user - array( - false, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER1, - self::TEST_USER1, - true, - ), - // system mount point for a specific group - array( - false, - OC_Mount_Config::MOUNT_TYPE_GROUP, - self::TEST_GROUP1, - self::TEST_USER1, - true, - ), - // user mount point - array( - true, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER1, - self::TEST_USER1, - true, - ), - - // Tests for non-visible mount points - // system mount point for another user - array( - false, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER2, - self::TEST_USER1, - false, - ), - // system mount point for a specific group - array( - false, - OC_Mount_Config::MOUNT_TYPE_GROUP, - self::TEST_GROUP2, - self::TEST_USER1, - false, - ), - // user mount point - array( - true, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER1, - self::TEST_USER2, - false, - ), - ); - } - - /** - * Test mount points used at mount time, making sure - * the configuration is prepared properly. - * - * @dataProvider mountDataProvider - * @param bool $isPersonal true for personal mount point, false for system mount point - * @param string $mountType mount type - * @param string $applicable target user/group or "all" - * @param string $testUser user for which to retrieve the mount points - * @param bool $expectVisible whether to expect the mount point to be visible for $testUser - */ - public function testMount($isPersonal, $mountType, $applicable, $testUser, $expectVisible) { - - $mountConfig = array( - 'host' => 'someost', - 'user' => 'someuser', - 'password' => 'somepassword', - 'root' => 'someroot', - 'share' => '', - ); - - // add mount point as "test" user - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $mountConfig, - $mountType, - $applicable, - $isPersonal - ) - ); - - // check mount points in the perspective of user $testUser - \OC_User::setUserId($testUser); - - $mountPoints = OC_Mount_Config::getAbsoluteMountPoints($testUser); - if ($expectVisible) { - $this->assertEquals(1, count($mountPoints)); - $this->assertTrue(isset($mountPoints['/' . self::TEST_USER1 . '/files/ext'])); - $this->assertEquals('Test_Mount_Config_Dummy_Storage', $mountPoints['/' . self::TEST_USER1 . '/files/ext']['class']); - $this->assertEquals($mountConfig, $mountPoints['/' . self::TEST_USER1 . '/files/ext']['options']); - } - else { - $this->assertEquals(0, count($mountPoints)); - } - } - - /** - * Test the same config for multiple users. - * The config will be merged by getSystemMountPoints(). - */ - public function testConfigMerging() { - - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $isPersonal = false; - $options = array( - 'host' => 'smbhost', - 'user' => 'smbuser', - 'password' => 'smbpassword', - 'share' => 'smbshare', - 'root' => 'smbroot' - ); - - // write config - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $options, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER1, - $isPersonal - ) - ); - - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $options, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER2, - $isPersonal - ) - ); - - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $options, - OC_Mount_Config::MOUNT_TYPE_GROUP, - self::TEST_GROUP2, - $isPersonal - ) - ); - - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $options, - OC_Mount_Config::MOUNT_TYPE_GROUP, - self::TEST_GROUP1, - $isPersonal - ) - ); - - // re-read config - $config = OC_Mount_Config::getSystemMountPoints(); - $this->assertEquals(1, count($config)); - $this->assertEquals('Test_Mount_Config_Dummy_Storage', $config[0]['class']); - $this->assertEquals('ext', $config[0]['mountpoint']); - $this->assertEquals($options, $config[0]['options']); - $this->assertEquals(array(self::TEST_USER1, self::TEST_USER2), $config[0]['applicable']['users']); - $this->assertEquals(array(self::TEST_GROUP2, self::TEST_GROUP1), $config[0]['applicable']['groups']); - } - - /** - * Create then re-read mount points configs where the mount points - * have the same path, the config must NOT be merged. - */ - public function testRereadMountpointWithSamePath() { - - $mountType = OC_Mount_Config::MOUNT_TYPE_USER; - $isPersonal = false; - $options1 = array( - 'host' => 'smbhost', - 'user' => 'smbuser', - 'password' => 'smbpassword', - 'share' => 'smbshare', - 'root' => 'smbroot' - ); - - // write config - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $options1, - $mountType, - self::TEST_USER1, - $isPersonal - ) - ); - - $options2 = array( - 'host' => 'anothersmbhost', - 'user' => 'anothersmbuser', - 'password' => 'anothersmbpassword', - 'share' => 'anothersmbshare', - 'root' => 'anothersmbroot' - ); - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $options2, - $mountType, - self::TEST_USER2, - $isPersonal - ) - ); - - // re-read config - $config = OC_Mount_Config::getSystemMountPoints(); - $this->assertEquals(2, count($config)); - $this->assertEquals('Test_Mount_Config_Dummy_Storage', $config[0]['class']); - $this->assertEquals('ext', $config[0]['mountpoint']); - $this->assertEquals($options1, $config[0]['options']); - $this->assertEquals('Test_Mount_Config_Dummy_Storage', $config[1]['class']); - $this->assertEquals('ext', $config[1]['mountpoint']); - $this->assertEquals($options2, $config[1]['options']); - } - - public function priorityDataProvider() { - return array( - - // test 1 - group vs group - array( - array( - array( - 'isPersonal' => false, - 'mountType' => OC_Mount_Config::MOUNT_TYPE_GROUP, - 'applicable' => self::TEST_GROUP1, - 'priority' => 50 - ), - array( - 'isPersonal' => false, - 'mountType' => OC_Mount_Config::MOUNT_TYPE_GROUP, - 'applicable' => self::TEST_GROUP1B, - 'priority' => 60 - ) - ), - 1 - ), - // test 2 - user vs personal - array( - array( - array( - 'isPersonal' => false, - 'mountType' => OC_Mount_Config::MOUNT_TYPE_USER, - 'applicable' => self::TEST_USER1, - 'priority' => 2000 - ), - array( - 'isPersonal' => true, - 'mountType' => OC_Mount_Config::MOUNT_TYPE_USER, - 'applicable' => self::TEST_USER1, - 'priority' => null - ) - ), - 1 - ), - // test 3 - all vs group vs user - array( - array( - array( - 'isPersonal' => false, - 'mountType' => OC_Mount_Config::MOUNT_TYPE_USER, - 'applicable' => 'all', - 'priority' => 70 - ), - array( - 'isPersonal' => false, - 'mountType' => OC_Mount_Config::MOUNT_TYPE_GROUP, - 'applicable' => self::TEST_GROUP1, - 'priority' => 60 - ), - array( - 'isPersonal' => false, - 'mountType' => OC_Mount_Config::MOUNT_TYPE_USER, - 'applicable' => self::TEST_USER1, - 'priority' => 50 - ) - ), - 2 - ) - - ); - } - - /** - * Ensure priorities are being respected - * Test user is self::TEST_USER1 - * - * @dataProvider priorityDataProvider - * @param array[] $mounts array of associative array of mount parameters: - * bool $isPersonal - * string $mountType - * string $applicable - * int|null $priority null for personal - * @param int $expected index of expected visible mount - */ - public function testPriority($mounts, $expected) { - - $mountConfig = array( - 'host' => 'somehost', - 'user' => 'someuser', - 'password' => 'somepassword', - 'root' => 'someroot', - 'share' => '', - ); - - // Add mount points - foreach($mounts as $i => $mount) { - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $mountConfig + array('id' => $i), - $mount['mountType'], - $mount['applicable'], - $mount['isPersonal'], - $mount['priority'] - ) - ); - } - - // Get mount points for user - $mountPoints = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER1); - - $this->assertEquals(1, count($mountPoints)); - $this->assertEquals($expected, $mountPoints['/'.self::TEST_USER1.'/files/ext']['options']['id']); - } - - /** - * Test for persistence of priority when changing mount options - */ - public function testPriorityPersistence() { - - $class = 'Test_Mount_Config_Dummy_Storage'; - $priority = 123; - $mountConfig = array( - 'host' => 'somehost', - 'user' => 'someuser', - 'password' => 'somepassword', - 'root' => 'someroot', - 'share' => '', - ); - - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - $class, - $mountConfig, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER1, - false, - $priority - ) - ); - - // Check for correct priority - $mountPoints = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER1); - $this->assertEquals($priority, - $mountPoints['/'.self::TEST_USER1.'/files/ext']['priority']); - - // Simulate changed mount options (without priority set) - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - $class, - $mountConfig, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER1, - false - ) - ); - - // Check for correct priority - $mountPoints = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER1); - $this->assertEquals($priority, - $mountPoints['/'.self::TEST_USER1.'/files/ext']['priority']); - } - - /* - * Test for correct personal configuration loading in file sharing scenarios - */ - public function testMultiUserPersonalConfigLoading() { - $mountConfig = array( - 'host' => 'somehost', - 'user' => 'someuser', - 'password' => 'somepassword', - 'root' => 'someroot', - 'share' => '', - ); - - // Create personal mount point - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - 'Test_Mount_Config_Dummy_Storage', - $mountConfig, - OC_Mount_Config::MOUNT_TYPE_USER, - self::TEST_USER1, - true - ) - ); - - // Ensure other user can read mount points - \OC_User::setUserId(self::TEST_USER2); - $mountPointsMe = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER2); - $mountPointsOther = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER1); - - $this->assertEquals(0, count($mountPointsMe)); - $this->assertEquals(1, count($mountPointsOther)); - $this->assertTrue(isset($mountPointsOther['/'.self::TEST_USER1.'/files/ext'])); - $this->assertEquals('Test_Mount_Config_Dummy_Storage', - $mountPointsOther['/'.self::TEST_USER1.'/files/ext']['class']); - $this->assertEquals($mountConfig, - $mountPointsOther['/'.self::TEST_USER1.'/files/ext']['options']); - } - - public function testAllowWritingIncompleteConfigIfStorageContructorFails() { - $storageClass = 'Test_Mount_Config_Dummy_Storage'; - $mountType = 'user'; - $applicable = 'all'; - $isPersonal = false; - - $this->assertEquals( - 0, - OC_Mount_Config::addMountPoint( - '/ext', - $storageClass, - array('simulateFail' => true), - $mountType, - $applicable, - $isPersonal - ) - ); - - // config can be retrieved afterwards - $mounts = OC_Mount_Config::getSystemMountPoints(); - $this->assertEquals(1, count($mounts)); - - // no storage id was set - $this->assertFalse(isset($mounts[0]['storage_id'])); - } -} diff --git a/apps/files_external/tests/service/backendservicetest.php b/apps/files_external/tests/service/backendservicetest.php new file mode 100644 index 00000000000..08f6b9bf988 --- /dev/null +++ b/apps/files_external/tests/service/backendservicetest.php @@ -0,0 +1,152 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Tests\Service; + +use \OCA\Files_External\Service\BackendService; + +class BackendServiceTest extends \Test\TestCase { + + /** @var \OCP\IConfig */ + protected $config; + + /** @var \OCP\IL10N */ + protected $l10n; + + protected function setUp() { + $this->config = $this->getMock('\OCP\IConfig'); + $this->l10n = $this->getMock('\OCP\IL10N'); + } + + protected function getBackendMock($class) { + $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->disableOriginalConstructor() + ->getMock(); + $backend->method('getIdentifier')->will($this->returnValue('identifier:'.$class)); + $backend->method('getIdentifierAliases')->will($this->returnValue(['identifier:'.$class])); + return $backend; + } + + public function testRegisterBackend() { + $service = new BackendService($this->config, $this->l10n); + + $backend = $this->getBackendMock('\Foo\Bar'); + + $backendAlias = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->disableOriginalConstructor() + ->getMock(); + $backendAlias->method('getIdentifierAliases') + ->willReturn(['identifier_real', 'identifier_alias']); + $backendAlias->method('getIdentifier') + ->willReturn('identifier_real'); + + $service->registerBackend($backend); + $service->registerBackend($backendAlias); + + $this->assertEquals($backend, $service->getBackend('identifier:\Foo\Bar')); + $this->assertEquals($backendAlias, $service->getBackend('identifier_real')); + $this->assertEquals($backendAlias, $service->getBackend('identifier_alias')); + + $backends = $service->getBackends(); + $this->assertCount(2, $backends); + $this->assertArrayHasKey('identifier:\Foo\Bar', $backends); + $this->assertArrayHasKey('identifier_real', $backends); + $this->assertArrayNotHasKey('identifier_alias', $backends); + } + + public function testUserMountingBackends() { + $this->config->expects($this->exactly(2)) + ->method('getAppValue') + ->will($this->returnValueMap([ + ['files_external', 'allow_user_mounting', 'yes', 'yes'], + ['files_external', 'user_mounting_backends', '', 'identifier:\User\Mount\Allowed,identifier_alias'] + ])); + + $service = new BackendService($this->config, $this->l10n); + + $backendAllowed = $this->getBackendMock('\User\Mount\Allowed'); + $backendAllowed->expects($this->never()) + ->method('removeVisibility'); + $backendNotAllowed = $this->getBackendMock('\User\Mount\NotAllowed'); + $backendNotAllowed->expects($this->once()) + ->method('removeVisibility') + ->with(BackendService::VISIBILITY_PERSONAL); + + $backendAlias = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->disableOriginalConstructor() + ->getMock(); + $backendAlias->method('getIdentifierAliases') + ->willReturn(['identifier_real', 'identifier_alias']); + $backendAlias->expects($this->never()) + ->method('removeVisibility'); + + $service->registerBackend($backendAllowed); + $service->registerBackend($backendNotAllowed); + $service->registerBackend($backendAlias); + } + + public function testGetAvailableBackends() { + $service = new BackendService($this->config, $this->l10n); + + $backendAvailable = $this->getBackendMock('\Backend\Available'); + $backendAvailable->expects($this->once()) + ->method('checkDependencies') + ->will($this->returnValue([])); + $backendNotAvailable = $this->getBackendMock('\Backend\NotAvailable'); + $backendNotAvailable->expects($this->once()) + ->method('checkDependencies') + ->will($this->returnValue([ + $this->getMockBuilder('\OCA\Files_External\Lib\MissingDependency') + ->disableOriginalConstructor() + ->getMock() + ])); + + $service->registerBackend($backendAvailable); + $service->registerBackend($backendNotAvailable); + + $availableBackends = $service->getAvailableBackends(); + $this->assertArrayHasKey('identifier:\Backend\Available', $availableBackends); + $this->assertArrayNotHasKey('identifier:\Backend\NotAvailable', $availableBackends); + } + + public function testGetUserBackends() { + $service = new BackendService($this->config, $this->l10n); + + $backendAllowed = $this->getBackendMock('\User\Mount\Allowed'); + $backendAllowed->expects($this->once()) + ->method('isVisibleFor') + ->with(BackendService::VISIBILITY_PERSONAL) + ->will($this->returnValue(true)); + $backendNotAllowed = $this->getBackendMock('\User\Mount\NotAllowed'); + $backendNotAllowed->expects($this->once()) + ->method('isVisibleFor') + ->with(BackendService::VISIBILITY_PERSONAL) + ->will($this->returnValue(false)); + + $service->registerBackend($backendAllowed); + $service->registerBackend($backendNotAllowed); + + $userBackends = $service->getBackendsVisibleFor(BackendService::VISIBILITY_PERSONAL); + $this->assertArrayHasKey('identifier:\User\Mount\Allowed', $userBackends); + $this->assertArrayNotHasKey('identifier:\User\Mount\NotAllowed', $userBackends); + } + +} + diff --git a/apps/files_external/tests/service/globalstoragesservicetest.php b/apps/files_external/tests/service/globalstoragesservicetest.php index ac25f58b1d1..05585d2c065 100644 --- a/apps/files_external/tests/service/globalstoragesservicetest.php +++ b/apps/files_external/tests/service/globalstoragesservicetest.php @@ -29,7 +29,7 @@ use \OCA\Files_external\Lib\StorageConfig; class GlobalStoragesServiceTest extends StoragesServiceTest { public function setUp() { parent::setUp(); - $this->service = new GlobalStoragesService(); + $this->service = new GlobalStoragesService($this->backendService); } public function tearDown() { @@ -40,7 +40,8 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { protected function makeTestStorageData() { return $this->makeStorageConfig([ 'mountPoint' => 'mountpoint', - 'backendClass' => '\OC\Files\Storage\SMB', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', 'backendOptions' => [ 'option1' => 'value1', 'option2' => 'value2', @@ -59,9 +60,10 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { return [ // all users [ - $this->makeStorageConfig([ + [ 'mountPoint' => 'mountpoint', - 'backendClass' => '\OC\Files\Storage\SMB', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', 'backendOptions' => [ 'option1' => 'value1', 'option2' => 'value2', @@ -70,13 +72,14 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { 'applicableUsers' => [], 'applicableGroups' => [], 'priority' => 15, - ]), + ], ], // some users [ - $this->makeStorageConfig([ + [ 'mountPoint' => 'mountpoint', - 'backendClass' => '\OC\Files\Storage\SMB', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', 'backendOptions' => [ 'option1' => 'value1', 'option2' => 'value2', @@ -85,13 +88,14 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { 'applicableUsers' => ['user1', 'user2'], 'applicableGroups' => [], 'priority' => 15, - ]), + ], ], // some groups [ - $this->makeStorageConfig([ + [ 'mountPoint' => 'mountpoint', - 'backendClass' => '\OC\Files\Storage\SMB', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', 'backendOptions' => [ 'option1' => 'value1', 'option2' => 'value2', @@ -100,13 +104,14 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { 'applicableUsers' => [], 'applicableGroups' => ['group1', 'group2'], 'priority' => 15, - ]), + ], ], // both users and groups [ - $this->makeStorageConfig([ + [ 'mountPoint' => 'mountpoint', - 'backendClass' => '\OC\Files\Storage\SMB', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', 'backendOptions' => [ 'option1' => 'value1', 'option2' => 'value2', @@ -115,7 +120,7 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { 'applicableUsers' => ['user1', 'user2'], 'applicableGroups' => ['group1', 'group2'], 'priority' => 15, - ]), + ], ], ]; } @@ -123,7 +128,8 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { /** * @dataProvider storageDataProvider */ - public function testAddStorage($storage) { + public function testAddStorage($storageParams) { + $storage = $this->makeStorageConfig($storageParams); $newStorage = $this->service->addStorage($storage); $this->assertEquals(1, $newStorage->getId()); @@ -132,7 +138,8 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { $newStorage = $this->service->getStorage(1); $this->assertEquals($storage->getMountPoint(), $newStorage->getMountPoint()); - $this->assertEquals($storage->getBackendClass(), $newStorage->getBackendClass()); + $this->assertEquals($storage->getBackend(), $newStorage->getBackend()); + $this->assertEquals($storage->getAuthMechanism(), $newStorage->getAuthMechanism()); $this->assertEquals($storage->getBackendOptions(), $newStorage->getBackendOptions()); $this->assertEquals($storage->getApplicableUsers(), $newStorage->getApplicableUsers()); $this->assertEquals($storage->getApplicableGroups(), $newStorage->getApplicableGroups()); @@ -148,10 +155,12 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { /** * @dataProvider storageDataProvider */ - public function testUpdateStorage($updatedStorage) { + public function testUpdateStorage($updatedStorageParams) { + $updatedStorage = $this->makeStorageConfig($updatedStorageParams); $storage = $this->makeStorageConfig([ 'mountPoint' => 'mountpoint', - 'backendClass' => '\OC\Files\Storage\SMB', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', 'backendOptions' => [ 'option1' => 'value1', 'option2' => 'value2', @@ -638,7 +647,8 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { $mountPointOptions = current($mountPointData); $this->assertEquals(1, $mountPointOptions['id']); - $this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']); + $this->assertEquals('identifier:\OCA\Files_External\Lib\Backend\SMB', $mountPointOptions['backend']); + $this->assertEquals('identifier:\Auth\Mechanism', $mountPointOptions['authMechanism']); $this->assertEquals(15, $mountPointOptions['priority']); $this->assertEquals(false, $mountPointOptions['mountOptions']['preview']); @@ -678,7 +688,8 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { $mountPointOptions = current($mountPointData); $this->assertEquals(1, $mountPointOptions['id']); - $this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']); + $this->assertEquals('identifier:\OCA\Files_External\Lib\Backend\SMB', $mountPointOptions['backend']); + $this->assertEquals('identifier:\Auth\Mechanism', $mountPointOptions['authMechanism']); $this->assertEquals(15, $mountPointOptions['priority']); $this->assertEquals(false, $mountPointOptions['mountOptions']['preview']); @@ -695,7 +706,8 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { $mountPointOptions = current($mountPointData); $this->assertEquals(1, $mountPointOptions['id']); - $this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']); + $this->assertEquals('identifier:\OCA\Files_External\Lib\Backend\SMB', $mountPointOptions['backend']); + $this->assertEquals('identifier:\Auth\Mechanism', $mountPointOptions['authMechanism']); $this->assertEquals(15, $mountPointOptions['priority']); $this->assertEquals(false, $mountPointOptions['mountOptions']['preview']); @@ -720,13 +732,15 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { $legacyBackendOptions = \OC_Mount_Config::encryptPasswords($legacyBackendOptions); $legacyConfig = [ - 'class' => '\OC\Files\Storage\SMB', + 'backend' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanism' => 'identifier:\Auth\Mechanism', 'options' => $legacyBackendOptions, 'mountOptions' => ['preview' => false], ]; // different mount options $legacyConfig2 = [ - 'class' => '\OC\Files\Storage\SMB', + 'backend' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanism' => 'identifier:\Auth\Mechanism', 'options' => $legacyBackendOptions, 'mountOptions' => ['preview' => true], ]; @@ -737,7 +751,8 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { // different config $legacyConfig3 = [ - 'class' => '\OC\Files\Storage\SMB', + 'backend' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanism' => 'identifier:\Auth\Mechanism', 'options' => $legacyBackendOptions2, 'mountOptions' => ['preview' => true], ]; @@ -811,4 +826,85 @@ class GlobalStoragesServiceTest extends StoragesServiceTest { $this->assertEquals([], $storage4->getApplicableGroups()); $this->assertEquals(['preview' => true], $storage4->getMountOptions()); } + + public function testReadLegacyConfigNoAuthMechanism() { + $configFile = $this->dataDir . '/mount.json'; + + $json = [ + 'user' => [ + 'user1' => [ + '/$user/files/somemount' => [ + 'backend' => 'identifier:\OCA\Files_External\Lib\Backend\SFTP', + 'authMechanism' => 'identifier:\Auth\Mechanism', + 'options' => [], + 'mountOptions' => [], + ], + '/$user/files/othermount' => [ + 'backend' => 'identifier:\OCA\Files_External\Lib\Backend\SFTP', + // no authMechanism + 'options' => [], + 'mountOptions' => [], + ], + ] + ] + ]; + + file_put_contents($configFile, json_encode($json)); + + $allStorages = $this->service->getAllStorages(); + + $this->assertCount(2, $allStorages); + + $storage1 = $allStorages[1]; + $storage2 = $allStorages[2]; + + $this->assertEquals('/somemount', $storage1->getMountPoint()); + $this->assertEquals('identifier:\OCA\Files_External\Lib\Backend\SFTP', $storage1->getBackend()->getIdentifier()); + $this->assertEquals('identifier:\Auth\Mechanism', $storage1->getAuthMechanism()->getIdentifier()); + + $this->assertEquals('/othermount', $storage2->getMountPoint()); + $this->assertEquals('identifier:\OCA\Files_External\Lib\Backend\SFTP', $storage2->getBackend()->getIdentifier()); + $this->assertEquals('identifier:\Other\Auth\Mechanism', $storage2->getAuthMechanism()->getIdentifier()); + } + + public function testReadLegacyConfigClass() { + $configFile = $this->dataDir . '/mount.json'; + + $json = [ + 'user' => [ + 'user1' => [ + '/$user/files/somemount' => [ + 'class' => 'identifier:\OCA\Files_External\Lib\Backend\SFTP', + 'authMechanism' => 'identifier:\Auth\Mechanism', + 'options' => [], + 'mountOptions' => [], + ], + '/$user/files/othermount' => [ + 'class' => 'identifier:sftp_alias', + 'authMechanism' => 'identifier:\Auth\Mechanism', + 'options' => [], + 'mountOptions' => [], + ], + ] + ] + ]; + + file_put_contents($configFile, json_encode($json)); + + $allStorages = $this->service->getAllStorages(); + + $this->assertCount(2, $allStorages); + + $storage1 = $allStorages[1]; + $storage2 = $allStorages[2]; + + $this->assertEquals('/somemount', $storage1->getMountPoint()); + $this->assertEquals('identifier:\OCA\Files_External\Lib\Backend\SFTP', $storage1->getBackend()->getIdentifier()); + $this->assertEquals('identifier:\Auth\Mechanism', $storage1->getAuthMechanism()->getIdentifier()); + + $this->assertEquals('/othermount', $storage2->getMountPoint()); + $this->assertEquals('identifier:\OCA\Files_External\Lib\Backend\SFTP', $storage2->getBackend()->getIdentifier()); + $this->assertEquals('identifier:\Auth\Mechanism', $storage2->getAuthMechanism()->getIdentifier()); + } + } diff --git a/apps/files_external/tests/service/storagesservicetest.php b/apps/files_external/tests/service/storagesservicetest.php index 36f68a83b11..28220c9bc2e 100644 --- a/apps/files_external/tests/service/storagesservicetest.php +++ b/apps/files_external/tests/service/storagesservicetest.php @@ -1,6 +1,7 @@ <?php /** * @author Vincent Petry <pvince81@owncloud.com> + * @author Robin McCorkell <rmccorkell@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 @@ -24,6 +25,7 @@ use \OC\Files\Filesystem; use \OCA\Files_external\NotFoundException; use \OCA\Files_external\Lib\StorageConfig; +use \OCA\Files_External\Lib\BackendService; abstract class StoragesServiceTest extends \Test\TestCase { @@ -32,6 +34,9 @@ abstract class StoragesServiceTest extends \Test\TestCase { */ protected $service; + /** @var BackendService */ + protected $backendService; + /** * Data directory * @@ -55,6 +60,51 @@ abstract class StoragesServiceTest extends \Test\TestCase { ); \OC_Mount_Config::$skipTest = true; + // prepare BackendService mock + $this->backendService = + $this->getMockBuilder('\OCA\Files_External\Service\BackendService') + ->disableOriginalConstructor() + ->getMock(); + + $authMechanisms = [ + 'identifier:\Auth\Mechanism' => $this->getAuthMechMock('null', '\Auth\Mechanism'), + 'identifier:\Other\Auth\Mechanism' => $this->getAuthMechMock('null', '\Other\Auth\Mechanism'), + 'identifier:\OCA\Files_External\Lib\Auth\NullMechanism' => $this->getAuthMechMock(), + ]; + $this->backendService->method('getAuthMechanism') + ->will($this->returnCallback(function($class) use ($authMechanisms) { + if (isset($authMechanisms[$class])) { + return $authMechanisms[$class]; + } + return null; + })); + $this->backendService->method('getAuthMechanismsByScheme') + ->will($this->returnCallback(function($schemes) use ($authMechanisms) { + return array_filter($authMechanisms, function ($authMech) use ($schemes) { + return in_array($authMech->getScheme(), $schemes, true); + }); + })); + $this->backendService->method('getAuthMechanisms') + ->will($this->returnValue($authMechanisms)); + + $sftpBackend = $this->getBackendMock('\OCA\Files_External\Lib\Backend\SFTP', '\OC\Files\Storage\SFTP'); + $backends = [ + 'identifier:\OCA\Files_External\Lib\Backend\SMB' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\SMB', '\OC\Files\Storage\SMB'), + 'identifier:\OCA\Files_External\Lib\Backend\SFTP' => $sftpBackend, + 'identifier:sftp_alias' => $sftpBackend, + ]; + $backends['identifier:\OCA\Files_External\Lib\Backend\SFTP']->method('getLegacyAuthMechanism') + ->willReturn($authMechanisms['identifier:\Other\Auth\Mechanism']); + $this->backendService->method('getBackend') + ->will($this->returnCallback(function($backendClass) use ($backends) { + if (isset($backends[$backendClass])) { + return $backends[$backendClass]; + } + return null; + })); + $this->backendService->method('getBackends') + ->will($this->returnValue($backends)); + \OCP\Util::connectHook( Filesystem::CLASSNAME, Filesystem::signal_create_mount, @@ -64,6 +114,19 @@ abstract class StoragesServiceTest extends \Test\TestCase { Filesystem::signal_delete_mount, get_class($this), 'deleteHookCallback'); + $containerMock = $this->getMock('\OCP\AppFramework\IAppContainer'); + $containerMock->method('query') + ->will($this->returnCallback(function($name) { + if ($name === 'OCA\Files_External\Service\BackendService') { + return $this->backendService; + } + })); + + \OC_Mount_Config::$app = $this->getMockBuilder('\OCA\Files_External\Appinfo\Application') + ->disableOriginalConstructor() + ->getMock(); + \OC_Mount_Config::$app->method('getContainer') + ->willReturn($containerMock); } public function tearDown() { @@ -71,6 +134,29 @@ abstract class StoragesServiceTest extends \Test\TestCase { self::$hookCalls = array(); } + protected function getBackendMock($class = '\OCA\Files_External\Lib\Backend\SMB', $storageClass = '\OC\Files\Storage\SMB') { + $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->disableOriginalConstructor() + ->getMock(); + $backend->method('getStorageClass') + ->willReturn($storageClass); + $backend->method('getIdentifier') + ->willReturn('identifier:'.$class); + return $backend; + } + + protected function getAuthMechMock($scheme = 'null', $class = '\OCA\Files_External\Lib\Auth\NullMechanism') { + $authMech = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism') + ->disableOriginalConstructor() + ->getMock(); + $authMech->method('getScheme') + ->willReturn($scheme); + $authMech->method('getIdentifier') + ->willReturn('identifier:'.$class); + + return $authMech; + } + /** * Creates a StorageConfig instance based on array data * @@ -84,7 +170,22 @@ abstract class StoragesServiceTest extends \Test\TestCase { $storage->setId($data['id']); } $storage->setMountPoint($data['mountPoint']); - $storage->setBackendClass($data['backendClass']); + if (!isset($data['backend'])) { + // data providers are run before $this->backendService is initialised + // so $data['backend'] can be specified directly + $data['backend'] = $this->backendService->getBackend($data['backendIdentifier']); + } + if (!isset($data['backend'])) { + throw new \Exception('oops, no backend'); + } + if (!isset($data['authMechanism'])) { + $data['authMechanism'] = $this->backendService->getAuthMechanism($data['authMechanismIdentifier']); + } + if (!isset($data['authMechanism'])) { + throw new \Exception('oops, no auth mechanism'); + } + $storage->setBackend($data['backend']); + $storage->setAuthMechanism($data['authMechanism']); $storage->setBackendOptions($data['backendOptions']); if (isset($data['applicableUsers'])) { $storage->setApplicableUsers($data['applicableUsers']); @@ -106,16 +207,22 @@ abstract class StoragesServiceTest extends \Test\TestCase { * @expectedException \OCA\Files_external\NotFoundException */ public function testNonExistingStorage() { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); $storage = new StorageConfig(255); $storage->setMountPoint('mountpoint'); - $storage->setBackendClass('\OC\Files\Storage\SMB'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); $this->service->updateStorage($storage); } public function testDeleteStorage() { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); $storage = new StorageConfig(255); $storage->setMountPoint('mountpoint'); - $storage->setBackendClass('\OC\Files\Storage\SMB'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); $storage->setBackendOptions(['password' => 'testPassword']); $newStorage = $this->service->addStorage($storage); @@ -140,6 +247,64 @@ abstract class StoragesServiceTest extends \Test\TestCase { $this->service->removeStorage(255); } + public function testCreateStorage() { + $mountPoint = 'mount'; + $backendIdentifier = 'identifier:\OCA\Files_External\Lib\Backend\SMB'; + $authMechanismIdentifier = 'identifier:\Auth\Mechanism'; + $backendOptions = ['param' => 'foo', 'param2' => 'bar']; + $mountOptions = ['option' => 'foobar']; + $applicableUsers = ['user1', 'user2']; + $applicableGroups = ['group']; + $priority = 123; + + $backend = $this->backendService->getBackend($backendIdentifier); + $authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier); + + $storage = $this->service->createStorage( + $mountPoint, + $backendIdentifier, + $authMechanismIdentifier, + $backendOptions, + $mountOptions, + $applicableUsers, + $applicableGroups, + $priority + ); + + $this->assertEquals('/'.$mountPoint, $storage->getMountPoint()); + $this->assertEquals($backend, $storage->getBackend()); + $this->assertEquals($authMechanism, $storage->getAuthMechanism()); + $this->assertEquals($backendOptions, $storage->getBackendOptions()); + $this->assertEquals($mountOptions, $storage->getMountOptions()); + $this->assertEquals($applicableUsers, $storage->getApplicableUsers()); + $this->assertEquals($applicableGroups, $storage->getApplicableGroups()); + $this->assertEquals($priority, $storage->getPriority()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCreateStorageInvalidClass() { + $this->service->createStorage( + 'mount', + 'identifier:\OC\Not\A\Backend', + 'identifier:\Auth\Mechanism', + [] + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCreateStorageInvalidAuthMechanismClass() { + $this->service->createStorage( + 'mount', + 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'identifier:\Not\An\Auth\Mechanism', + [] + ); + } + public static function createHookCallback($params) { self::$hookCalls[] = array( 'signal' => Filesystem::signal_create_mount, diff --git a/apps/files_external/tests/service/userglobalstoragesservicetest.php b/apps/files_external/tests/service/userglobalstoragesservicetest.php new file mode 100644 index 00000000000..49a02453840 --- /dev/null +++ b/apps/files_external/tests/service/userglobalstoragesservicetest.php @@ -0,0 +1,215 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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_External\Tests\Service; + +use \OCA\Files_External\Service\UserGlobalStoragesService; +use \OCP\IGroupManager; + +use \OCA\Files_External\Lib\StorageConfig; + +class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { + + protected $groupManager; + + protected $globalStoragesService; + + protected $user; + + const USER_ID = 'test_user'; + const GROUP_ID = 'test_group'; + + public function setUp() { + parent::setUp(); + + $this->globalStoragesService = $this->service; + + $this->user = new \OC\User\User(self::USER_ID, null); + $userSession = $this->getMock('\OCP\IUserSession'); + $userSession + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $this->groupManager = $this->getMock('\OCP\IGroupManager'); + $this->groupManager->method('isInGroup') + ->will($this->returnCallback(function($userId, $groupId) { + if ($userId === self::USER_ID && $groupId === self::GROUP_ID) { + return true; + } + return false; + })); + + $this->service = new UserGlobalStoragesService( + $this->backendService, + $userSession, + $this->groupManager + ); + } + + public function applicableStorageProvider() { + return [ + [[], [], true], + + // not applicable cases + [['user1'], [], false], + [[], ['group1'], false], + [['user1'], ['group1'], false], + + // applicable cases + [[self::USER_ID], [], true], + [[], [self::GROUP_ID], true], + [[self::USER_ID], ['group1'], true], + [['user1'], [self::GROUP_ID], true], + + // sanity checks + [['user1', 'user2', self::USER_ID, 'user3'], [], true], + ]; + } + + /** + * @dataProvider applicableStorageProvider + */ + public function testGetStorageWithApplicable($applicableUsers, $applicableGroups, $isVisible) { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + + $storage = new StorageConfig(); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + $storage->setApplicableUsers($applicableUsers); + $storage->setApplicableGroups($applicableGroups); + + $newStorage = $this->globalStoragesService->addStorage($storage); + + $storages = $this->service->getAllStorages(); + if ($isVisible) { + $this->assertEquals(1, count($storages)); + $retrievedStorage = $this->service->getStorage($newStorage->getId()); + $this->assertEquals('/mountpoint', $retrievedStorage->getMountPoint()); + } else { + $this->assertEquals(0, count($storages)); + } + + } + + /** + * @expectedException \DomainException + */ + public function testAddStorage($storageParams = null) { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $this->service->addStorage($storage); + } + + /** + * @expectedException \DomainException + */ + public function testUpdateStorage($storageParams = null) { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $newStorage = $this->globalStoragesService->addStorage($storage); + + $retrievedStorage = $this->service->getStorage($newStorage->getId()); + $retrievedStorage->setMountPoint('abc'); + $this->service->updateStorage($retrievedStorage); + } + + /** + * @expectedException \DomainException + */ + public function testDeleteStorage() { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $newStorage = $this->globalStoragesService->addStorage($storage); + $this->assertEquals(1, $newStorage->getId()); + + $this->service->removeStorage(1); + } + + public function testHooksAddStorage($a = null, $b = null, $c = null) { + // we don't test this here + $this->assertTrue(true); + } + + public function testHooksUpdateStorage($a = null, $b = null, $c = null, $d = null, $e = null) { + // we don't test this here + $this->assertTrue(true); + } + + public function testHooksRenameMountPoint() { + // we don't test this here + $this->assertTrue(true); + } + + public function testHooksDeleteStorage($a = null, $b = null, $c = null) { + // we don't test this here + $this->assertTrue(true); + } + + public function testLegacyConfigConversionApplicableAll() { + // we don't test this here + $this->assertTrue(true); + } + + public function testLegacyConfigConversionApplicableUserAndGroup() { + // we don't test this here + $this->assertTrue(true); + } + + public function testReadLegacyConfigAndGenerateConfigId() { + // we don't test this here + $this->assertTrue(true); + } + + public function testReadLegacyConfigNoAuthMechanism() { + // we don't test this here + $this->assertTrue(true); + } + + public function testReadLegacyConfigClass() { + // we don't test this here + $this->assertTrue(true); + } + +} diff --git a/apps/files_external/tests/service/userstoragesservicetest.php b/apps/files_external/tests/service/userstoragesservicetest.php index ab102741ee2..0d5b82e2f8c 100644 --- a/apps/files_external/tests/service/userstoragesservicetest.php +++ b/apps/files_external/tests/service/userstoragesservicetest.php @@ -40,7 +40,7 @@ class UserStoragesServiceTest extends StoragesServiceTest { ->method('getUser') ->will($this->returnValue($this->user)); - $this->service = new UserStoragesService($userSession); + $this->service = new UserStoragesService($this->backendService, $userSession); // create home folder mkdir($this->dataDir . '/' . $this->userId . '/'); @@ -54,7 +54,8 @@ class UserStoragesServiceTest extends StoragesServiceTest { private function makeTestStorageData() { return $this->makeStorageConfig([ 'mountPoint' => 'mountpoint', - 'backendClass' => '\OC\Files\Storage\SMB', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', 'backendOptions' => [ 'option1' => 'value1', 'option2' => 'value2', @@ -76,7 +77,8 @@ class UserStoragesServiceTest extends StoragesServiceTest { $newStorage = $this->service->getStorage(1); $this->assertEquals($storage->getMountPoint(), $newStorage->getMountPoint()); - $this->assertEquals($storage->getBackendClass(), $newStorage->getBackendClass()); + $this->assertEquals($storage->getBackend(), $newStorage->getBackend()); + $this->assertEquals($storage->getAuthMechanism(), $newStorage->getAuthMechanism()); $this->assertEquals($storage->getBackendOptions(), $newStorage->getBackendOptions()); $this->assertEquals(1, $newStorage->getId()); $this->assertEquals(0, $newStorage->getStatus()); @@ -98,7 +100,8 @@ class UserStoragesServiceTest extends StoragesServiceTest { public function testUpdateStorage() { $storage = $this->makeStorageConfig([ 'mountPoint' => 'mountpoint', - 'backendClass' => '\OC\Files\Storage\SMB', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', 'backendOptions' => [ 'option1' => 'value1', 'option2' => 'value2', @@ -191,7 +194,8 @@ class UserStoragesServiceTest extends StoragesServiceTest { $mountPointOptions = current($mountPointData); $this->assertEquals(1, $mountPointOptions['id']); - $this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']); + $this->assertEquals('identifier:\OCA\Files_External\Lib\Backend\SMB', $mountPointOptions['backend']); + $this->assertEquals('identifier:\Auth\Mechanism', $mountPointOptions['authMechanism']); $this->assertEquals(false, $mountPointOptions['mountOptions']['preview']); $backendOptions = $mountPointOptions['options']; @@ -214,13 +218,15 @@ class UserStoragesServiceTest extends StoragesServiceTest { $legacyBackendOptions = \OC_Mount_Config::encryptPasswords($legacyBackendOptions); $legacyConfig = [ - 'class' => '\OC\Files\Storage\SMB', + 'backend' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanism' => 'identifier:\Auth\Mechanism', 'options' => $legacyBackendOptions, 'mountOptions' => ['preview' => false], ]; // different mount options $legacyConfig2 = [ - 'class' => '\OC\Files\Storage\SMB', + 'backend' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanism' => 'identifier:\Auth\Mechanism', 'options' => $legacyBackendOptions, 'mountOptions' => ['preview' => true], ]; diff --git a/apps/files_external/tests/storageconfigtest.php b/apps/files_external/tests/storageconfigtest.php index c30a4935ce1..dba5105d7db 100644 --- a/apps/files_external/tests/storageconfigtest.php +++ b/apps/files_external/tests/storageconfigtest.php @@ -26,9 +26,22 @@ use \OCA\Files_external\Lib\StorageConfig; class StorageConfigTest extends \Test\TestCase { public function testJsonSerialization() { + $backend = $this->getMockBuilder('\OCA\Files_External\Lib\Backend\Backend') + ->disableOriginalConstructor() + ->getMock(); + $backend->method('getIdentifier') + ->willReturn('storage::identifier'); + + $authMech = $this->getMockBuilder('\OCA\Files_External\Lib\Auth\AuthMechanism') + ->disableOriginalConstructor() + ->getMock(); + $authMech->method('getIdentifier') + ->willReturn('auth::identifier'); + $storageConfig = new StorageConfig(1); $storageConfig->setMountPoint('test'); - $storageConfig->setBackendClass('\OC\Files\Storage\SMB'); + $storageConfig->setBackend($backend); + $storageConfig->setAuthMechanism($authMech); $storageConfig->setBackendOptions(['user' => 'test', 'password' => 'password123']); $storageConfig->setPriority(128); $storageConfig->setApplicableUsers(['user1', 'user2']); @@ -39,7 +52,8 @@ class StorageConfigTest extends \Test\TestCase { $this->assertEquals(1, $json['id']); $this->assertEquals('/test', $json['mountPoint']); - $this->assertEquals('\OC\Files\Storage\SMB', $json['backendClass']); + $this->assertEquals('storage::identifier', $json['backend']); + $this->assertEquals('auth::identifier', $json['authMechanism']); $this->assertEquals('test', $json['backendOptions']['user']); $this->assertEquals('password123', $json['backendOptions']['password']); $this->assertEquals(128, $json['priority']); diff --git a/apps/files_sharing/api/server2server.php b/apps/files_sharing/api/server2server.php index 8bda0bb8749..211dc52c333 100644 --- a/apps/files_sharing/api/server2server.php +++ b/apps/files_sharing/api/server2server.php @@ -51,6 +51,14 @@ class Server2Server { return new \OC_OCS_Result(null, 400, 'The mountpoint name contains invalid characters.'); } + \OCP\Util::writeLog('files_sharing', 'shareWith before, ' . $shareWith, \OCP\Util::DEBUG); + \OCP\Util::emitHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + array('uid' => &$shareWith) + ); + \OCP\Util::writeLog('files_sharing', 'shareWith after, ' . $shareWith, \OCP\Util::DEBUG); + if (!\OCP\User::userExists($shareWith)) { return new \OC_OCS_Result(null, 400, 'User does not exists'); } diff --git a/apps/files_sharing/lib/share/folder.php b/apps/files_sharing/lib/share/folder.php index e18839b577d..ef42393f58d 100644 --- a/apps/files_sharing/lib/share/folder.php +++ b/apps/files_sharing/lib/share/folder.php @@ -39,7 +39,7 @@ class OC_Share_Backend_Folder extends OC_Share_Backend_File implements OCP\Share $shares = \OCP\Share::getItemSharedWithUser('folder', $parent, $shareWith, $owner); if ($shares) { foreach ($shares as $share) { - $name = substr($share['path'], strrpos($share['path'], '/') + 1); + $name = basename($share['path']); $share['collection']['path'] = $name; $share['collection']['item_type'] = 'folder'; $share['file_path'] = $name; diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 2962f62520d..43c76125e16 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -16,6 +16,7 @@ OCP\Util::addStyle('files', 'files'); OCP\Util::addStyle('files', 'upload'); OCP\Util::addScript('files', 'filesummary'); OCP\Util::addScript('files', 'breadcrumb'); +OCP\Util::addScript('files', 'fileinfomodel'); OCP\Util::addScript('files', 'files'); OCP\Util::addScript('files', 'filelist'); OCP\Util::addscript('files', 'keyboardshortcuts'); diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index 5457e6b654f..68fd1b698e0 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -62,6 +62,13 @@ if(count($configPrefixes) > 0) { OCP\Backgroundjob::registerJob('OCA\user_ldap\lib\Jobs'); OCP\Backgroundjob::registerJob('\OCA\User_LDAP\Jobs\CleanUp'); +\OCP\Util::connectHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + '\OCA\user_ldap\lib\Helper', + 'loginName2UserName' +); + if(OCP\App::isEnabled('user_webdavauth')) { OCP\Util::writeLog('user_ldap', 'user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour', diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php index 40874b2ef9f..57b75823a1d 100644 --- a/apps/user_ldap/lib/helper.php +++ b/apps/user_ldap/lib/helper.php @@ -27,6 +27,9 @@ namespace OCA\user_ldap\lib; +use OCA\user_ldap\lib\LDAP; +use OCA\user_ldap\User_Proxy; + class Helper { /** @@ -181,4 +184,32 @@ class Helper { return $domain; } + + /** + * listens to a hook thrown by server2server sharing and replaces the given + * login name by a username, if it matches an LDAP user. + * + * @param array $param + * @throws \Exception + */ + public static function loginName2UserName($param) { + if(!isset($param['uid'])) { + throw new \Exception('key uid is expected to be set in $param'); + } + + //ain't it ironic? + $helper = new Helper(); + + $configPrefixes = $helper->getServerConfigurationPrefixes(true); + $ldapWrapper = new LDAP(); + $ocConfig = \OC::$server->getConfig(); + + $userBackend = new User_Proxy( + $configPrefixes, $ldapWrapper, $ocConfig + ); + $uid = $userBackend->loginName2UserName($param['uid'] ); + if($uid !== false) { + $param['uid'] = $uid; + } + } } diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index a2f4b4ee9e5..00cba718369 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -71,6 +71,43 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn } /** + * returns the username for the given login name, if available + * + * @param string $loginName + * @return string|false + */ + public function loginName2UserName($loginName) { + try { + $ldapRecord = $this->getLDAPUserByLoginName($loginName); + $user = $this->access->userManager->get($ldapRecord['dn']); + if($user instanceof OfflineUser) { + return false; + } + return $user->getUsername(); + } catch (\Exception $e) { + return false; + } + } + + /** + * returns an LDAP record based on a given login name + * + * @param string $loginName + * @return array + * @throws \Exception + */ + public function getLDAPUserByLoginName($loginName) { + //find out dn of the user name + $attrs = array($this->access->connection->ldapUserDisplayName, 'dn', + 'uid', 'samaccountname'); + $users = $this->access->fetchUsersByLoginName($loginName, $attrs); + if(count($users) < 1) { + throw new \Exception('No user available for the given login name.'); + } + return $users[0]; + } + + /** * Check if the password is correct * @param string $uid The username * @param string $password The password @@ -79,15 +116,14 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn * Check if the password is correct without logging in the user */ public function checkPassword($uid, $password) { - //find out dn of the user name - $attrs = array($this->access->connection->ldapUserDisplayName, 'dn', - 'uid', 'samaccountname'); - $users = $this->access->fetchUsersByLoginName($uid, $attrs); - if(count($users) < 1) { + try { + $ldapRecord = $this->getLDAPUserByLoginName($uid); + } catch(\Exception $e) { return false; } - $dn = $users[0]['dn']; + $dn = $ldapRecord['dn']; $user = $this->access->userManager->get($dn); + if(!$user instanceof User) { \OCP\Util::writeLog('user_ldap', 'LDAP Login: Could not get user object for DN ' . $dn . @@ -102,14 +138,14 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn } $user->markLogin(); - if(isset($users[0][$this->access->connection->ldapUserDisplayName])) { - $dpn = $users[0][$this->access->connection->ldapUserDisplayName]; + if(isset($ldapRecord[$this->access->connection->ldapUserDisplayName])) { + $dpn = $ldapRecord[$this->access->connection->ldapUserDisplayName]; $user->storeDisplayName($dpn); } - if(isset($users[0]['uid'])) { - $user->storeLDAPUserName($users[0]['uid']); - } else if(isset($users[0]['samaccountname'])) { - $user->storeLDAPUserName($users[0]['samaccountname']); + if(isset($ldapRecord['uid'])) { + $user->storeLDAPUserName($ldapRecord['uid']); + } else if(isset($ldapRecord['samaccountname'])) { + $user->storeLDAPUserName($ldapRecord['samaccountname']); } return $user->getUsername(); diff --git a/apps/user_ldap/user_proxy.php b/apps/user_ldap/user_proxy.php index 683529eb902..1491be3f394 100644 --- a/apps/user_ldap/user_proxy.php +++ b/apps/user_ldap/user_proxy.php @@ -161,7 +161,7 @@ class User_Proxy extends lib\Proxy implements \OCP\IUserBackend, \OCP\UserInterf /** * check if a user exists on LDAP - * @param string|OCA\User_LDAP\lib\User\User $user either the ownCloud user + * @param string|\OCA\User_LDAP\lib\User\User $user either the ownCloud user * name or an instance of that user * @return boolean */ @@ -183,6 +183,17 @@ class User_Proxy extends lib\Proxy implements \OCP\IUserBackend, \OCP\UserInterf } /** + * returns the username for the given login name, if available + * + * @param string $loginName + * @return string|false + */ + public function loginName2UserName($loginName) { + $id = 'LOGINNAME,' . $loginName; + return $this->handleRequest($id, 'loginName2UserName', array($loginName)); + } + + /** * get the user's home directory * @param string $uid the username * @return boolean |