aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/dav/appinfo/info.xml6
-rw-r--r--apps/dav/appinfo/v1/caldav.php69
-rw-r--r--apps/dav/appinfo/v1/carddav.php70
-rw-r--r--apps/dav/appinfo/v1/webdav.php3
-rw-r--r--apps/dav/lib/caldav/caldavbackend.php10
-rw-r--r--apps/dav/lib/carddav/addressbookroot.php3
-rw-r--r--apps/dav/lib/carddav/carddavbackend.php43
-rw-r--r--apps/dav/lib/connector/sabre/auth.php6
-rw-r--r--apps/dav/lib/connector/sabre/filesreportplugin.php321
-rw-r--r--apps/dav/lib/connector/sabre/principal.php43
-rw-r--r--apps/dav/lib/connector/sabre/serverfactory.php11
-rw-r--r--apps/dav/lib/dav/sharing/backend.php19
-rw-r--r--apps/dav/tests/unit/connector/sabre/filesreportplugin.php519
-rw-r--r--apps/dav/tests/unit/connector/sabre/principal.php5
-rw-r--r--apps/federatedfilesharing/appinfo/app.php27
-rw-r--r--apps/federatedfilesharing/appinfo/info.xml14
-rw-r--r--apps/federatedfilesharing/lib/addresshandler.php184
-rw-r--r--apps/federatedfilesharing/lib/federatedshareprovider.php562
-rw-r--r--apps/federatedfilesharing/lib/notifications.php144
-rw-r--r--apps/federatedfilesharing/lib/tokenhandler.php61
-rw-r--r--apps/federatedfilesharing/tests/addresshandlertest.php198
-rw-r--r--apps/federatedfilesharing/tests/federatedshareprovidertest.php447
-rw-r--r--apps/federatedfilesharing/tests/tokenhandlertest.php62
-rw-r--r--apps/files/l10n/de.js2
-rw-r--r--apps/files/l10n/de.json2
-rw-r--r--apps/files_external/appinfo/register_command.php2
-rw-r--r--apps/files_external/command/verify.php145
-rw-r--r--apps/files_external/l10n/de.js7
-rw-r--r--apps/files_external/l10n/de.json7
-rw-r--r--apps/files_external/l10n/fr.js8
-rw-r--r--apps/files_external/l10n/fr.json8
-rw-r--r--apps/files_external/l10n/pt_BR.js5
-rw-r--r--apps/files_external/l10n/pt_BR.json5
-rw-r--r--apps/files_sharing/api/share20ocs.php57
-rw-r--r--apps/files_sharing/appinfo/info.xml2
-rw-r--r--apps/files_sharing/appinfo/update.php2
-rw-r--r--apps/files_sharing/lib/migration.php6
-rw-r--r--apps/files_sharing/tests/api/share20ocstest.php13
-rw-r--r--apps/files_sharing/tests/migrationtest.php21
-rw-r--r--apps/files_versions/lib/storage.php21
-rw-r--r--apps/files_versions/tests/versions.php4
-rw-r--r--apps/user_ldap/l10n/fr.js8
-rw-r--r--apps/user_ldap/l10n/fr.json8
-rw-r--r--apps/user_ldap/l10n/pt_BR.js2
-rw-r--r--apps/user_ldap/l10n/pt_BR.json2
45 files changed, 3068 insertions, 96 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index f035d19d862..4f3a93dbf8b 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -14,6 +14,12 @@
<files>appinfo/v1/webdav.php</files>
<webdav>appinfo/v1/webdav.php</webdav>
<dav>appinfo/v2/remote.php</dav>
+ <!-- carddav endpoints as used before ownCloud 9.0 -->
+ <contacts>appinfo/v1/carddav.php</contacts>
+ <carddav>appinfo/v1/carddav.php</carddav>
+ <!-- caldav endpoints as used before ownCloud 9.0 -->
+ <calendar>appinfo/v1/caldav.php</calendar>
+ <caldav>appinfo/v1/caldav.php</caldav>
</remote>
<public>
<webdav>appinfo/v1/publicwebdav.php</webdav>
diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php
new file mode 100644
index 00000000000..f860ced3877
--- /dev/null
+++ b/apps/dav/appinfo/v1/caldav.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, 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/>
+ *
+ */
+
+// Backends
+use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\Connector\Sabre\Auth;
+use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin;
+use OCA\DAV\Connector\Sabre\MaintenancePlugin;
+use OCA\DAV\Connector\Sabre\Principal;
+use Sabre\CalDAV\CalendarRoot;
+
+$authBackend = new Auth(
+ \OC::$server->getSession(),
+ \OC::$server->getUserSession(),
+ 'principals/'
+);
+$principalBackend = new Principal(
+ \OC::$server->getUserManager(),
+ \OC::$server->getGroupManager(),
+ 'principals/'
+);
+$db = \OC::$server->getDatabaseConnection();
+$calDavBackend = new CalDavBackend($db, $principalBackend);
+
+// Root nodes
+$principalCollection = new \Sabre\CalDAV\Principal\Collection($principalBackend);
+$principalCollection->disableListing = true; // Disable listing
+
+$addressBookRoot = new CalendarRoot($principalBackend, $calDavBackend);
+$addressBookRoot->disableListing = true; // Disable listing
+
+$nodes = array(
+ $principalCollection,
+ $addressBookRoot,
+);
+
+// Fire up server
+$server = new \Sabre\DAV\Server($nodes);
+$server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri());
+$server->setBaseUri($baseuri);
+
+// Add plugins
+$server->addPlugin(new MaintenancePlugin());
+$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'ownCloud'));
+$server->addPlugin(new \Sabre\CalDAV\Plugin());
+$server->addPlugin(new \Sabre\DAVACL\Plugin());
+$server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin());
+$server->addPlugin(new ExceptionLoggerPlugin('caldav', \OC::$server->getLogger()));
+
+// And off we go!
+$server->exec();
diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php
new file mode 100644
index 00000000000..e0c79c75b72
--- /dev/null
+++ b/apps/dav/appinfo/v1/carddav.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, 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/>
+ *
+ */
+
+// Backends
+use OCA\DAV\CardDAV\AddressBookRoot;
+use OCA\DAV\CardDAV\CardDavBackend;
+use OCA\DAV\Connector\Sabre\AppEnabledPlugin;
+use OCA\DAV\Connector\Sabre\Auth;
+use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin;
+use OCA\DAV\Connector\Sabre\MaintenancePlugin;
+use OCA\DAV\Connector\Sabre\Principal;
+use Sabre\CardDAV\Plugin;
+
+$authBackend = new Auth(
+ \OC::$server->getSession(),
+ \OC::$server->getUserSession(),
+ 'principals/'
+);
+$principalBackend = new Principal(
+ \OC::$server->getUserManager(),
+ \OC::$server->getGroupManager(),
+ 'principals/'
+);
+$db = \OC::$server->getDatabaseConnection();
+$cardDavBackend = new CardDavBackend($db, $principalBackend);
+
+// Root nodes
+$principalCollection = new \Sabre\CalDAV\Principal\Collection($principalBackend);
+$principalCollection->disableListing = true; // Disable listing
+
+$addressBookRoot = new AddressBookRoot($principalBackend, $cardDavBackend);
+$addressBookRoot->disableListing = true; // Disable listing
+
+$nodes = array(
+ $principalCollection,
+ $addressBookRoot,
+);
+
+// Fire up server
+$server = new \Sabre\DAV\Server($nodes);
+$server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri());
+$server->setBaseUri($baseuri);
+// Add plugins
+$server->addPlugin(new MaintenancePlugin());
+$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'ownCloud'));
+$server->addPlugin(new Plugin());
+$server->addPlugin(new \Sabre\DAVACL\Plugin());
+$server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin());
+$server->addPlugin(new ExceptionLoggerPlugin('carddav', \OC::$server->getLogger()));
+
+// And off we go!
+$server->exec();
diff --git a/apps/dav/appinfo/v1/webdav.php b/apps/dav/appinfo/v1/webdav.php
index d75c3526bdd..3d3e51e84bc 100644
--- a/apps/dav/appinfo/v1/webdav.php
+++ b/apps/dav/appinfo/v1/webdav.php
@@ -40,7 +40,8 @@ $serverFactory = new \OCA\DAV\Connector\Sabre\ServerFactory(
// Backends
$authBackend = new \OCA\DAV\Connector\Sabre\Auth(
\OC::$server->getSession(),
- \OC::$server->getUserSession()
+ \OC::$server->getUserSession(),
+ 'principals/'
);
$requestUri = \OC::$server->getRequest()->getRequestUri();
diff --git a/apps/dav/lib/caldav/caldavbackend.php b/apps/dav/lib/caldav/caldavbackend.php
index 3aa493e5087..52b4812b05b 100644
--- a/apps/dav/lib/caldav/caldavbackend.php
+++ b/apps/dav/lib/caldav/caldavbackend.php
@@ -106,7 +106,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
public function __construct(\OCP\IDBConnection $db, Principal $principalBackend) {
$this->db = $db;
$this->principalBackend = $principalBackend;
- $this->sharingBackend = new Backend($this->db, 'calendar');
+ $this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar');
}
/**
@@ -172,7 +172,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $calendars[$calendar['id']] = $calendar;
+ if (!isset($calendars[$calendar['id']])) {
+ $calendars[$calendar['id']] = $calendar;
+ }
}
$stmt->closeCursor();
@@ -221,7 +223,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $calendars[$calendar['id']] = $calendar;
+ if (!isset($calendars[$calendar['id']])) {
+ $calendars[$calendar['id']] = $calendar;
+ }
}
$result->closeCursor();
diff --git a/apps/dav/lib/carddav/addressbookroot.php b/apps/dav/lib/carddav/addressbookroot.php
index 2680135dec2..99c36c2e767 100644
--- a/apps/dav/lib/carddav/addressbookroot.php
+++ b/apps/dav/lib/carddav/addressbookroot.php
@@ -40,6 +40,9 @@ class AddressBookRoot extends \Sabre\CardDAV\AddressBookRoot {
function getName() {
+ if ($this->principalPrefix === 'principals') {
+ return parent::getName();
+ }
// Grabbing all the components of the principal path.
$parts = explode('/', $this->principalPrefix);
diff --git a/apps/dav/lib/carddav/carddavbackend.php b/apps/dav/lib/carddav/carddavbackend.php
index 9ca166c22a2..c4f29b39d0d 100644
--- a/apps/dav/lib/carddav/carddavbackend.php
+++ b/apps/dav/lib/carddav/carddavbackend.php
@@ -72,7 +72,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
public function __construct(IDBConnection $db, Principal $principalBackend) {
$this->db = $db;
$this->principalBackend = $principalBackend;
- $this->sharingBackend = new Backend($this->db, 'addressbook');
+ $this->sharingBackend = new Backend($this->db, $principalBackend, 'addressbook');
}
/**
@@ -93,11 +93,11 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return array
*/
function getAddressBooksForUser($principalUri) {
+ $principalUri = $this->convertPrincipal($principalUri, true);
$query = $this->db->getQueryBuilder();
$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
->from('addressbooks')
- ->where($query->expr()->eq('principaluri', $query->createParameter('principaluri')))
- ->setParameter('principaluri', $principalUri);
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
$addressBooks = [];
@@ -106,7 +106,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$addressBooks[$row['id']] = [
'id' => $row['id'],
'uri' => $row['uri'],
- 'principaluri' => $row['principaluri'],
+ 'principaluri' => $this->convertPrincipal($row['principaluri'], false),
'{DAV:}displayname' => $row['displayname'],
'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
@@ -133,17 +133,19 @@ class CardDavBackend implements BackendInterface, SyncSupport {
list(, $name) = URLUtil::splitPath($row['principaluri']);
$uri = $row['uri'] . '_shared_by_' . $name;
$displayName = $row['displayname'] . "($name)";
- $addressBooks[$row['id']] = [
- 'id' => $row['id'],
- 'uri' => $uri,
- 'principaluri' => $principalUri,
- '{DAV:}displayname' => $displayName,
- '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
- '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
- '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
- '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
- '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => $row['access'] === self::ACCESS_READ,
- ];
+ if (!isset($addressBooks[$row['id']])) {
+ $addressBooks[$row['id']] = [
+ 'id' => $row['id'],
+ 'uri' => $uri,
+ 'principaluri' => $principalUri,
+ '{DAV:}displayname' => $displayName,
+ '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
+ '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => $row['access'] === self::ACCESS_READ,
+ ];
+ }
}
$result->closeCursor();
@@ -919,4 +921,15 @@ class CardDavBackend implements BackendInterface, SyncSupport {
public function applyShareAcl($addressBookId, $acl) {
return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
}
+
+ private function convertPrincipal($principalUri, $toV2) {
+ if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
+ list(, $name) = URLUtil::splitPath($principalUri);
+ if ($toV2 === true) {
+ return "principals/users/$name";
+ }
+ return "principals/$name";
+ }
+ return $principalUri;
+ }
}
diff --git a/apps/dav/lib/connector/sabre/auth.php b/apps/dav/lib/connector/sabre/auth.php
index cc679e44dbe..a046e078482 100644
--- a/apps/dav/lib/connector/sabre/auth.php
+++ b/apps/dav/lib/connector/sabre/auth.php
@@ -49,12 +49,14 @@ class Auth extends AbstractBasic {
/**
* @param ISession $session
* @param IUserSession $userSession
+ * @param string $principalPrefix
*/
public function __construct(ISession $session,
- IUserSession $userSession) {
+ IUserSession $userSession,
+ $principalPrefix = 'principals/users/') {
$this->session = $session;
$this->userSession = $userSession;
- $this->principalPrefix = 'principals/users/';
+ $this->principalPrefix = $principalPrefix;
}
/**
diff --git a/apps/dav/lib/connector/sabre/filesreportplugin.php b/apps/dav/lib/connector/sabre/filesreportplugin.php
new file mode 100644
index 00000000000..5bdd7a71ddc
--- /dev/null
+++ b/apps/dav/lib/connector/sabre/filesreportplugin.php
@@ -0,0 +1,321 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\DAV\Connector\Sabre;
+
+use OC\Files\View;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\PreconditionFailed;
+use Sabre\DAV\Exception\ReportNotSupported;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\ServerPlugin;
+use Sabre\DAV\Tree;
+use Sabre\DAV\Xml\Element\Response;
+use Sabre\DAV\Xml\Response\MultiStatus;
+use Sabre\DAV\PropFind;
+use OCP\SystemTag\ISystemTagObjectMapper;
+use OCP\IUserSession;
+use OCP\Files\Folder;
+use OCP\IGroupManager;
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\TagNotFoundException;
+
+class FilesReportPlugin extends ServerPlugin {
+
+ // namespace
+ const NS_OWNCLOUD = 'http://owncloud.org/ns';
+ const REPORT_NAME = '{http://owncloud.org/ns}filter-files';
+ const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
+
+ /**
+ * Reference to main server object
+ *
+ * @var \Sabre\DAV\Server
+ */
+ private $server;
+
+ /**
+ * @var Tree
+ */
+ private $tree;
+
+ /**
+ * @var View
+ */
+ private $fileView;
+
+ /**
+ * @var ISystemTagManager
+ */
+ private $tagManager;
+
+ /**
+ * @var ISystemTagObjectMapper
+ */
+ private $tagMapper;
+
+ /**
+ * @var IUserSession
+ */
+ private $userSession;
+
+ /**
+ * @var IGroupManager
+ */
+ private $groupManager;
+
+ /**
+ * @var Folder
+ */
+ private $userFolder;
+
+ /**
+ * @param Tree $tree
+ * @param View $view
+ */
+ public function __construct(Tree $tree,
+ View $view,
+ ISystemTagManager $tagManager,
+ ISystemTagObjectMapper $tagMapper,
+ IUserSession $userSession,
+ IGroupManager $groupManager,
+ Folder $userFolder
+ ) {
+ $this->tree = $tree;
+ $this->fileView = $view;
+ $this->tagManager = $tagManager;
+ $this->tagMapper = $tagMapper;
+ $this->userSession = $userSession;
+ $this->groupManager = $groupManager;
+ $this->userFolder = $userFolder;
+ }
+
+ /**
+ * This initializes the plugin.
+ *
+ * This function is called by \Sabre\DAV\Server, after
+ * addPlugin is called.
+ *
+ * This method should set up the required event subscriptions.
+ *
+ * @param \Sabre\DAV\Server $server
+ * @return void
+ */
+ public function initialize(\Sabre\DAV\Server $server) {
+
+ $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
+
+ $this->server = $server;
+ $this->server->on('report', array($this, 'onReport'));
+ }
+
+ /**
+ * Returns a list of reports this plugin supports.
+ *
+ * This will be used in the {DAV:}supported-report-set property.
+ *
+ * @param string $uri
+ * @return array
+ */
+ public function getSupportedReportSet($uri) {
+ return [self::REPORT_NAME];
+ }
+
+ /**
+ * REPORT operations to look for files
+ *
+ * @param string $reportName
+ * @param [] $report
+ * @param string $uri
+ * @return bool
+ * @throws NotFound
+ * @throws ReportNotSupported
+ */
+ public function onReport($reportName, $report, $uri) {
+ $reportTargetNode = $this->server->tree->getNodeForPath($uri);
+ if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) {
+ throw new ReportNotSupported();
+ }
+
+ $ns = '{' . $this::NS_OWNCLOUD . '}';
+ $requestedProps = [];
+ $filterRules = [];
+
+ // parse report properties and gather filter info
+ foreach ($report as $reportProps) {
+ $name = $reportProps['name'];
+ if ($name === $ns . 'filter-rules') {
+ $filterRules = $reportProps['value'];
+ } else if ($name === '{DAV:}prop') {
+ // propfind properties
+ foreach ($reportProps['value'] as $propVal) {
+ $requestedProps[] = $propVal['name'];
+ }
+ }
+ }
+
+ if (empty($filterRules)) {
+ // an empty filter would return all existing files which would be slow
+ throw new BadRequest('Missing filter-rule block in request');
+ }
+
+ // gather all file ids matching filter
+ try {
+ $resultFileIds = $this->processFilterRules($filterRules);
+ } catch (TagNotFoundException $e) {
+ throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);
+ }
+
+ // find sabre nodes by file id, restricted to the root node path
+ $results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
+
+ $responses = $this->prepareResponses($requestedProps, $results);
+
+ $xml = $this->server->xml->write(
+ '{DAV:}multistatus',
+ new MultiStatus($responses)
+ );
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setBody($xml);
+
+ return false;
+ }
+
+ /**
+ * Find file ids matching the given filter rules
+ *
+ * @param array $filterRules
+ * @return array array of unique file id results
+ *
+ * @throws TagNotFoundException whenever a tag was not found
+ */
+ public function processFilterRules($filterRules) {
+ $ns = '{' . $this::NS_OWNCLOUD . '}';
+ $resultFileIds = [];
+ $systemTagIds = [];
+ foreach ($filterRules as $filterRule) {
+ if ($filterRule['name'] === $ns . 'systemtag') {
+ $systemTagIds[] = $filterRule['value'];
+ }
+ }
+
+ // check user permissions, if applicable
+ if (!$this->isAdmin()) {
+ // check visibility/permission
+ $tags = $this->tagManager->getTagsByIds($systemTagIds);
+ $unknownTagIds = [];
+ foreach ($tags as $tag) {
+ if (!$tag->isUserVisible()) {
+ $unknownTagIds[] = $tag->getId();
+ }
+ }
+
+ if (!empty($unknownTagIds)) {
+ throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
+ }
+ }
+
+ // fetch all file ids and intersect them
+ foreach ($systemTagIds as $systemTagId) {
+ $fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
+
+ if (empty($resultFileIds)) {
+ $resultFileIds = $fileIds;
+ } else {
+ $resultFileIds = array_intersect($resultFileIds, $fileIds);
+ }
+ }
+ return $resultFileIds;
+ }
+
+ /**
+ * Prepare propfind response for the given nodes
+ *
+ * @param string[] $requestedProps requested properties
+ * @param Node[] nodes nodes for which to fetch and prepare responses
+ * @return Response[]
+ */
+ public function prepareResponses($requestedProps, $nodes) {
+ $responses = [];
+ foreach ($nodes as $node) {
+ $propFind = new PropFind($node->getPath(), $requestedProps);
+
+ $this->server->getPropertiesByNode($propFind, $node);
+ // copied from Sabre Server's getPropertiesForPath
+ $result = $propFind->getResultForMultiStatus();
+ $result['href'] = $propFind->getPath();
+
+ $resourceType = $this->server->getResourceTypeForNode($node);
+ if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
+ $result['href'] .= '/';
+ }
+
+ $responses[] = new Response(
+ rtrim($this->server->getBaseUri(), '/') . $node->getPath(),
+ $result,
+ 200
+ );
+ }
+ return $responses;
+ }
+
+ /**
+ * Find Sabre nodes by file ids
+ *
+ * @param Node $rootNode root node for search
+ * @param array $fileIds file ids
+ * @return Node[] array of Sabre nodes
+ */
+ public function findNodesByFileIds($rootNode, $fileIds) {
+ $folder = $this->userFolder;
+ if (trim($rootNode->getPath(), '/') !== '') {
+ $folder = $folder->get($rootNode->getPath());
+ }
+
+ $results = [];
+ foreach ($fileIds as $fileId) {
+ $entry = $folder->getById($fileId);
+ if ($entry) {
+ $entry = current($entry);
+ if ($entry instanceof \OCP\Files\File) {
+ $results[] = new File($this->fileView, $entry);
+ } else if ($entry instanceof \OCP\Files\Folder) {
+ $results[] = new Directory($this->fileView, $entry);
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Returns whether the currently logged in user is an administrator
+ */
+ private function isAdmin() {
+ $user = $this->userSession->getUser();
+ if ($user !== null) {
+ return $this->groupManager->isAdmin($user->getUID());
+ }
+ return false;
+ }
+}
diff --git a/apps/dav/lib/connector/sabre/principal.php b/apps/dav/lib/connector/sabre/principal.php
index 5f02d1271df..4f26390e3cc 100644
--- a/apps/dav/lib/connector/sabre/principal.php
+++ b/apps/dav/lib/connector/sabre/principal.php
@@ -46,12 +46,20 @@ class Principal implements BackendInterface {
/** @var IGroupManager */
private $groupManager;
+ /** @var string */
+ private $principalPrefix;
+
/**
* @param IUserManager $userManager
+ * @param IGroupManager $groupManager
+ * @param string $principalPrefix
*/
- public function __construct(IUserManager $userManager, IGroupManager $groupManager) {
+ public function __construct(IUserManager $userManager,
+ IGroupManager $groupManager,
+ $principalPrefix = 'principals/users/') {
$this->userManager = $userManager;
$this->groupManager = $groupManager;
+ $this->principalPrefix = trim($principalPrefix, '/');
}
/**
@@ -70,7 +78,7 @@ class Principal implements BackendInterface {
public function getPrincipalsByPrefix($prefixPath) {
$principals = [];
- if ($prefixPath === 'principals/users') {
+ if ($prefixPath === $this->principalPrefix) {
foreach($this->userManager->search('') as $user) {
$principals[] = $this->userToPrincipal($user);
}
@@ -88,20 +96,15 @@ class Principal implements BackendInterface {
* @return array
*/
public function getPrincipalByPath($path) {
- $elements = explode('/', $path);
- if ($elements[0] !== 'principals') {
- return null;
- }
- if ($elements[1] !== 'users') {
- return null;
- }
- $name = $elements[2];
- $user = $this->userManager->get($name);
+ list($prefix, $name) = URLUtil::splitPath($path);
- if (!is_null($user)) {
- return $this->userToPrincipal($user);
- }
+ if ($prefix === $this->principalPrefix) {
+ $user = $this->userManager->get($name);
+ if (!is_null($user)) {
+ return $this->userToPrincipal($user);
+ }
+ }
return null;
}
@@ -132,7 +135,7 @@ class Principal implements BackendInterface {
public function getGroupMembership($principal) {
list($prefix, $name) = URLUtil::splitPath($principal);
- if ($prefix === 'principals/users') {
+ if ($prefix === $this->principalPrefix) {
$user = $this->userManager->get($name);
if (!$user) {
throw new Exception('Principal not found');
@@ -141,11 +144,9 @@ class Principal implements BackendInterface {
$groups = $this->groupManager->getUserGroups($user);
$groups = array_map(function($group) {
/** @var IGroup $group */
- return 'principals/groups/' . $group->getGID();
+ return $this->principalPrefix . '/' . $group->getGID();
}, $groups);
- $groups[]= 'principals/users/'.$name.'/calendar-proxy-read';
- $groups[]= 'principals/users/'.$name.'/calendar-proxy-write';
return $groups;
}
return [];
@@ -200,7 +201,7 @@ class Principal implements BackendInterface {
$userId = $user->getUID();
$displayName = $user->getDisplayName();
$principal = [
- 'uri' => "principals/users/$userId",
+ 'uri' => $this->principalPrefix . '/' . $userId,
'{DAV:}displayname' => is_null($displayName) ? $userId : $displayName,
];
@@ -212,4 +213,8 @@ class Principal implements BackendInterface {
return $principal;
}
+ public function getPrincipalPrefix() {
+ return $this->principalPrefix;
+ }
+
}
diff --git a/apps/dav/lib/connector/sabre/serverfactory.php b/apps/dav/lib/connector/sabre/serverfactory.php
index fa4fda46870..9a828787a0d 100644
--- a/apps/dav/lib/connector/sabre/serverfactory.php
+++ b/apps/dav/lib/connector/sabre/serverfactory.php
@@ -115,7 +115,7 @@ class ServerFactory {
// wait with registering these until auth is handled and the filesystem is setup
$server->on('beforeMethod', function () use ($server, $objectTree, $viewCallBack) {
// ensure the skeleton is copied
- \OC::$server->getUserFolder();
+ $userFolder = \OC::$server->getUserFolder();
/** @var \OC\Files\View $view */
$view = $viewCallBack();
@@ -135,6 +135,15 @@ class ServerFactory {
if($this->userSession->isLoggedIn()) {
$server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin(\OC::$server->getCommentsManager(), $this->userSession));
+ $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesReportPlugin(
+ $objectTree,
+ $view,
+ \OC::$server->getSystemTagManager(),
+ \OC::$server->getSystemTagObjectMapper(),
+ $this->userSession,
+ \OC::$server->getGroupManager(),
+ $userFolder
+ ));
// custom properties plugin must be the last one
$server->addPlugin(
new \Sabre\DAV\PropertyStorage\Plugin(
diff --git a/apps/dav/lib/dav/sharing/backend.php b/apps/dav/lib/dav/sharing/backend.php
index 0b28891fbc4..7556706b6d0 100644
--- a/apps/dav/lib/dav/sharing/backend.php
+++ b/apps/dav/lib/dav/sharing/backend.php
@@ -24,27 +24,30 @@
namespace OCA\DAV\DAV\Sharing;
+use OCA\DAV\Connector\Sabre\Principal;
use OCP\IDBConnection;
class Backend {
/** @var IDBConnection */
private $db;
+ /** @var Principal */
+ private $principalBackend;
+ /** @var string */
+ private $resourceType;
const ACCESS_OWNER = 1;
const ACCESS_READ_WRITE = 2;
const ACCESS_READ = 3;
- /** @var string */
- private $resourceType;
-
/**
- * CardDavBackend constructor.
- *
* @param IDBConnection $db
+ * @param Principal $principalBackend
+ * @param string $resourceType
*/
- public function __construct(IDBConnection $db, $resourceType) {
+ public function __construct(IDBConnection $db, Principal $principalBackend, $resourceType) {
$this->db = $db;
+ $this->principalBackend = $principalBackend;
$this->resourceType = $resourceType;
}
@@ -141,6 +144,7 @@ class Backend {
* * readOnly - boolean
* * summary - Optional, a description for the share
*
+ * @param int $resourceId
* @return array
*/
public function getShares($resourceId) {
@@ -153,9 +157,10 @@ class Backend {
$shares = [];
while($row = $result->fetch()) {
+ $p = $this->principalBackend->getPrincipalByPath($row['principaluri']);
$shares[]= [
'href' => "principal:${row['principaluri']}",
-// 'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '',
+ 'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '',
'status' => 1,
'readOnly' => ($row['access'] == self::ACCESS_READ),
'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}principal' => $row['principaluri']
diff --git a/apps/dav/tests/unit/connector/sabre/filesreportplugin.php b/apps/dav/tests/unit/connector/sabre/filesreportplugin.php
new file mode 100644
index 00000000000..853e52f5039
--- /dev/null
+++ b/apps/dav/tests/unit/connector/sabre/filesreportplugin.php
@@ -0,0 +1,519 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\DAV\Tests\Unit\Sabre\Connector;
+
+use OCA\DAV\Connector\Sabre\FilesReportPlugin as FilesReportPluginImplementation;
+use Sabre\DAV\Exception\NotFound;
+use OCP\SystemTag\ISystemTagObjectMapper;
+use OC\Files\View;
+use OCP\Files\Folder;
+use OCP\IGroupManager;
+use OCP\SystemTag\ISystemTagManager;
+
+class FilesReportPlugin extends \Test\TestCase {
+ /** @var \Sabre\DAV\Server */
+ private $server;
+
+ /** @var \Sabre\DAV\Tree */
+ private $tree;
+
+ /** @var ISystemTagObjectMapper */
+ private $tagMapper;
+
+ /** @var ISystemTagManager */
+ private $tagManager;
+
+ /** @var \OCP\IUserSession */
+ private $userSession;
+
+ /** @var FilesReportPluginImplementation */
+ private $plugin;
+
+ /** @var View **/
+ private $view;
+
+ /** @var IGroupManager **/
+ private $groupManager;
+
+ /** @var Folder **/
+ private $userFolder;
+
+ public function setUp() {
+ parent::setUp();
+ $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->view = $this->getMockBuilder('\OC\Files\View')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->server = $this->getMockBuilder('\Sabre\DAV\Server')
+ ->setConstructorArgs([$this->tree])
+ ->setMethods(['getRequestUri'])
+ ->getMock();
+
+ $this->groupManager = $this->getMockBuilder('\OCP\IGroupManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->userFolder = $this->getMockBuilder('\OCP\Files\Folder')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+ $this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper');
+ $this->userSession = $this->getMock('\OCP\IUserSession');
+
+ $user = $this->getMock('\OCP\IUser');
+ $user->expects($this->any())
+ ->method('getUID')
+ ->will($this->returnValue('testuser'));
+ $this->userSession->expects($this->any())
+ ->method('getUser')
+ ->will($this->returnValue($user));
+
+ $this->plugin = new FilesReportPluginImplementation(
+ $this->tree,
+ $this->view,
+ $this->tagManager,
+ $this->tagMapper,
+ $this->userSession,
+ $this->groupManager,
+ $this->userFolder
+ );
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\ReportNotSupported
+ */
+ public function testOnReportInvalidNode() {
+ $path = 'totally/unrelated/13';
+
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->with('/' . $path)
+ ->will($this->returnValue($this->getMock('\Sabre\DAV\INode')));
+
+ $this->server->expects($this->any())
+ ->method('getRequestUri')
+ ->will($this->returnValue($path));
+ $this->plugin->initialize($this->server);
+
+ $this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, [], '/' . $path);
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\ReportNotSupported
+ */
+ public function testOnReportInvalidReportName() {
+ $path = 'test';
+
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->with('/' . $path)
+ ->will($this->returnValue($this->getMock('\Sabre\DAV\INode')));
+
+ $this->server->expects($this->any())
+ ->method('getRequestUri')
+ ->will($this->returnValue($path));
+ $this->plugin->initialize($this->server);
+
+ $this->plugin->onReport('{whoever}whatever', [], '/' . $path);
+ }
+
+ public function testOnReport() {
+ $path = 'test';
+
+ $parameters = [
+ [
+ 'name' => '{DAV:}prop',
+ 'value' => [
+ ['name' => '{DAV:}getcontentlength', 'value' => ''],
+ ['name' => '{http://owncloud.org/ns}size', 'value' => ''],
+ ],
+ ],
+ [
+ 'name' => '{http://owncloud.org/ns}filter-rules',
+ 'value' => [
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+ ],
+ ],
+ ];
+
+ $this->groupManager->expects($this->any())
+ ->method('isAdmin')
+ ->will($this->returnValue(true));
+
+ $this->tagMapper->expects($this->at(0))
+ ->method('getObjectIdsForTags')
+ ->with('123', 'files')
+ ->will($this->returnValue(['111', '222']));
+ $this->tagMapper->expects($this->at(1))
+ ->method('getObjectIdsForTags')
+ ->with('456', 'files')
+ ->will($this->returnValue(['111', '222', '333']));
+
+ $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $response->expects($this->once())
+ ->method('setHeader')
+ ->with('Content-Type', 'application/xml; charset=utf-8');
+
+ $response->expects($this->once())
+ ->method('setStatus')
+ ->with(207);
+
+ $response->expects($this->once())
+ ->method('setBody');
+
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->with('/' . $path)
+ ->will($this->returnValue($reportTargetNode));
+
+ $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $filesNode2 = $this->getMockBuilder('\OCP\Files\File')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->userFolder->expects($this->at(0))
+ ->method('getById')
+ ->with('111')
+ ->will($this->returnValue([$filesNode1]));
+ $this->userFolder->expects($this->at(1))
+ ->method('getById')
+ ->with('222')
+ ->will($this->returnValue([$filesNode2]));
+
+ $this->server->expects($this->any())
+ ->method('getRequestUri')
+ ->will($this->returnValue($path));
+ $this->server->httpResponse = $response;
+ $this->plugin->initialize($this->server);
+
+ $this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path);
+ }
+
+ public function testFindNodesByFileIdsRoot() {
+ $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $filesNode1->expects($this->once())
+ ->method('getName')
+ ->will($this->returnValue('first node'));
+
+ $filesNode2 = $this->getMockBuilder('\OCP\Files\File')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $filesNode2->expects($this->once())
+ ->method('getName')
+ ->will($this->returnValue('second node'));
+
+ $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $reportTargetNode->expects($this->any())
+ ->method('getPath')
+ ->will($this->returnValue('/'));
+
+ $this->userFolder->expects($this->at(0))
+ ->method('getById')
+ ->with('111')
+ ->will($this->returnValue([$filesNode1]));
+ $this->userFolder->expects($this->at(1))
+ ->method('getById')
+ ->with('222')
+ ->will($this->returnValue([$filesNode2]));
+
+ $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
+
+ $this->assertCount(2, $result);
+ $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]);
+ $this->assertEquals('first node', $result[0]->getName());
+ $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]);
+ $this->assertEquals('second node', $result[1]->getName());
+ }
+
+ public function testFindNodesByFileIdsSubDir() {
+ $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $filesNode1->expects($this->once())
+ ->method('getName')
+ ->will($this->returnValue('first node'));
+
+ $filesNode2 = $this->getMockBuilder('\OCP\Files\File')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $filesNode2->expects($this->once())
+ ->method('getName')
+ ->will($this->returnValue('second node'));
+
+ $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $reportTargetNode->expects($this->any())
+ ->method('getPath')
+ ->will($this->returnValue('/sub1/sub2'));
+
+
+ $subNode = $this->getMockBuilder('\OCP\Files\Folder')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->userFolder->expects($this->at(0))
+ ->method('get')
+ ->with('/sub1/sub2')
+ ->will($this->returnValue($subNode));
+
+ $subNode->expects($this->at(0))
+ ->method('getById')
+ ->with('111')
+ ->will($this->returnValue([$filesNode1]));
+ $subNode->expects($this->at(1))
+ ->method('getById')
+ ->with('222')
+ ->will($this->returnValue([$filesNode2]));
+
+ $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
+
+ $this->assertCount(2, $result);
+ $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]);
+ $this->assertEquals('first node', $result[0]->getName());
+ $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]);
+ $this->assertEquals('second node', $result[1]->getName());
+ }
+
+ public function testPrepareResponses() {
+ $requestedProps = ['{DAV:}getcontentlength', '{http://owncloud.org/ns}fileid', '{DAV:}resourcetype'];
+
+ $node1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $node2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $node1->expects($this->once())
+ ->method('getInternalFileId')
+ ->will($this->returnValue('111'));
+ $node2->expects($this->once())
+ ->method('getInternalFileId')
+ ->will($this->returnValue('222'));
+ $node2->expects($this->once())
+ ->method('getSize')
+ ->will($this->returnValue(1024));
+
+ $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view));
+ $this->plugin->initialize($this->server);
+ $responses = $this->plugin->prepareResponses($requestedProps, [$node1, $node2]);
+
+ $this->assertCount(2, $responses);
+
+ $this->assertEquals(200, $responses[0]->getHttpStatus());
+ $this->assertEquals(200, $responses[1]->getHttpStatus());
+
+ $props1 = $responses[0]->getResponseProperties();
+ $this->assertEquals('111', $props1[200]['{http://owncloud.org/ns}fileid']);
+ $this->assertNull($props1[404]['{DAV:}getcontentlength']);
+ $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props1[200]['{DAV:}resourcetype']);
+ $resourceType1 = $props1[200]['{DAV:}resourcetype']->getValue();
+ $this->assertEquals('{DAV:}collection', $resourceType1[0]);
+
+ $props2 = $responses[1]->getResponseProperties();
+ $this->assertEquals('1024', $props2[200]['{DAV:}getcontentlength']);
+ $this->assertEquals('222', $props2[200]['{http://owncloud.org/ns}fileid']);
+ $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props2[200]['{DAV:}resourcetype']);
+ $this->assertCount(0, $props2[200]['{DAV:}resourcetype']->getValue());
+ }
+
+ public function testProcessFilterRulesSingle() {
+ $this->groupManager->expects($this->any())
+ ->method('isAdmin')
+ ->will($this->returnValue(true));
+
+ $this->tagMapper->expects($this->once())
+ ->method('getObjectIdsForTags')
+ ->with('123')
+ ->will($this->returnValue(['111', '222']));
+
+ $rules = [
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+ ];
+
+ $this->assertEquals(['111', '222'], $this->plugin->processFilterRules($rules));
+ }
+
+ public function testProcessFilterRulesAndCondition() {
+ $this->groupManager->expects($this->any())
+ ->method('isAdmin')
+ ->will($this->returnValue(true));
+
+ $this->tagMapper->expects($this->at(0))
+ ->method('getObjectIdsForTags')
+ ->with('123')
+ ->will($this->returnValue(['111', '222']));
+ $this->tagMapper->expects($this->at(1))
+ ->method('getObjectIdsForTags')
+ ->with('456')
+ ->will($this->returnValue(['222', '333']));
+
+ $rules = [
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+ ];
+
+ $this->assertEquals(['222'], array_values($this->plugin->processFilterRules($rules)));
+ }
+
+ public function testProcessFilterRulesInvisibleTagAsAdmin() {
+ $this->groupManager->expects($this->any())
+ ->method('isAdmin')
+ ->will($this->returnValue(true));
+
+ $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag');
+ $tag1->expects($this->any())
+ ->method('getId')
+ ->will($this->returnValue('123'));
+ $tag1->expects($this->any())
+ ->method('isUserVisible')
+ ->will($this->returnValue(true));
+
+ $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag');
+ $tag2->expects($this->any())
+ ->method('getId')
+ ->will($this->returnValue('123'));
+ $tag2->expects($this->any())
+ ->method('isUserVisible')
+ ->will($this->returnValue(false));
+
+ // no need to fetch tags to check permissions
+ $this->tagManager->expects($this->never())
+ ->method('getTagsByIds');
+
+ $this->tagMapper->expects($this->at(0))
+ ->method('getObjectIdsForTags')
+ ->with('123')
+ ->will($this->returnValue(['111', '222']));
+ $this->tagMapper->expects($this->at(1))
+ ->method('getObjectIdsForTags')
+ ->with('456')
+ ->will($this->returnValue(['222', '333']));
+
+ $rules = [
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+ ];
+
+ $this->assertEquals(['222'], array_values($this->plugin->processFilterRules($rules)));
+ }
+
+ /**
+ * @expectedException \OCP\SystemTag\TagNotFoundException
+ */
+ public function testProcessFilterRulesInvisibleTagAsUser() {
+ $this->groupManager->expects($this->any())
+ ->method('isAdmin')
+ ->will($this->returnValue(false));
+
+ $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag');
+ $tag1->expects($this->any())
+ ->method('getId')
+ ->will($this->returnValue('123'));
+ $tag1->expects($this->any())
+ ->method('isUserVisible')
+ ->will($this->returnValue(true));
+
+ $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag');
+ $tag2->expects($this->any())
+ ->method('getId')
+ ->will($this->returnValue('123'));
+ $tag2->expects($this->any())
+ ->method('isUserVisible')
+ ->will($this->returnValue(false)); // invisible
+
+ $this->tagManager->expects($this->once())
+ ->method('getTagsByIds')
+ ->with(['123', '456'])
+ ->will($this->returnValue([$tag1, $tag2]));
+
+ $rules = [
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+ ];
+
+ $this->plugin->processFilterRules($rules);
+ }
+
+ public function testProcessFilterRulesVisibleTagAsUser() {
+ $this->groupManager->expects($this->any())
+ ->method('isAdmin')
+ ->will($this->returnValue(false));
+
+ $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag');
+ $tag1->expects($this->any())
+ ->method('getId')
+ ->will($this->returnValue('123'));
+ $tag1->expects($this->any())
+ ->method('isUserVisible')
+ ->will($this->returnValue(true));
+
+ $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag');
+ $tag2->expects($this->any())
+ ->method('getId')
+ ->will($this->returnValue('123'));
+ $tag2->expects($this->any())
+ ->method('isUserVisible')
+ ->will($this->returnValue(true));
+
+ $this->tagManager->expects($this->once())
+ ->method('getTagsByIds')
+ ->with(['123', '456'])
+ ->will($this->returnValue([$tag1, $tag2]));
+
+ $this->tagMapper->expects($this->at(0))
+ ->method('getObjectIdsForTags')
+ ->with('123')
+ ->will($this->returnValue(['111', '222']));
+ $this->tagMapper->expects($this->at(1))
+ ->method('getObjectIdsForTags')
+ ->with('456')
+ ->will($this->returnValue(['222', '333']));
+
+ $rules = [
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+ ];
+
+ $this->assertEquals(['222'], array_values($this->plugin->processFilterRules($rules)));
+ }
+}
diff --git a/apps/dav/tests/unit/connector/sabre/principal.php b/apps/dav/tests/unit/connector/sabre/principal.php
index d6bc7cd405f..07bfd5d263b 100644
--- a/apps/dav/tests/unit/connector/sabre/principal.php
+++ b/apps/dav/tests/unit/connector/sabre/principal.php
@@ -211,10 +211,7 @@ class Principal extends TestCase {
->method('getUserGroups')
->willReturn([]);
- $expectedResponse = [
- 'principals/users/foo/calendar-proxy-read',
- 'principals/users/foo/calendar-proxy-write'
- ];
+ $expectedResponse = [];
$response = $this->connector->getGroupMembership('principals/users/foo');
$this->assertSame($expectedResponse, $response);
}
diff --git a/apps/federatedfilesharing/appinfo/app.php b/apps/federatedfilesharing/appinfo/app.php
new file mode 100644
index 00000000000..804ab69759c
--- /dev/null
+++ b/apps/federatedfilesharing/appinfo/app.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\FederatedFileSharing\AppInfo;
+
+use OCP\AppFramework\App;
+
+new App('federatedfilesharing');
+
diff --git a/apps/federatedfilesharing/appinfo/info.xml b/apps/federatedfilesharing/appinfo/info.xml
new file mode 100644
index 00000000000..d88ea2640e1
--- /dev/null
+++ b/apps/federatedfilesharing/appinfo/info.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<info>
+ <id>federatedfilesharing</id>
+ <name>Federated File Sharing</name>
+ <description>Provide federated file sharing across ownCloud servers</description>
+ <licence>AGPL</licence>
+ <author>Bjoern Schiessle, Roeland Jago Douma</author>
+ <version>0.1.0</version>
+ <namespace>FederatedFileSharing</namespace>
+ <category>other</category>
+ <dependencies>
+ <owncloud min-version="9.0" max-version="9.0" />
+ </dependencies>
+</info>
diff --git a/apps/federatedfilesharing/lib/addresshandler.php b/apps/federatedfilesharing/lib/addresshandler.php
new file mode 100644
index 00000000000..92768f11b95
--- /dev/null
+++ b/apps/federatedfilesharing/lib/addresshandler.php
@@ -0,0 +1,184 @@
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\FederatedFileSharing;
+use OC\HintException;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+
+/**
+ * Class AddressHandler - parse, modify and construct federated sharing addresses
+ *
+ * @package OCA\FederatedFileSharing
+ */
+class AddressHandler {
+
+ /** @var IL10N */
+ private $l;
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /**
+ * AddressHandler constructor.
+ *
+ * @param IURLGenerator $urlGenerator
+ * @param IL10N $il10n
+ */
+ public function __construct(
+ IURLGenerator $urlGenerator,
+ IL10N $il10n
+ ) {
+ $this->l = $il10n;
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ /**
+ * split user and remote from federated cloud id
+ *
+ * @param string $address federated share address
+ * @return array [user, remoteURL]
+ * @throws HintException
+ */
+ public function splitUserRemote($address) {
+ if (strpos($address, '@') === false) {
+ $hint = $this->l->t('Invalid Federated Cloud ID');
+ throw new HintException('Invalid Federated Cloud ID', $hint);
+ }
+
+ // Find the first character that is not allowed in user names
+ $id = str_replace('\\', '/', $address);
+ $posSlash = strpos($id, '/');
+ $posColon = strpos($id, ':');
+
+ if ($posSlash === false && $posColon === false) {
+ $invalidPos = strlen($id);
+ } else if ($posSlash === false) {
+ $invalidPos = $posColon;
+ } else if ($posColon === false) {
+ $invalidPos = $posSlash;
+ } else {
+ $invalidPos = min($posSlash, $posColon);
+ }
+
+ // Find the last @ before $invalidPos
+ $pos = $lastAtPos = 0;
+ while ($lastAtPos !== false && $lastAtPos <= $invalidPos) {
+ $pos = $lastAtPos;
+ $lastAtPos = strpos($id, '@', $pos + 1);
+ }
+
+ if ($pos !== false) {
+ $user = substr($id, 0, $pos);
+ $remote = substr($id, $pos + 1);
+ $remote = $this->fixRemoteURL($remote);
+ if (!empty($user) && !empty($remote)) {
+ return array($user, $remote);
+ }
+ }
+
+ $hint = $this->l->t('Invalid Federated Cloud ID');
+ throw new HintException('Invalid Federated Cloud ID', $hint);
+ }
+
+ /**
+ * generate remote URL part of federated ID
+ *
+ * @return string url of the current server
+ */
+ public function generateRemoteURL() {
+ $url = $this->urlGenerator->getAbsoluteURL('/');
+ return $url;
+ }
+
+ /**
+ * check if two federated cloud IDs refer to the same user
+ *
+ * @param string $user1
+ * @param string $server1
+ * @param string $user2
+ * @param string $server2
+ * @return bool true if both users and servers are the same
+ */
+ public function compareAddresses($user1, $server1, $user2, $server2) {
+ $normalizedServer1 = strtolower($this->removeProtocolFromUrl($server1));
+ $normalizedServer2 = strtolower($this->removeProtocolFromUrl($server2));
+
+ if (rtrim($normalizedServer1, '/') === rtrim($normalizedServer2, '/')) {
+ // FIXME this should be a method in the user management instead
+ \OCP\Util::emitHook(
+ '\OCA\Files_Sharing\API\Server2Server',
+ 'preLoginNameUsedAsUserName',
+ array('uid' => &$user1)
+ );
+ \OCP\Util::emitHook(
+ '\OCA\Files_Sharing\API\Server2Server',
+ 'preLoginNameUsedAsUserName',
+ array('uid' => &$user2)
+ );
+
+ if ($user1 === $user2) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * remove protocol from URL
+ *
+ * @param string $url
+ * @return string
+ */
+ public function removeProtocolFromUrl($url) {
+ if (strpos($url, 'https://') === 0) {
+ return substr($url, strlen('https://'));
+ } else if (strpos($url, 'http://') === 0) {
+ return substr($url, strlen('http://'));
+ }
+
+ return $url;
+ }
+
+ /**
+ * Strips away a potential file names and trailing slashes:
+ * - http://localhost
+ * - http://localhost/
+ * - http://localhost/index.php
+ * - http://localhost/index.php/s/{shareToken}
+ *
+ * all return: http://localhost
+ *
+ * @param string $remote
+ * @return string
+ */
+ protected function fixRemoteURL($remote) {
+ $remote = str_replace('\\', '/', $remote);
+ if ($fileNamePosition = strpos($remote, '/index.php')) {
+ $remote = substr($remote, 0, $fileNamePosition);
+ }
+ $remote = rtrim($remote, '/');
+
+ return $remote;
+ }
+
+}
diff --git a/apps/federatedfilesharing/lib/federatedshareprovider.php b/apps/federatedfilesharing/lib/federatedshareprovider.php
new file mode 100644
index 00000000000..0825a0e69bc
--- /dev/null
+++ b/apps/federatedfilesharing/lib/federatedshareprovider.php
@@ -0,0 +1,562 @@
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\FederatedFileSharing;
+
+use OC\Share20\Share;
+use OCP\Files\IRootFolder;
+use OCP\IL10N;
+use OCP\ILogger;
+use OCP\Share\IShare;
+use OCP\Share\IShareProvider;
+use OC\Share20\Exception\InvalidShare;
+use OCP\Share\Exceptions\ShareNotFound;
+use OCP\Files\NotFoundException;
+use OCP\IDBConnection;
+use OCP\Files\Node;
+
+/**
+ * Class FederatedShareProvider
+ *
+ * @package OCA\FederatedFileSharing
+ */
+class FederatedShareProvider implements IShareProvider {
+
+ const SHARE_TYPE_REMOTE = 6;
+
+ /** @var IDBConnection */
+ private $dbConnection;
+
+ /** @var AddressHandler */
+ private $addressHandler;
+
+ /** @var Notifications */
+ private $notifications;
+
+ /** @var TokenHandler */
+ private $tokenHandler;
+
+ /** @var IL10N */
+ private $l;
+
+ /** @var ILogger */
+ private $logger;
+
+ /** @var IRootFolder */
+ private $rootFolder;
+
+ /**
+ * DefaultShareProvider constructor.
+ *
+ * @param IDBConnection $connection
+ * @param AddressHandler $addressHandler
+ * @param Notifications $notifications
+ * @param TokenHandler $tokenHandler
+ * @param IL10N $l10n
+ * @param ILogger $logger
+ * @param IRootFolder $rootFolder
+ */
+ public function __construct(
+ IDBConnection $connection,
+ AddressHandler $addressHandler,
+ Notifications $notifications,
+ TokenHandler $tokenHandler,
+ IL10N $l10n,
+ ILogger $logger,
+ IRootFolder $rootFolder
+ ) {
+ $this->dbConnection = $connection;
+ $this->addressHandler = $addressHandler;
+ $this->notifications = $notifications;
+ $this->tokenHandler = $tokenHandler;
+ $this->l = $l10n;
+ $this->logger = $logger;
+ $this->rootFolder = $rootFolder;
+ }
+
+ /**
+ * Return the identifier of this provider.
+ *
+ * @return string Containing only [a-zA-Z0-9]
+ */
+ public function identifier() {
+ return 'ocFederatedSharing';
+ }
+
+ /**
+ * Share a path
+ *
+ * @param IShare $share
+ * @return IShare The share object
+ * @throws ShareNotFound
+ * @throws \Exception
+ */
+ public function create(IShare $share) {
+
+ $shareWith = $share->getSharedWith();
+ $itemSource = $share->getNodeId();
+ $itemType = $share->getNodeType();
+ $uidOwner = $share->getShareOwner();
+ $permissions = $share->getPermissions();
+ $sharedBy = $share->getSharedBy();
+
+ /*
+ * Check if file is not already shared with the remote user
+ */
+ $alreadyShared = $this->getSharedWith($shareWith, self::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
+ if (!empty($alreadyShared)) {
+ $message = 'Sharing %s failed, because this item is already shared with %s';
+ $message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', array($share->getNode()->getName(), $shareWith));
+ $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
+ throw new \Exception($message_t);
+ }
+
+
+ // don't allow federated shares if source and target server are the same
+ list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
+ $currentServer = $this->addressHandler->generateRemoteURL();
+ $currentUser = $sharedBy;
+ if ($this->addressHandler->compareAddresses($user, $remote, $currentUser, $currentServer)) {
+ $message = 'Not allowed to create a federated share with the same user.';
+ $message_t = $this->l->t('Not allowed to create a federated share with the same user');
+ $this->logger->debug($message, ['app' => 'Federated File Sharing']);
+ throw new \Exception($message_t);
+ }
+
+ $token = $this->tokenHandler->generateToken();
+
+ $shareWith = $user . '@' . $remote;
+
+ $shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
+
+ $send = $this->notifications->sendRemoteShare(
+ $token,
+ $shareWith,
+ $share->getNode()->getName(),
+ $shareId,
+ $share->getSharedBy()
+ );
+
+ $data = $this->getRawShare($shareId);
+ $share = $this->createShare($data);
+
+ if ($send === false) {
+ $this->delete($share);
+ $message_t = $this->l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.',
+ [$share->getNode()->getName(), $shareWith]);
+ throw new \Exception($message_t);
+ }
+
+ return $share;
+ }
+
+ /**
+ * add share to the database and return the ID
+ *
+ * @param int $itemSource
+ * @param string $itemType
+ * @param string $shareWith
+ * @param string $sharedBy
+ * @param string $uidOwner
+ * @param int $permissions
+ * @param string $token
+ * @return int
+ */
+ private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token) {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert('share')
+ ->setValue('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE))
+ ->setValue('item_type', $qb->createNamedParameter($itemType))
+ ->setValue('item_source', $qb->createNamedParameter($itemSource))
+ ->setValue('file_source', $qb->createNamedParameter($itemSource))
+ ->setValue('share_with', $qb->createNamedParameter($shareWith))
+ ->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
+ ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
+ ->setValue('permissions', $qb->createNamedParameter($permissions))
+ ->setValue('token', $qb->createNamedParameter($token))
+ ->setValue('stime', $qb->createNamedParameter(time()));
+
+ /*
+ * Added to fix https://github.com/owncloud/core/issues/22215
+ * Can be removed once we get rid of ajax/share.php
+ */
+ $qb->setValue('file_target', $qb->createNamedParameter(''));
+
+ $qb->execute();
+ $id = $qb->getLastInsertId();
+
+ return (int)$id;
+ }
+
+ /**
+ * Update a share
+ *
+ * @param IShare $share
+ * @return IShare The share object
+ */
+ public function update(IShare $share) {
+ /*
+ * We allow updating the permissions of federated shares
+ */
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->update('share')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
+ ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
+ ->execute();
+
+ return $share;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function move(IShare $share, $recipient) {
+ /*
+ * This function does nothing yet as it is just for outgoing
+ * federated shares.
+ */
+ return $share;
+ }
+
+ /**
+ * Get all children of this share
+ *
+ * @param IShare $parent
+ * @return IShare[]
+ */
+ public function getChildren(IShare $parent) {
+ $children = [];
+
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('*')
+ ->from('share')
+ ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
+ ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
+ ->orderBy('id');
+
+ $cursor = $qb->execute();
+ while($data = $cursor->fetch()) {
+ $children[] = $this->createShare($data);
+ }
+ $cursor->closeCursor();
+
+ return $children;
+ }
+
+ /**
+ * Delete a share
+ *
+ * @param IShare $share
+ */
+ public function delete(IShare $share) {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->delete('share')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())));
+ $qb->execute();
+
+ list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
+ $this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteFromSelf(IShare $share, $recipient) {
+ // nothing to do here. Technically deleteFromSelf in the context of federated
+ // shares is a umount of a external storage. This is handled here
+ // apps/files_sharing/lib/external/manager.php
+ // TODO move this code over to this app
+ return;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('*')
+ ->from('share');
+
+ $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
+
+ /**
+ * Reshares for this user are shares where they are the owner.
+ */
+ if ($reshares === false) {
+ //Special case for old shares created via the web UI
+ $or1 = $qb->expr()->andX(
+ $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
+ $qb->expr()->isNull('uid_initiator')
+ );
+
+ $qb->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
+ $or1
+ )
+ );
+ } else {
+ $qb->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
+ $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
+ )
+ );
+ }
+
+ if ($node !== null) {
+ $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
+ }
+
+ if ($limit !== -1) {
+ $qb->setMaxResults($limit);
+ }
+
+ $qb->setFirstResult($offset);
+ $qb->orderBy('id');
+
+ $cursor = $qb->execute();
+ $shares = [];
+ while($data = $cursor->fetch()) {
+ $shares[] = $this->createShare($data);
+ }
+ $cursor->closeCursor();
+
+ return $shares;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getShareById($id, $recipientId = null) {
+ $qb = $this->dbConnection->getQueryBuilder();
+
+ $qb->select('*')
+ ->from('share')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
+ ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
+
+ $cursor = $qb->execute();
+ $data = $cursor->fetch();
+ $cursor->closeCursor();
+
+ if ($data === false) {
+ throw new ShareNotFound();
+ }
+
+ try {
+ $share = $this->createShare($data);
+ } catch (InvalidShare $e) {
+ throw new ShareNotFound();
+ }
+
+ return $share;
+ }
+
+ /**
+ * Get shares for a given path
+ *
+ * @param \OCP\Files\Node $path
+ * @return IShare[]
+ */
+ public function getSharesByPath(Node $path) {
+ $qb = $this->dbConnection->getQueryBuilder();
+
+ $cursor = $qb->select('*')
+ ->from('share')
+ ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
+ ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
+ ->execute();
+
+ $shares = [];
+ while($data = $cursor->fetch()) {
+ $shares[] = $this->createShare($data);
+ }
+ $cursor->closeCursor();
+
+ return $shares;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
+ /** @var IShare[] $shares */
+ $shares = [];
+
+ //Get shares directly with this user
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('*')
+ ->from('share');
+
+ // Order by id
+ $qb->orderBy('id');
+
+ // Set limit and offset
+ if ($limit !== -1) {
+ $qb->setMaxResults($limit);
+ }
+ $qb->setFirstResult($offset);
+
+ $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
+ $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
+
+ // Filter by node if provided
+ if ($node !== null) {
+ $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
+ }
+
+ $cursor = $qb->execute();
+
+ while($data = $cursor->fetch()) {
+ $shares[] = $this->createShare($data);
+ }
+ $cursor->closeCursor();
+
+
+ return $shares;
+ }
+
+ /**
+ * Get a share by token
+ *
+ * @param string $token
+ * @return IShare
+ * @throws ShareNotFound
+ */
+ public function getShareByToken($token) {
+ $qb = $this->dbConnection->getQueryBuilder();
+
+ $cursor = $qb->select('*')
+ ->from('share')
+ ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
+ ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
+ ->execute();
+
+ $data = $cursor->fetch();
+
+ if ($data === false) {
+ throw new ShareNotFound();
+ }
+
+ try {
+ $share = $this->createShare($data);
+ } catch (InvalidShare $e) {
+ throw new ShareNotFound();
+ }
+
+ return $share;
+ }
+
+ /**
+ * get database row of a give share
+ *
+ * @param $id
+ * @return array
+ * @throws ShareNotFound
+ */
+ private function getRawShare($id) {
+
+ // Now fetch the inserted share and create a complete share object
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('*')
+ ->from('share')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
+
+ $cursor = $qb->execute();
+ $data = $cursor->fetch();
+ $cursor->closeCursor();
+
+ if ($data === false) {
+ throw new ShareNotFound;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Create a share object from an database row
+ *
+ * @param array $data
+ * @return IShare
+ * @throws InvalidShare
+ * @throws ShareNotFound
+ */
+ private function createShare($data) {
+
+ $share = new Share($this->rootFolder);
+ $share->setId((int)$data['id'])
+ ->setShareType((int)$data['share_type'])
+ ->setPermissions((int)$data['permissions'])
+ ->setTarget($data['file_target'])
+ ->setMailSend((bool)$data['mail_send'])
+ ->setToken($data['token']);
+
+ $shareTime = new \DateTime();
+ $shareTime->setTimestamp((int)$data['stime']);
+ $share->setShareTime($shareTime);
+ $share->setSharedWith($data['share_with']);
+
+ if ($data['uid_initiator'] !== null) {
+ $share->setShareOwner($data['uid_owner']);
+ $share->setSharedBy($data['uid_initiator']);
+ } else {
+ //OLD SHARE
+ $share->setSharedBy($data['uid_owner']);
+ $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
+
+ $owner = $path->getOwner();
+ $share->setShareOwner($owner->getUID());
+ }
+
+ $share->setNodeId((int)$data['file_source']);
+ $share->setNodeType($data['item_type']);
+
+ $share->setProviderId($this->identifier());
+
+ return $share;
+ }
+
+ /**
+ * Get the node with file $id for $user
+ *
+ * @param string $userId
+ * @param int $id
+ * @return \OCP\Files\File|\OCP\Files\Folder
+ * @throws InvalidShare
+ */
+ private function getNode($userId, $id) {
+ try {
+ $userFolder = $this->rootFolder->getUserFolder($userId);
+ } catch (NotFoundException $e) {
+ throw new InvalidShare();
+ }
+
+ $nodes = $userFolder->getById($id);
+
+ if (empty($nodes)) {
+ throw new InvalidShare();
+ }
+
+ return $nodes[0];
+ }
+
+}
diff --git a/apps/federatedfilesharing/lib/notifications.php b/apps/federatedfilesharing/lib/notifications.php
new file mode 100644
index 00000000000..d778ac87828
--- /dev/null
+++ b/apps/federatedfilesharing/lib/notifications.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\FederatedFileSharing;
+
+
+use OCP\Http\Client\IClientService;
+
+class Notifications {
+
+ const BASE_PATH_TO_SHARE_API = '/ocs/v1.php/cloud/shares';
+ const RESPONSE_FORMAT = 'json'; // default response format for ocs calls
+
+ /** @var AddressHandler */
+ private $addressHandler;
+
+ /** @var IClientService */
+ private $httpClientService;
+
+ /**
+ * Notifications constructor.
+ *
+ * @param AddressHandler $addressHandler
+ * @param IClientService $httpClientService
+ */
+ public function __construct(
+ AddressHandler $addressHandler,
+ IClientService $httpClientService
+ ) {
+ $this->addressHandler = $addressHandler;
+ $this->httpClientService = $httpClientService;
+ }
+
+ /**
+ * send server-to-server share to remote server
+ *
+ * @param string $token
+ * @param string $shareWith
+ * @param string $name
+ * @param int $remote_id
+ * @param string $owner
+ * @return bool
+ */
+ public function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) {
+
+ list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
+
+ if ($user && $remote) {
+ $url = $remote . self::BASE_PATH_TO_SHARE_API . '?format=' . self::RESPONSE_FORMAT;
+ $local = $this->addressHandler->generateRemoteURL();
+
+ $fields = array(
+ 'shareWith' => $user,
+ 'token' => $token,
+ 'name' => $name,
+ 'remoteId' => $remote_id,
+ 'owner' => $owner,
+ 'remote' => $local,
+ );
+
+ $url = $this->addressHandler->removeProtocolFromUrl($url);
+ $result = $this->tryHttpPost($url, $fields);
+ $status = json_decode($result['result'], true);
+
+ if ($result['success'] && $status['ocs']['meta']['statuscode'] === 100) {
+ \OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $remote]);
+ return true;
+ }
+
+ }
+
+ return false;
+ }
+
+ /**
+ * send server-to-server unshare to remote server
+ *
+ * @param string $remote url
+ * @param int $id share id
+ * @param string $token
+ * @return bool
+ */
+ public function sendRemoteUnShare($remote, $id, $token) {
+ $url = rtrim($remote, '/') . self::BASE_PATH_TO_SHARE_API . '/' . $id . '/unshare?format=' . self::RESPONSE_FORMAT;
+ $fields = array('token' => $token, 'format' => 'json');
+ $url = $this->addressHandler->removeProtocolFromUrl($url);
+ $result = $this->tryHttpPost($url, $fields);
+ $status = json_decode($result['result'], true);
+
+ return ($result['success'] && $status['ocs']['meta']['statuscode'] === 100);
+ }
+
+ /**
+ * try http post first with https and then with http as a fallback
+ *
+ * @param string $url
+ * @param array $fields post parameters
+ * @return array
+ */
+ private function tryHttpPost($url, array $fields) {
+ $client = $this->httpClientService->newClient();
+ $protocol = 'https://';
+ $result = [
+ 'success' => false,
+ 'result' => '',
+ ];
+ $try = 0;
+ while ($result['success'] === false && $try < 2) {
+ try {
+ $response = $client->post($protocol . $url, [
+ 'body' => $fields
+ ]);
+ $result['result'] = $response->getBody();
+ $result['success'] = true;
+ break;
+ } catch (\Exception $e) {
+ $try++;
+ $protocol = 'http://';
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/apps/federatedfilesharing/lib/tokenhandler.php b/apps/federatedfilesharing/lib/tokenhandler.php
new file mode 100644
index 00000000000..ec5f73127d6
--- /dev/null
+++ b/apps/federatedfilesharing/lib/tokenhandler.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\FederatedFileSharing;
+
+
+use OCP\Security\ISecureRandom;
+
+/**
+ * Class TokenHandler
+ *
+ * @package OCA\FederatedFileSharing
+ */
+class TokenHandler {
+
+ const TOKEN_LENGTH = 15;
+
+ /** @var ISecureRandom */
+ private $secureRandom;
+
+ /**
+ * TokenHandler constructor.
+ *
+ * @param ISecureRandom $secureRandom
+ */
+ public function __construct(ISecureRandom $secureRandom) {
+ $this->secureRandom = $secureRandom;
+ }
+
+ /**
+ * generate to token used to authenticate federated shares
+ *
+ * @return string
+ */
+ public function generateToken() {
+ $token = $this->secureRandom->generate(
+ self::TOKEN_LENGTH,
+ ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS);
+ return $token;
+ }
+
+}
diff --git a/apps/federatedfilesharing/tests/addresshandlertest.php b/apps/federatedfilesharing/tests/addresshandlertest.php
new file mode 100644
index 00000000000..b1c23dc75bf
--- /dev/null
+++ b/apps/federatedfilesharing/tests/addresshandlertest.php
@@ -0,0 +1,198 @@
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\FederatedFileSharing\Tests;
+
+
+use OCA\FederatedFileSharing\AddressHandler;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use Test\TestCase;
+
+class AddressHandlerTest extends TestCase {
+
+ /** @var AddressHandler */
+ private $addressHandler;
+
+ /** @var IURLGenerator | \PHPUnit_Framework_MockObject_MockObject */
+ private $urlGenerator;
+
+ /** @var IL10N | \PHPUnit_Framework_MockObject_MockObject */
+ private $il10n;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->urlGenerator = $this->getMock('OCP\IURLGenerator');
+ $this->il10n = $this->getMock('OCP\IL10N');
+
+ $this->addressHandler = new AddressHandler($this->urlGenerator, $this->il10n);
+ }
+
+ public function dataTestSplitUserRemote() {
+ $userPrefix = ['user@name', 'username'];
+ $protocols = ['', 'http://', 'https://'];
+ $remotes = [
+ 'localhost',
+ 'local.host',
+ 'dev.local.host',
+ 'dev.local.host/path',
+ 'dev.local.host/at@inpath',
+ '127.0.0.1',
+ '::1',
+ '::192.0.2.128',
+ '::192.0.2.128/at@inpath',
+ ];
+
+ $testCases = [];
+ foreach ($userPrefix as $user) {
+ foreach ($remotes as $remote) {
+ foreach ($protocols as $protocol) {
+ $baseUrl = $user . '@' . $protocol . $remote;
+
+ $testCases[] = [$baseUrl, $user, $protocol . $remote];
+ $testCases[] = [$baseUrl . '/', $user, $protocol . $remote];
+ $testCases[] = [$baseUrl . '/index.php', $user, $protocol . $remote];
+ $testCases[] = [$baseUrl . '/index.php/s/token', $user, $protocol . $remote];
+ }
+ }
+ }
+ return $testCases;
+ }
+
+ /**
+ * @dataProvider dataTestSplitUserRemote
+ *
+ * @param string $remote
+ * @param string $expectedUser
+ * @param string $expectedUrl
+ */
+ public function testSplitUserRemote($remote, $expectedUser, $expectedUrl) {
+ list($remoteUser, $remoteUrl) = $this->addressHandler->splitUserRemote($remote);
+ $this->assertSame($expectedUser, $remoteUser);
+ $this->assertSame($expectedUrl, $remoteUrl);
+ }
+
+ public function dataTestSplitUserRemoteError() {
+ return array(
+ // Invalid path
+ array('user@'),
+
+ // Invalid user
+ array('@server'),
+ array('us/er@server'),
+ array('us:er@server'),
+
+ // Invalid splitting
+ array('user'),
+ array(''),
+ array('us/erserver'),
+ array('us:erserver'),
+ );
+ }
+
+ /**
+ * @dataProvider dataTestSplitUserRemoteError
+ *
+ * @param string $id
+ * @expectedException \OC\HintException
+ */
+ public function testSplitUserRemoteError($id) {
+ $this->addressHandler->splitUserRemote($id);
+ }
+
+ /**
+ * @dataProvider dataTestCompareAddresses
+ *
+ * @param string $user1
+ * @param string $server1
+ * @param string $user2
+ * @param string $server2
+ * @param bool $expected
+ */
+ public function testCompareAddresses($user1, $server1, $user2, $server2, $expected) {
+ $this->assertSame($expected,
+ $this->addressHandler->compareAddresses($user1, $server1, $user2, $server2)
+ );
+ }
+
+ public function dataTestCompareAddresses() {
+ return [
+ ['user1', 'http://server1', 'user1', 'http://server1', true],
+ ['user1', 'https://server1', 'user1', 'http://server1', true],
+ ['user1', 'http://serVer1', 'user1', 'http://server1', true],
+ ['user1', 'http://server1/', 'user1', 'http://server1', true],
+ ['user1', 'server1', 'user1', 'http://server1', true],
+ ['user1', 'http://server1', 'user1', 'http://server2', false],
+ ['user1', 'https://server1', 'user1', 'http://server2', false],
+ ['user1', 'http://serVer1', 'user1', 'http://serer2', false],
+ ['user1', 'http://server1/', 'user1', 'http://server2', false],
+ ['user1', 'server1', 'user1', 'http://server2', false],
+ ['user1', 'http://server1', 'user2', 'http://server1', false],
+ ['user1', 'https://server1', 'user2', 'http://server1', false],
+ ['user1', 'http://serVer1', 'user2', 'http://server1', false],
+ ['user1', 'http://server1/', 'user2', 'http://server1', false],
+ ['user1', 'server1', 'user2', 'http://server1', false],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestRemoveProtocolFromUrl
+ *
+ * @param string $url
+ * @param string $expectedResult
+ */
+ public function testRemoveProtocolFromUrl($url, $expectedResult) {
+ $result = $this->addressHandler->removeProtocolFromUrl($url);
+ $this->assertSame($expectedResult, $result);
+ }
+
+ public function dataTestRemoveProtocolFromUrl() {
+ return [
+ ['http://owncloud.org', 'owncloud.org'],
+ ['https://owncloud.org', 'owncloud.org'],
+ ['owncloud.org', 'owncloud.org'],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestFixRemoteUrl
+ *
+ * @param string $url
+ * @param string $expected
+ */
+ public function testFixRemoteUrl($url, $expected) {
+ $this->assertSame($expected,
+ $this->invokePrivate($this->addressHandler, 'fixRemoteURL', [$url])
+ );
+ }
+
+ public function dataTestFixRemoteUrl() {
+ return [
+ ['http://localhost', 'http://localhost'],
+ ['http://localhost/', 'http://localhost'],
+ ['http://localhost/index.php', 'http://localhost'],
+ ['http://localhost/index.php/s/AShareToken', 'http://localhost'],
+ ];
+ }
+
+}
diff --git a/apps/federatedfilesharing/tests/federatedshareprovidertest.php b/apps/federatedfilesharing/tests/federatedshareprovidertest.php
new file mode 100644
index 00000000000..a7ff02e7697
--- /dev/null
+++ b/apps/federatedfilesharing/tests/federatedshareprovidertest.php
@@ -0,0 +1,447 @@
+<?php
+
+namespace OCA\FederatedFileSharing\Tests;
+
+
+use OCA\FederatedFileSharing\AddressHandler;
+use OCA\FederatedFileSharing\FederatedShareProvider;
+use OCA\FederatedFileSharing\Notifications;
+use OCA\FederatedFileSharing\TokenHandler;
+use OCP\Files\IRootFolder;
+use OCP\IDBConnection;
+use OCP\IL10N;
+use OCP\ILogger;
+use OCP\Share\IManager;
+use Test\TestCase;
+
+/**
+ * Class FederatedShareProviderTest
+ *
+ * @package OCA\FederatedFileSharing\Tests
+ * @group DB
+ */
+class FederatedShareProviderTest extends TestCase {
+
+ /** @var IDBConnection */
+ protected $connection;
+ /** @var AddressHandler | \PHPUnit_Framework_MockObject_MockObject */
+ protected $addressHandler;
+ /** @var Notifications | \PHPUnit_Framework_MockObject_MockObject */
+ protected $notifications;
+ /** @var TokenHandler */
+ protected $tokenHandler;
+ /** @var IL10N */
+ protected $l;
+ /** @var ILogger */
+ protected $logger;
+ /** @var IRootFolder | \PHPUnit_Framework_MockObject_MockObject */
+ protected $rootFolder;
+
+ /** @var IManager */
+ protected $shareManager;
+ /** @var FederatedShareProvider */
+ protected $provider;
+
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->connection = \OC::$server->getDatabaseConnection();
+ $this->notifications = $this->getMockBuilder('OCA\FederatedFileSharing\Notifications')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->tokenHandler = $this->getMockBuilder('OCA\FederatedFileSharing\TokenHandler')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->l = $this->getMock('OCP\IL10N');
+ $this->l->method('t')
+ ->will($this->returnCallback(function($text, $parameters = []) {
+ return vsprintf($text, $parameters);
+ }));
+ $this->logger = $this->getMock('OCP\ILogger');
+ $this->rootFolder = $this->getMock('OCP\Files\IRootFolder');
+ $this->addressHandler = new AddressHandler(\OC::$server->getURLGenerator(), $this->l);
+
+ $this->provider = new FederatedShareProvider(
+ $this->connection,
+ $this->addressHandler,
+ $this->notifications,
+ $this->tokenHandler,
+ $this->l,
+ $this->logger,
+ $this->rootFolder
+ );
+
+ $this->shareManager = \OC::$server->getShareManager();
+ }
+
+ public function tearDown() {
+ $this->connection->getQueryBuilder()->delete('share')->execute();
+
+ return parent::tearDown();
+ }
+
+ public function testCreate() {
+ $share = $this->shareManager->newShare();
+
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $share->setSharedWith('user@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+
+ $this->tokenHandler->method('generateToken')->willReturn('token');
+
+ $this->notifications->expects($this->once())
+ ->method('sendRemoteShare')
+ ->with(
+ $this->equalTo('token'),
+ $this->equalTo('user@server.com'),
+ $this->equalTo('myFile'),
+ $this->anything(),
+ 'sharedBy'
+ )->willReturn(true);
+
+ $this->rootFolder->expects($this->never())->method($this->anything());
+
+ $share = $this->provider->create($share);
+
+ $qb = $this->connection->getQueryBuilder();
+ $stmt = $qb->select('*')
+ ->from('share')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
+ ->execute();
+
+ $data = $stmt->fetch();
+ $stmt->closeCursor();
+
+ $expected = [
+ 'share_type' => \OCP\Share::SHARE_TYPE_REMOTE,
+ 'share_with' => 'user@server.com',
+ 'uid_owner' => 'shareOwner',
+ 'uid_initiator' => 'sharedBy',
+ 'item_type' => 'file',
+ 'item_source' => 42,
+ 'file_source' => 42,
+ 'permissions' => 19,
+ 'accepted' => 0,
+ 'token' => 'token',
+ ];
+ $this->assertArraySubset($expected, $data);
+
+ $this->assertEquals($data['id'], $share->getId());
+ $this->assertEquals(\OCP\Share::SHARE_TYPE_REMOTE, $share->getShareType());
+ $this->assertEquals('user@server.com', $share->getSharedWith());
+ $this->assertEquals('sharedBy', $share->getSharedBy());
+ $this->assertEquals('shareOwner', $share->getShareOwner());
+ $this->assertEquals('file', $share->getNodeType());
+ $this->assertEquals(42, $share->getNodeId());
+ $this->assertEquals(19, $share->getPermissions());
+ $this->assertEquals('token', $share->getToken());
+ }
+
+ public function testCreateCouldNotFindServer() {
+ $share = $this->shareManager->newShare();
+
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $share->setSharedWith('user@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+
+ $this->tokenHandler->method('generateToken')->willReturn('token');
+
+ $this->notifications->expects($this->once())
+ ->method('sendRemoteShare')
+ ->with(
+ $this->equalTo('token'),
+ $this->equalTo('user@server.com'),
+ $this->equalTo('myFile'),
+ $this->anything(),
+ 'sharedBy'
+ )->willReturn(false);
+
+ $this->rootFolder->expects($this->once())
+ ->method('getUserFolder')
+ ->with('shareOwner')
+ ->will($this->returnSelf());
+ $this->rootFolder->method('getById')
+ ->with('42')
+ ->willReturn([$node]);
+
+ try {
+ $share = $this->provider->create($share);
+ $this->fail();
+ } catch (\Exception $e) {
+ $this->assertEquals('Sharing myFile failed, could not find user@server.com, maybe the server is currently unreachable.', $e->getMessage());
+ }
+
+ $qb = $this->connection->getQueryBuilder();
+ $stmt = $qb->select('*')
+ ->from('share')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
+ ->execute();
+
+ $data = $stmt->fetch();
+ $stmt->closeCursor();
+
+ $this->assertFalse($data);
+ }
+
+ public function testCreateShareWithSelf() {
+ $share = $this->shareManager->newShare();
+
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $shareWith = 'sharedBy@' . $this->addressHandler->generateRemoteURL();
+
+ $share->setSharedWith($shareWith)
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+
+ $this->rootFolder->expects($this->never())->method($this->anything());
+
+ try {
+ $share = $this->provider->create($share);
+ $this->fail();
+ } catch (\Exception $e) {
+ $this->assertEquals('Not allowed to create a federated share with the same user', $e->getMessage());
+ }
+
+ $qb = $this->connection->getQueryBuilder();
+ $stmt = $qb->select('*')
+ ->from('share')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
+ ->execute();
+
+ $data = $stmt->fetch();
+ $stmt->closeCursor();
+
+ $this->assertFalse($data);
+ }
+
+ public function testCreateAlreadyShared() {
+ $share = $this->shareManager->newShare();
+
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $share->setSharedWith('user@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+
+ $this->tokenHandler->method('generateToken')->willReturn('token');
+
+ $this->notifications->expects($this->once())
+ ->method('sendRemoteShare')
+ ->with(
+ $this->equalTo('token'),
+ $this->equalTo('user@server.com'),
+ $this->equalTo('myFile'),
+ $this->anything(),
+ 'sharedBy'
+ )->willReturn(true);
+
+ $this->rootFolder->expects($this->never())->method($this->anything());
+
+ $this->provider->create($share);
+
+ try {
+ $this->provider->create($share);
+ } catch (\Exception $e) {
+ $this->assertEquals('Sharing myFile failed, because this item is already shared with user@server.com', $e->getMessage());
+ }
+ }
+
+ public function testUpdate() {
+ $share = $this->shareManager->newShare();
+
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $share->setSharedWith('user@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+
+ $this->tokenHandler->method('generateToken')->willReturn('token');
+
+ $this->notifications->expects($this->once())
+ ->method('sendRemoteShare')
+ ->with(
+ $this->equalTo('token'),
+ $this->equalTo('user@server.com'),
+ $this->equalTo('myFile'),
+ $this->anything(),
+ 'sharedBy'
+ )->willReturn(true);
+
+ $this->rootFolder->expects($this->never())->method($this->anything());
+
+ $share = $this->provider->create($share);
+
+ $share->setPermissions(1);
+ $this->provider->update($share);
+
+ $share = $this->provider->getShareById($share->getId());
+
+ $this->assertEquals(1, $share->getPermissions());
+ }
+
+ public function testGetSharedBy() {
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $this->tokenHandler->method('generateToken')->willReturn('token');
+ $this->notifications
+ ->method('sendRemoteShare')
+ ->willReturn(true);
+
+ $this->rootFolder->expects($this->never())->method($this->anything());
+
+ $share = $this->shareManager->newShare();
+ $share->setSharedWith('user@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+ $this->provider->create($share);
+
+ $share2 = $this->shareManager->newShare();
+ $share2->setSharedWith('user2@server.com')
+ ->setSharedBy('sharedBy2')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+ $this->provider->create($share2);
+
+ $shares = $this->provider->getSharesBy('sharedBy', \OCP\Share::SHARE_TYPE_REMOTE, null, false, -1, 0);
+
+ $this->assertCount(1, $shares);
+ $this->assertEquals('user@server.com', $shares[0]->getSharedWith());
+ $this->assertEquals('sharedBy', $shares[0]->getSharedBy());
+ }
+
+ public function testGetSharedByWithNode() {
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $this->tokenHandler->method('generateToken')->willReturn('token');
+ $this->notifications
+ ->method('sendRemoteShare')
+ ->willReturn(true);
+
+ $this->rootFolder->expects($this->never())->method($this->anything());
+
+ $share = $this->shareManager->newShare();
+ $share->setSharedWith('user@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+ $this->provider->create($share);
+
+ $node2 = $this->getMock('\OCP\Files\File');
+ $node2->method('getId')->willReturn(43);
+ $node2->method('getName')->willReturn('myOtherFile');
+
+ $share2 = $this->shareManager->newShare();
+ $share2->setSharedWith('user@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node2);
+ $this->provider->create($share2);
+
+ $shares = $this->provider->getSharesBy('sharedBy', \OCP\Share::SHARE_TYPE_REMOTE, $node2, false, -1, 0);
+
+ $this->assertCount(1, $shares);
+ $this->assertEquals(43, $shares[0]->getNodeId());
+ }
+
+ public function testGetSharedByWithReshares() {
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $this->tokenHandler->method('generateToken')->willReturn('token');
+ $this->notifications
+ ->method('sendRemoteShare')
+ ->willReturn(true);
+
+ $this->rootFolder->expects($this->never())->method($this->anything());
+
+ $share = $this->shareManager->newShare();
+ $share->setSharedWith('user@server.com')
+ ->setSharedBy('shareOwner')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+ $this->provider->create($share);
+
+ $share2 = $this->shareManager->newShare();
+ $share2->setSharedWith('user2@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+ $this->provider->create($share2);
+
+ $shares = $this->provider->getSharesBy('shareOwner', \OCP\Share::SHARE_TYPE_REMOTE, null, true, -1, 0);
+
+ $this->assertCount(2, $shares);
+ }
+
+ public function testGetSharedByWithLimit() {
+ $node = $this->getMock('\OCP\Files\File');
+ $node->method('getId')->willReturn(42);
+ $node->method('getName')->willReturn('myFile');
+
+ $this->tokenHandler->method('generateToken')->willReturn('token');
+ $this->notifications
+ ->method('sendRemoteShare')
+ ->willReturn(true);
+
+ $this->rootFolder->expects($this->never())->method($this->anything());
+
+ $share = $this->shareManager->newShare();
+ $share->setSharedWith('user@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+ $this->provider->create($share);
+
+ $share2 = $this->shareManager->newShare();
+ $share2->setSharedWith('user2@server.com')
+ ->setSharedBy('sharedBy')
+ ->setShareOwner('shareOwner')
+ ->setPermissions(19)
+ ->setNode($node);
+ $this->provider->create($share2);
+
+ $shares = $this->provider->getSharesBy('shareOwner', \OCP\Share::SHARE_TYPE_REMOTE, null, true, 1, 1);
+
+ $this->assertCount(1, $shares);
+ $this->assertEquals('user2@server.com', $shares[0]->getSharedWith());
+ }
+}
diff --git a/apps/federatedfilesharing/tests/tokenhandlertest.php b/apps/federatedfilesharing/tests/tokenhandlertest.php
new file mode 100644
index 00000000000..4ff428d4a46
--- /dev/null
+++ b/apps/federatedfilesharing/tests/tokenhandlertest.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\FederatedFileSharing\Tests;
+
+
+use OCA\FederatedFileSharing\TokenHandler;
+use OCP\Security\ISecureRandom;
+use Test\TestCase;
+
+class TokenHandlerTest extends TestCase {
+
+ /** @var TokenHandler */
+ private $tokenHandler;
+
+ /** @var ISecureRandom | \PHPUnit_Framework_MockObject_MockObject */
+ private $secureRandom;
+
+ /** @var int */
+ private $expectedTokenLength = 15;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->secureRandom = $this->getMock('OCP\Security\ISecureRandom');
+
+ $this->tokenHandler = new TokenHandler($this->secureRandom);
+ }
+
+ public function testGenerateToken() {
+
+ $this->secureRandom->expects($this->once())->method('generate')
+ ->with(
+ $this->expectedTokenLength,
+ ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS
+ )
+ ->willReturn(true);
+
+ $this->assertTrue($this->tokenHandler->generateToken());
+
+ }
+
+}
diff --git a/apps/files/l10n/de.js b/apps/files/l10n/de.js
index 960edf5b718..2314b29ace9 100644
--- a/apps/files/l10n/de.js
+++ b/apps/files/l10n/de.js
@@ -101,6 +101,8 @@ OC.L10N.register(
"Maximum upload size" : "Maximale Upload-Größe",
"max. possible: " : "maximal möglich:",
"Save" : "Speichern",
+ "With PHP-FPM it might take 5 minutes for changes to be applied." : "Mit PHP-FPM kann es bis zu 5 Minuten dauern, bis die Einstellungen übernommen werden.",
+ "Missing permissions to edit from here." : "Fehlende Berechtigungen um von hier aus zu bearbeiten.",
"Settings" : "Einstellungen",
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Benutze diese Adresse, um <a href=\"%s\" target=\"_blank\">über WebDAV auf Deine Dateien zuzugreifen</a>",
diff --git a/apps/files/l10n/de.json b/apps/files/l10n/de.json
index 3d1e061781e..111a5e11a1e 100644
--- a/apps/files/l10n/de.json
+++ b/apps/files/l10n/de.json
@@ -99,6 +99,8 @@
"Maximum upload size" : "Maximale Upload-Größe",
"max. possible: " : "maximal möglich:",
"Save" : "Speichern",
+ "With PHP-FPM it might take 5 minutes for changes to be applied." : "Mit PHP-FPM kann es bis zu 5 Minuten dauern, bis die Einstellungen übernommen werden.",
+ "Missing permissions to edit from here." : "Fehlende Berechtigungen um von hier aus zu bearbeiten.",
"Settings" : "Einstellungen",
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\">access your Files via WebDAV</a>" : "Benutze diese Adresse, um <a href=\"%s\" target=\"_blank\">über WebDAV auf Deine Dateien zuzugreifen</a>",
diff --git a/apps/files_external/appinfo/register_command.php b/apps/files_external/appinfo/register_command.php
index 5f6f42bf8c1..927ce9869f9 100644
--- a/apps/files_external/appinfo/register_command.php
+++ b/apps/files_external/appinfo/register_command.php
@@ -29,6 +29,7 @@ use OCA\Files_External\Command\Export;
use OCA\Files_External\Command\Delete;
use OCA\Files_External\Command\Create;
use OCA\Files_External\Command\Backends;
+use OCA\Files_External\Command\Verify;
$userManager = OC::$server->getUserManager();
$userSession = OC::$server->getUserSession();
@@ -51,3 +52,4 @@ $application->add(new Export($globalStorageService, $userStorageService, $userSe
$application->add(new Delete($globalStorageService, $userStorageService, $userSession, $userManager));
$application->add(new Create($globalStorageService, $userStorageService, $userManager, $userSession, $backendService));
$application->add(new Backends($backendService));
+$application->add(new Verify($globalStorageService));
diff --git a/apps/files_external/command/verify.php b/apps/files_external/command/verify.php
new file mode 100644
index 00000000000..f985cb401af
--- /dev/null
+++ b/apps/files_external/command/verify.php
@@ -0,0 +1,145 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, 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\Command;
+
+use OC\Core\Command\Base;
+use OCA\Files_External\Lib\Auth\AuthMechanism;
+use OCA\Files_External\Lib\Backend\Backend;
+use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
+use OCA\Files_external\Lib\StorageConfig;
+use OCA\Files_external\NotFoundException;
+use OCA\Files_external\Service\GlobalStoragesService;
+use OCP\Files\StorageNotAvailableException;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Helper\TableHelper;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Verify extends Base {
+ /**
+ * @var GlobalStoragesService
+ */
+ protected $globalService;
+
+ function __construct(GlobalStoragesService $globalService) {
+ parent::__construct();
+ $this->globalService = $globalService;
+ }
+
+ protected function configure() {
+ $this
+ ->setName('files_external:verify')
+ ->setDescription('Verify mount configuration')
+ ->addArgument(
+ 'mount_id',
+ InputArgument::REQUIRED,
+ 'The id of the mount to check'
+ )->addOption(
+ 'config',
+ 'c',
+ InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Additional config option to set before checking in key=value pairs, required for certain auth backends such as login credentails'
+ );
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $mountId = $input->getArgument('mount_id');
+ $configInput = $input->getOption('config');
+
+ try {
+ $mount = $this->globalService->getStorage($mountId);
+ } catch (NotFoundException $e) {
+ $output->writeln('<error>Mount with id "' . $mountId . ' not found, check "occ files_external:list" to get available mounts"</error>');
+ return 404;
+ }
+
+ $this->updateStorageStatus($mount, $configInput, $output);
+
+ $this->writeArrayInOutputFormat($input, $output, [
+ 'status' => StorageNotAvailableException::getStateCodeName($mount->getStatus()),
+ 'code' => $mount->getStatus(),
+ 'message' => $mount->getStatusMessage()
+ ]);
+ }
+
+ private function manipulateStorageConfig(StorageConfig $storage) {
+ /** @var AuthMechanism */
+ $authMechanism = $storage->getAuthMechanism();
+ $authMechanism->manipulateStorageConfig($storage);
+ /** @var Backend */
+ $backend = $storage->getBackend();
+ $backend->manipulateStorageConfig($storage);
+ }
+
+ private function updateStorageStatus(StorageConfig &$storage, $configInput, OutputInterface $output) {
+ try {
+ try {
+ $this->manipulateStorageConfig($storage);
+ } catch (InsufficientDataForMeaningfulAnswerException $e) {
+ if (count($configInput) === 0) { // extra config options might solve the error
+ throw $e;
+ }
+ }
+
+ foreach ($configInput as $configOption) {
+ if (!strpos($configOption, '=')) {
+ $output->writeln('<error>Invalid mount configuration option "' . $configOption . '"</error>');
+ return;
+ }
+ list($key, $value) = explode('=', $configOption, 2);
+ $storage->setBackendOption($key, $value);
+ }
+
+ /** @var Backend */
+ $backend = $storage->getBackend();
+ // update status (can be time-consuming)
+ $storage->setStatus(
+ \OC_Mount_Config::getBackendStatus(
+ $backend->getStorageClass(),
+ $storage->getBackendOptions(),
+ false
+ )
+ );
+ } catch (InsufficientDataForMeaningfulAnswerException $e) {
+ $status = $e->getCode() ? $e->getCode() : StorageNotAvailableException::STATUS_INDETERMINATE;
+ $storage->setStatus(
+ $status,
+ $e->getMessage()
+ );
+ } catch (StorageNotAvailableException $e) {
+ $storage->setStatus(
+ $e->getCode(),
+ $e->getMessage()
+ );
+ } catch (\Exception $e) {
+ // FIXME: convert storage exceptions to StorageNotAvailableException
+ $storage->setStatus(
+ StorageNotAvailableException::STATUS_ERROR,
+ get_class($e) . ': ' . $e->getMessage()
+ );
+ }
+ }
+}
diff --git a/apps/files_external/l10n/de.js b/apps/files_external/l10n/de.js
index 709450948ee..1a8f6f28394 100644
--- a/apps/files_external/l10n/de.js
+++ b/apps/files_external/l10n/de.js
@@ -31,8 +31,12 @@ OC.L10N.register(
"Saving..." : "Speichern…",
"Save" : "Speichern",
"There was an error with message: " : "Es ist ein Fehler mit folgender Meldung aufgetreten:",
+ "external-storage" : "Externer Speicher",
"Username" : "Benutzername",
"Password" : "Passwort",
+ "Credentials saved" : "Anmeldeinformationen gespeichert",
+ "Credentials saving failed" : "speichern der Anmeldeinformationen fehlgeschlagen",
+ "Credentials required" : "Anmeldeinformationen benötigt",
"Access key" : "Zugangsschlüssel",
"Secret key" : "Geheimer Schlüssel",
"None" : "Keine",
@@ -46,6 +50,7 @@ OC.L10N.register(
"Tenant name" : "Name des Mieters",
"Rackspace" : "Rackspace",
"API key" : "API-Schlüssel",
+ "Global Credentails" : "Globale Anmeldeinformtionen",
"Username and password" : "Benutzername und Passwort",
"RSA public key" : "RSA öffentlicher Schlüssel",
"Public key" : "Öffentlicher Schlüssel",
@@ -78,6 +83,7 @@ OC.L10N.register(
"Username as share" : "Benutzername als Freigabe",
"OpenStack Object Storage" : "Openstack-Objektspeicher",
"Service name" : "Service Name",
+ "Request timeout (seconds)" : "Anfrage -Timeout ( Sekunden)",
"<b>Note:</b> " : "<b>Hinweis:</b> ",
"<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Hinweis:</b> Die cURL-Unterstützung von PHP ist nicht aktiviert oder installiert. Das Hinzufügen von %s ist nicht möglich. Bitte wende Dich zur Installation an Deinen Systemadministrator.",
"<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Hinweis:</b> Die FTP Unterstützung von PHP ist nicht aktiviert oder installiert. Das Hinzufügen von %s ist nicht möglich. Bitte wende Dich sich zur Installation an Deinen Systemadministrator.",
@@ -88,6 +94,7 @@ OC.L10N.register(
"Storage type" : "Du hast noch keinen externen Speicher",
"Scope" : "Anwendungsbereich",
"External Storage" : "Externer Speicher",
+ "Global Credentials" : "Globale Anmeldeinformationen",
"Folder name" : "Ordnername",
"Authentication" : "Authentifizierung",
"Configuration" : "Konfiguration",
diff --git a/apps/files_external/l10n/de.json b/apps/files_external/l10n/de.json
index 9c645c31298..c6517f40fc0 100644
--- a/apps/files_external/l10n/de.json
+++ b/apps/files_external/l10n/de.json
@@ -29,8 +29,12 @@
"Saving..." : "Speichern…",
"Save" : "Speichern",
"There was an error with message: " : "Es ist ein Fehler mit folgender Meldung aufgetreten:",
+ "external-storage" : "Externer Speicher",
"Username" : "Benutzername",
"Password" : "Passwort",
+ "Credentials saved" : "Anmeldeinformationen gespeichert",
+ "Credentials saving failed" : "speichern der Anmeldeinformationen fehlgeschlagen",
+ "Credentials required" : "Anmeldeinformationen benötigt",
"Access key" : "Zugangsschlüssel",
"Secret key" : "Geheimer Schlüssel",
"None" : "Keine",
@@ -44,6 +48,7 @@
"Tenant name" : "Name des Mieters",
"Rackspace" : "Rackspace",
"API key" : "API-Schlüssel",
+ "Global Credentails" : "Globale Anmeldeinformtionen",
"Username and password" : "Benutzername und Passwort",
"RSA public key" : "RSA öffentlicher Schlüssel",
"Public key" : "Öffentlicher Schlüssel",
@@ -76,6 +81,7 @@
"Username as share" : "Benutzername als Freigabe",
"OpenStack Object Storage" : "Openstack-Objektspeicher",
"Service name" : "Service Name",
+ "Request timeout (seconds)" : "Anfrage -Timeout ( Sekunden)",
"<b>Note:</b> " : "<b>Hinweis:</b> ",
"<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Hinweis:</b> Die cURL-Unterstützung von PHP ist nicht aktiviert oder installiert. Das Hinzufügen von %s ist nicht möglich. Bitte wende Dich zur Installation an Deinen Systemadministrator.",
"<b>Note:</b> The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "<b>Hinweis:</b> Die FTP Unterstützung von PHP ist nicht aktiviert oder installiert. Das Hinzufügen von %s ist nicht möglich. Bitte wende Dich sich zur Installation an Deinen Systemadministrator.",
@@ -86,6 +92,7 @@
"Storage type" : "Du hast noch keinen externen Speicher",
"Scope" : "Anwendungsbereich",
"External Storage" : "Externer Speicher",
+ "Global Credentials" : "Globale Anmeldeinformationen",
"Folder name" : "Ordnername",
"Authentication" : "Authentifizierung",
"Configuration" : "Konfiguration",
diff --git a/apps/files_external/l10n/fr.js b/apps/files_external/l10n/fr.js
index 5328746e5be..ae780704b80 100644
--- a/apps/files_external/l10n/fr.js
+++ b/apps/files_external/l10n/fr.js
@@ -29,7 +29,7 @@ OC.L10N.register(
"Error generating key pair" : "Erreur lors de la génération des clés",
"Enable encryption" : "Activer le chiffrement",
"Enable previews" : "Activer les prévisualisations",
- "Enable sharing" : "Activer le Partage",
+ "Enable sharing" : "Permettre le partage",
"Check for changes" : "Rechercher les modifications",
"Never" : "Jamais",
"Once every direct access" : "Une fois à chaque accès direct",
@@ -45,14 +45,14 @@ OC.L10N.register(
"Couldn't get the list of external mount points: {type}" : "Impossible de récupérer la liste des points de montage externes : {type}",
"There was an error with message: " : "Il y a eu une erreur avec le message :",
"External mount error" : "Erreur de point de montage externe",
- "external-storage" : "Stockage externe",
+ "external-storage" : "external-storage",
"Couldn't get the list of Windows network drive mount points: empty response from the server" : "Impossible d'obtenir la liste des points de montage des disques réseaux Windows : Réponse vide du serveur",
"Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Certains points de montage externes configurés ne sont pas connectés. Veuillez cliquer sur la(les) ligne(s) rouge(s) pour plus d'informations",
"Please enter the credentials for the {mount} mount" : "Veuillez entrer les identifiants pour le montage {mount}",
"Username" : "Nom d'utilisateur",
"Password" : "Mot de passe",
"Credentials saved" : "Identifiants sauvegardés",
- "Credentials saving failed" : "La sauvegarde des Identifiants a échoué",
+ "Credentials saving failed" : "La sauvegarde des identifiants a échoué",
"Credentials required" : "Des informations d'identification sont requises",
"Access key" : "Clé d'accès",
"Secret key" : "Clé secrète",
@@ -73,7 +73,7 @@ OC.L10N.register(
"Log-in credentials, save in database" : "Identifiants de connexion, sauvegardés dans la base de données",
"Username and password" : "Nom d'utilisateur et mot de passe",
"Log-in credentials, save in session" : "Identifiants de connexion, sauvegardés pour la session",
- "User entered, store in database" : "Identifiant utilisateur entré, enregistré dans la base de données",
+ "User entered, store in database" : "Fourni par l'utilisateur, enregistré dans la base de données",
"RSA public key" : "Clé publique RSA",
"Public key" : "Clef publique",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/fr.json b/apps/files_external/l10n/fr.json
index bfbfe0c85ca..53438506bb3 100644
--- a/apps/files_external/l10n/fr.json
+++ b/apps/files_external/l10n/fr.json
@@ -27,7 +27,7 @@
"Error generating key pair" : "Erreur lors de la génération des clés",
"Enable encryption" : "Activer le chiffrement",
"Enable previews" : "Activer les prévisualisations",
- "Enable sharing" : "Activer le Partage",
+ "Enable sharing" : "Permettre le partage",
"Check for changes" : "Rechercher les modifications",
"Never" : "Jamais",
"Once every direct access" : "Une fois à chaque accès direct",
@@ -43,14 +43,14 @@
"Couldn't get the list of external mount points: {type}" : "Impossible de récupérer la liste des points de montage externes : {type}",
"There was an error with message: " : "Il y a eu une erreur avec le message :",
"External mount error" : "Erreur de point de montage externe",
- "external-storage" : "Stockage externe",
+ "external-storage" : "external-storage",
"Couldn't get the list of Windows network drive mount points: empty response from the server" : "Impossible d'obtenir la liste des points de montage des disques réseaux Windows : Réponse vide du serveur",
"Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Certains points de montage externes configurés ne sont pas connectés. Veuillez cliquer sur la(les) ligne(s) rouge(s) pour plus d'informations",
"Please enter the credentials for the {mount} mount" : "Veuillez entrer les identifiants pour le montage {mount}",
"Username" : "Nom d'utilisateur",
"Password" : "Mot de passe",
"Credentials saved" : "Identifiants sauvegardés",
- "Credentials saving failed" : "La sauvegarde des Identifiants a échoué",
+ "Credentials saving failed" : "La sauvegarde des identifiants a échoué",
"Credentials required" : "Des informations d'identification sont requises",
"Access key" : "Clé d'accès",
"Secret key" : "Clé secrète",
@@ -71,7 +71,7 @@
"Log-in credentials, save in database" : "Identifiants de connexion, sauvegardés dans la base de données",
"Username and password" : "Nom d'utilisateur et mot de passe",
"Log-in credentials, save in session" : "Identifiants de connexion, sauvegardés pour la session",
- "User entered, store in database" : "Identifiant utilisateur entré, enregistré dans la base de données",
+ "User entered, store in database" : "Fourni par l'utilisateur, enregistré dans la base de données",
"RSA public key" : "Clé publique RSA",
"Public key" : "Clef publique",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/pt_BR.js b/apps/files_external/l10n/pt_BR.js
index 8706a554d1e..484550ad109 100644
--- a/apps/files_external/l10n/pt_BR.js
+++ b/apps/files_external/l10n/pt_BR.js
@@ -45,10 +45,15 @@ OC.L10N.register(
"Couldn't get the list of external mount points: {type}" : "Não foi possível obter a lista de pontos de montagem externos: {type}",
"There was an error with message: " : "Houve um erro com a mensagem:",
"External mount error" : "Erro de montagem externa",
+ "external-storage" : "armazenamento-externo",
"Couldn't get the list of Windows network drive mount points: empty response from the server" : "Não foi possível obter a lista unidades de pontos de montagem da rede do Windows: resposta vazia a partir do servidor",
"Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Alguns dos pontos de montagem externos configurados não estão conectados. Por favor clique na linha vermelha(s) para mais informações",
+ "Please enter the credentials for the {mount} mount" : "Por favor, insira as credenciais para montar {mount}",
"Username" : "Nome de Usuário",
"Password" : "Senha",
+ "Credentials saved" : "Credenciais salvas",
+ "Credentials saving failed" : "A gravação das credenciais falhou",
+ "Credentials required" : "Credenciais são exigidas",
"Access key" : "Chave da acesso",
"Secret key" : "Chave secreta",
"Builtin" : "Construídas em",
diff --git a/apps/files_external/l10n/pt_BR.json b/apps/files_external/l10n/pt_BR.json
index 9cdbf5d7a11..70bb4422730 100644
--- a/apps/files_external/l10n/pt_BR.json
+++ b/apps/files_external/l10n/pt_BR.json
@@ -43,10 +43,15 @@
"Couldn't get the list of external mount points: {type}" : "Não foi possível obter a lista de pontos de montagem externos: {type}",
"There was an error with message: " : "Houve um erro com a mensagem:",
"External mount error" : "Erro de montagem externa",
+ "external-storage" : "armazenamento-externo",
"Couldn't get the list of Windows network drive mount points: empty response from the server" : "Não foi possível obter a lista unidades de pontos de montagem da rede do Windows: resposta vazia a partir do servidor",
"Some of the configured external mount points are not connected. Please click on the red row(s) for more information" : "Alguns dos pontos de montagem externos configurados não estão conectados. Por favor clique na linha vermelha(s) para mais informações",
+ "Please enter the credentials for the {mount} mount" : "Por favor, insira as credenciais para montar {mount}",
"Username" : "Nome de Usuário",
"Password" : "Senha",
+ "Credentials saved" : "Credenciais salvas",
+ "Credentials saving failed" : "A gravação das credenciais falhou",
+ "Credentials required" : "Credenciais são exigidas",
"Access key" : "Chave da acesso",
"Secret key" : "Chave secreta",
"Builtin" : "Construídas em",
diff --git a/apps/files_sharing/api/share20ocs.php b/apps/files_sharing/api/share20ocs.php
index c03243d1d41..67a94aaf8aa 100644
--- a/apps/files_sharing/api/share20ocs.php
+++ b/apps/files_sharing/api/share20ocs.php
@@ -26,6 +26,7 @@ use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Files\IRootFolder;
+use OCP\Share;
use OCP\Share\IManager;
use OCP\Share\Exceptions\ShareNotFound;
@@ -164,8 +165,15 @@ class Share20OCS {
}
if ($share === null) {
- //For now federated shares are handled by the old endpoint.
- return \OCA\Files_Sharing\API\Local::getShare(['id' => $id]);
+ if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
+ return new \OC_OCS_Result(null, 404, 'wrong share ID, share doesn\'t exist.');
+ }
+
+ try {
+ $share = $this->shareManager->getShareById('ocFederatedSharing:' . $id);
+ } catch (ShareNotFound $e) {
+ return new \OC_OCS_Result(null, 404, 'wrong share ID, share doesn\'t exist.');
+ }
}
if ($this->canAccessShare($share)) {
@@ -195,7 +203,15 @@ class Share20OCS {
// Could not find the share as internal share... maybe it is a federated share
if ($share === null) {
- return \OCA\Files_Sharing\API\Local::deleteShare(['id' => $id]);
+ if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
+ return new \OC_OCS_Result(null, 404, 'wrong share ID, share doesn\'t exist.');
+ }
+
+ try {
+ $share = $this->shareManager->getShareById('ocFederatedSharing:' . $id);
+ } catch (ShareNotFound $e) {
+ return new \OC_OCS_Result(null, 404, 'wrong share ID, share doesn\'t exist.');
+ }
}
if (!$this->canAccessShare($share)) {
@@ -313,8 +329,12 @@ class Share20OCS {
}
} else if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
- //fixme Remote shares are handled by old code path for now
- return \OCA\Files_Sharing\API\Local::createShare([]);
+ if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
+ return new \OC_OCS_Result(null, 403, 'Sharing '.$path.' failed, because the backend does not allow shares from type '.$shareType);
+ }
+
+ $share->setSharedWith($shareWith);
+ $share->setPermissions($permissions);
} else {
return new \OC_OCS_Result(null, 400, "unknown share type");
}
@@ -368,11 +388,12 @@ class Share20OCS {
/** @var \OCP\Share\IShare[] $shares */
$shares = [];
foreach ($nodes as $node) {
- $shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_USER, $node, false, -1, 0));
+ $shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_USER, $node, false, -1, 0));
$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_GROUP, $node, false, -1, 0));
- $shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_LINK, $node, false, -1, 0));
- //TODO: Add federated shares
-
+ $shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_LINK, $node, false, -1, 0));
+ if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
+ $shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_REMOTE, $node, false, -1, 0));
+ }
}
$formatted = [];
@@ -427,10 +448,14 @@ class Share20OCS {
$userShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_USER, $path, $reshares, -1, 0);
$groupShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_GROUP, $path, $reshares, -1, 0);
$linkShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_LINK, $path, $reshares, -1, 0);
- //TODO: Add federated shares
-
$shares = array_merge($userShares, $groupShares, $linkShares);
+ if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
+ $federatedShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_REMOTE, $path, $reshares, -1, 0);
+ $shares = array_merge($shares, $federatedShares);
+ }
+
+
$formatted = [];
foreach ($shares as $share) {
$formatted[] = $this->formatShare($share);
@@ -456,7 +481,15 @@ class Share20OCS {
// Could not find the share as internal share... maybe it is a federated share
if ($share === null) {
- return \OCA\Files_Sharing\API\Local::updateShare(['id' => $id]);
+ if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
+ return new \OC_OCS_Result(null, 404, 'wrong share ID, share doesn\'t exist.');
+ }
+
+ try {
+ $share = $this->shareManager->getShareById('ocFederatedSharing:' . $id);
+ } catch (ShareNotFound $e) {
+ return new \OC_OCS_Result(null, 404, 'wrong share ID, share doesn\'t exist.');
+ }
}
if (!$this->canAccessShare($share)) {
diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml
index 17826be47b4..29ae15e4722 100644
--- a/apps/files_sharing/appinfo/info.xml
+++ b/apps/files_sharing/appinfo/info.xml
@@ -10,7 +10,7 @@ Turning the feature off removes shared files and folders on the server for all s
<licence>AGPL</licence>
<author>Michael Gapczynski, Bjoern Schiessle</author>
<default_enable/>
- <version>0.9.0</version>
+ <version>0.9.1</version>
<types>
<filesystem/>
</types>
diff --git a/apps/files_sharing/appinfo/update.php b/apps/files_sharing/appinfo/update.php
index d754a95705c..ced227a107b 100644
--- a/apps/files_sharing/appinfo/update.php
+++ b/apps/files_sharing/appinfo/update.php
@@ -25,7 +25,7 @@ use OCA\Files_Sharing\Migration;
$installedVersion = \OC::$server->getConfig()->getAppValue('files_sharing', 'installed_version');
// Migration OC8.2 -> OC9
-if (version_compare($installedVersion, '0.9.0', '<')) {
+if (version_compare($installedVersion, '0.9.1', '<')) {
$m = new Migration(\OC::$server->getDatabaseConnection());
$m->removeReShares();
$m->updateInitiatorInfo();
diff --git a/apps/files_sharing/lib/migration.php b/apps/files_sharing/lib/migration.php
index 90e0dead480..e7346385510 100644
--- a/apps/files_sharing/lib/migration.php
+++ b/apps/files_sharing/lib/migration.php
@@ -142,7 +142,8 @@ class Migration {
[
\OCP\Share::SHARE_TYPE_USER,
\OCP\Share::SHARE_TYPE_GROUP,
- \OCP\Share::SHARE_TYPE_LINK
+ \OCP\Share::SHARE_TYPE_LINK,
+ \OCP\Share::SHARE_TYPE_REMOTE,
],
Connection::PARAM_INT_ARRAY
)
@@ -185,7 +186,8 @@ class Migration {
[
\OCP\Share::SHARE_TYPE_USER,
\OCP\Share::SHARE_TYPE_GROUP,
- \OCP\Share::SHARE_TYPE_LINK
+ \OCP\Share::SHARE_TYPE_LINK,
+ \OCP\Share::SHARE_TYPE_REMOTE,
],
Connection::PARAM_INT_ARRAY
)
diff --git a/apps/files_sharing/tests/api/share20ocstest.php b/apps/files_sharing/tests/api/share20ocstest.php
index 97abdca7ac3..a1094ce4b22 100644
--- a/apps/files_sharing/tests/api/share20ocstest.php
+++ b/apps/files_sharing/tests/api/share20ocstest.php
@@ -97,10 +97,17 @@ class Share20OCSTest extends \Test\TestCase {
public function testDeleteShareShareNotFound() {
$this->shareManager
- ->expects($this->once())
+ ->expects($this->exactly(2))
->method('getShareById')
- ->with('ocinternal:42')
- ->will($this->throwException(new \OCP\Share\Exceptions\ShareNotFound()));
+ ->will($this->returnCallback(function($id) {
+ if ($id === 'ocinternal:42' || $id === 'ocFederatedSharing:42') {
+ throw new \OCP\Share\Exceptions\ShareNotFound();
+ } else {
+ throw new \Exception();
+ }
+ }));
+
+ $this->shareManager->method('outgoingServer2ServerSharesAllowed')->willReturn(true);
$expected = new \OC_OCS_Result(null, 404, 'wrong share ID, share doesn\'t exist.');
$this->assertEquals($expected, $this->ocs->deleteShare(42));
diff --git a/apps/files_sharing/tests/migrationtest.php b/apps/files_sharing/tests/migrationtest.php
index e1c047e0342..8a40b76a642 100644
--- a/apps/files_sharing/tests/migrationtest.php
+++ b/apps/files_sharing/tests/migrationtest.php
@@ -209,6 +209,23 @@ class MigrationTest extends TestCase {
$this->assertSame(1,
$query->execute()
);
+ $parent = $query->getLastInsertId();
+ // third re-share, should be attached to the first user share after migration
+ $query->setParameter('share_type', \OCP\Share::SHARE_TYPE_REMOTE)
+ ->setParameter('share_with', 'user@server.com')
+ ->setParameter('uid_owner', 'user3')
+ ->setParameter('uid_initiator', '')
+ ->setParameter('parent', $parent)
+ ->setParameter('item_type', 'file')
+ ->setParameter('item_source', '2')
+ ->setParameter('item_target', '/2')
+ ->setParameter('file_source', 2)
+ ->setParameter('file_target', '/foobar')
+ ->setParameter('permissions', 31)
+ ->setParameter('stime', time());
+ $this->assertSame(1,
+ $query->execute()
+ );
}
public function testRemoveReShares() {
@@ -221,7 +238,7 @@ class MigrationTest extends TestCase {
$query = $this->connection->getQueryBuilder();
$query->select('*')->from($this->table)->orderBy('id');
$result = $query->execute()->fetchAll();
- $this->assertSame(8, count($result));
+ $this->assertSame(9, count($result));
// shares which shouldn't be modified
for ($i = 0; $i < 4; $i++) {
@@ -238,7 +255,7 @@ class MigrationTest extends TestCase {
$this->assertEmpty($result[5]['uid_initiator']);
$this->assertNull($result[5]['parent']);
// flatted re-shares
- for($i = 6; $i < 8; $i++) {
+ for($i = 6; $i < 9; $i++) {
$this->assertSame('owner2', $result[$i]['uid_owner']);
$user = 'user' . ($i - 5);
$this->assertSame($user, $result[$i]['uid_initiator']);
diff --git a/apps/files_versions/lib/storage.php b/apps/files_versions/lib/storage.php
index 35b3110928b..88a4126dabd 100644
--- a/apps/files_versions/lib/storage.php
+++ b/apps/files_versions/lib/storage.php
@@ -52,6 +52,10 @@ class Storage {
const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
const VERSIONS_ROOT = 'files_versions/';
+ const DELETE_TRIGGER_MASTER_REMOVED = 0;
+ const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
+ const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
+
// files for which we can remove the versions after the delete operation was successful
private static $deletedFiles = array();
@@ -210,9 +214,9 @@ class Storage {
$versions = self::getVersions($uid, $filename);
if (!empty($versions)) {
foreach ($versions as $v) {
- \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version']));
+ \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
self::deleteVersion($view, $filename . '.v' . $v['version']);
- \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version']));
+ \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
}
}
}
@@ -309,6 +313,7 @@ class Storage {
Storage::scheduleExpire($uid, $file);
\OC_Hook::emit('\OCP\Versions', 'rollback', array(
'path' => $filename,
+ 'revision' => $revision,
));
return true;
} else if ($versionCreated) {
@@ -444,9 +449,9 @@ class Storage {
$view = new \OC\Files\View('/' . $uid . '/files_versions');
if (!empty($toDelete)) {
foreach ($toDelete as $version) {
- \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version']));
+ \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
- \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version']));
+ \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
}
}
}
@@ -705,9 +710,9 @@ class Storage {
}
foreach($toDelete as $key => $path) {
- \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path));
+ \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
self::deleteVersion($versionsFileview, $path);
- \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path));
+ \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
unset($allVersions[$key]); // update array with the versions we keep
\OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::DEBUG);
}
@@ -722,9 +727,9 @@ class Storage {
reset($allVersions);
while ($availableSpace < 0 && $i < $numOfVersions) {
$version = current($allVersions);
- \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version']));
+ \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
- \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version']));
+ \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
\OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::DEBUG);
$versionsSize -= $version['size'];
$availableSpace += $version['size'];
diff --git a/apps/files_versions/tests/versions.php b/apps/files_versions/tests/versions.php
index b85877cebd3..d7d9c7a4fb4 100644
--- a/apps/files_versions/tests/versions.php
+++ b/apps/files_versions/tests/versions.php
@@ -652,7 +652,9 @@ class Test_Files_Versioning extends \Test\TestCase {
'path' => '/sub/test.txt',
);
- $this->assertEquals($expectedParams, $params);
+ $this->assertEquals($expectedParams['path'], $params['path']);
+ $this->assertTrue(array_key_exists('revision', $params));
+ $this->assertTrue($params['revision'] > 0);
$this->assertEquals('version2', $this->rootView->file_get_contents($filePath));
$info2 = $this->rootView->getFileInfo($filePath);
diff --git a/apps/user_ldap/l10n/fr.js b/apps/user_ldap/l10n/fr.js
index e7136bb70a5..9d934181190 100644
--- a/apps/user_ldap/l10n/fr.js
+++ b/apps/user_ldap/l10n/fr.js
@@ -39,7 +39,7 @@ OC.L10N.register(
"Select attributes" : "Sélectionner les attributs",
"User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "Utilisateur introuvable. Veuillez vérifier les attributs de login et le nom d'utilisateur. Filtre effectif (à copier-coller pour valider en ligne de commande):<br/>",
"User found and settings verified." : "Utilisateur trouvé et paramètres vérifiés.",
- "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter.",
+ "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Les paramètres ont été vérifiés, mais la recherche retourne plus d'un utilisateur. Seul le premier trouvé pourra se connecter. Nous vous conseillons d'utiliser un filtre plus restrictif.",
"An unspecified error occurred. Please check the settings and the log." : "Une erreur inconnue s'est produite. Veuillez vérifier les paramètres et le log.",
"The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "Le filtre de recherche n'est pas valide, probablement à cause de problèmes de syntaxe tels que des parenthèses manquantes. Veuillez le corriger.",
"A connection error to LDAP / AD occurred, please check host, port and credentials." : "Une erreur s'est produite lors de la connexion au LDAP / AD. Veuillez vérifier l'hôte, le port et les informations d'identification.",
@@ -122,6 +122,8 @@ OC.L10N.register(
"Directory Settings" : "Paramètres du répertoire",
"User Display Name Field" : "Champ \"nom d'affichage\" de l'utilisateur",
"The LDAP attribute to use to generate the user's display name." : "L'attribut LDAP utilisé pour générer le nom d'affichage de l'utilisateur.",
+ "2nd User Display Name Field" : "Second attribut pour le nom d'affichage",
+ "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Optionnel. Attribut LDAP à ajouter au nom d'affichage, entre parenthèses. Cela donnera par exemple : \"John Doe (john.doe@example.com)\".",
"Base User Tree" : "DN racine de l'arbre utilisateurs",
"One User Base DN per line" : "Un DN de base utilisateur par ligne",
"User Search Attributes" : "Attributs de recherche utilisateurs",
@@ -132,8 +134,8 @@ OC.L10N.register(
"One Group Base DN per line" : "Un DN de base groupe par ligne",
"Group Search Attributes" : "Attributs de recherche des groupes",
"Group-Member association" : "Association groupe-membre",
- "Dynamic Group Member URL" : "URL de Membre de Groupe Dynamique",
- "The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)" : "L'attribut LDAP mis sur les objets de groupe contient une URL de recherche LDAP qui détermine quels objets appartiennent à ce groupe. (Un attribut vide désactive la fonctionnalité de groupe dynamique)",
+ "Dynamic Group Member URL" : "Dynamic Group Member URL",
+ "The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)" : "L'attribut LDAP des objets groupes qui contient l'URL de recherche LDAP déterminant quels objets appartiennent au groupe. (Un attribut vide désactive la fonctionnalité de groupes dynamiques)",
"Nested Groups" : "Groupes imbriqués",
"When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "Si activé, les groupes contenant d'autres groupes sont pris en charge (fonctionne uniquement si l'attribut membre du groupe contient des DNs).",
"Paging chunksize" : "Paging chunksize",
diff --git a/apps/user_ldap/l10n/fr.json b/apps/user_ldap/l10n/fr.json
index 50bac76a781..64df1a3db67 100644
--- a/apps/user_ldap/l10n/fr.json
+++ b/apps/user_ldap/l10n/fr.json
@@ -37,7 +37,7 @@
"Select attributes" : "Sélectionner les attributs",
"User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "Utilisateur introuvable. Veuillez vérifier les attributs de login et le nom d'utilisateur. Filtre effectif (à copier-coller pour valider en ligne de commande):<br/>",
"User found and settings verified." : "Utilisateur trouvé et paramètres vérifiés.",
- "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter.",
+ "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Les paramètres ont été vérifiés, mais la recherche retourne plus d'un utilisateur. Seul le premier trouvé pourra se connecter. Nous vous conseillons d'utiliser un filtre plus restrictif.",
"An unspecified error occurred. Please check the settings and the log." : "Une erreur inconnue s'est produite. Veuillez vérifier les paramètres et le log.",
"The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "Le filtre de recherche n'est pas valide, probablement à cause de problèmes de syntaxe tels que des parenthèses manquantes. Veuillez le corriger.",
"A connection error to LDAP / AD occurred, please check host, port and credentials." : "Une erreur s'est produite lors de la connexion au LDAP / AD. Veuillez vérifier l'hôte, le port et les informations d'identification.",
@@ -120,6 +120,8 @@
"Directory Settings" : "Paramètres du répertoire",
"User Display Name Field" : "Champ \"nom d'affichage\" de l'utilisateur",
"The LDAP attribute to use to generate the user's display name." : "L'attribut LDAP utilisé pour générer le nom d'affichage de l'utilisateur.",
+ "2nd User Display Name Field" : "Second attribut pour le nom d'affichage",
+ "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Optionnel. Attribut LDAP à ajouter au nom d'affichage, entre parenthèses. Cela donnera par exemple : \"John Doe (john.doe@example.com)\".",
"Base User Tree" : "DN racine de l'arbre utilisateurs",
"One User Base DN per line" : "Un DN de base utilisateur par ligne",
"User Search Attributes" : "Attributs de recherche utilisateurs",
@@ -130,8 +132,8 @@
"One Group Base DN per line" : "Un DN de base groupe par ligne",
"Group Search Attributes" : "Attributs de recherche des groupes",
"Group-Member association" : "Association groupe-membre",
- "Dynamic Group Member URL" : "URL de Membre de Groupe Dynamique",
- "The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)" : "L'attribut LDAP mis sur les objets de groupe contient une URL de recherche LDAP qui détermine quels objets appartiennent à ce groupe. (Un attribut vide désactive la fonctionnalité de groupe dynamique)",
+ "Dynamic Group Member URL" : "Dynamic Group Member URL",
+ "The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)" : "L'attribut LDAP des objets groupes qui contient l'URL de recherche LDAP déterminant quels objets appartiennent au groupe. (Un attribut vide désactive la fonctionnalité de groupes dynamiques)",
"Nested Groups" : "Groupes imbriqués",
"When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "Si activé, les groupes contenant d'autres groupes sont pris en charge (fonctionne uniquement si l'attribut membre du groupe contient des DNs).",
"Paging chunksize" : "Paging chunksize",
diff --git a/apps/user_ldap/l10n/pt_BR.js b/apps/user_ldap/l10n/pt_BR.js
index 8e52b17dce6..4a0b743154f 100644
--- a/apps/user_ldap/l10n/pt_BR.js
+++ b/apps/user_ldap/l10n/pt_BR.js
@@ -122,6 +122,8 @@ OC.L10N.register(
"Directory Settings" : "Configurações de Diretório",
"User Display Name Field" : "Campo Nome de Exibição de Usuário",
"The LDAP attribute to use to generate the user's display name." : "O atributo LDAP para usar para gerar o nome de exibição do usuário.",
+ "2nd User Display Name Field" : "2º Nome do Campo de Exibição do Usuário",
+ "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Opcional. Um atributo LDAP para ser adicionado ao nome de exibição entre colchetes. Exemplo de resultado »John Doe (john.doe@example.org)«.",
"Base User Tree" : "Árvore de Usuário Base",
"One User Base DN per line" : "Um usuário-base DN por linha",
"User Search Attributes" : "Atributos de Busca de Usuário",
diff --git a/apps/user_ldap/l10n/pt_BR.json b/apps/user_ldap/l10n/pt_BR.json
index 4ca70230a19..87f8d3864ec 100644
--- a/apps/user_ldap/l10n/pt_BR.json
+++ b/apps/user_ldap/l10n/pt_BR.json
@@ -120,6 +120,8 @@
"Directory Settings" : "Configurações de Diretório",
"User Display Name Field" : "Campo Nome de Exibição de Usuário",
"The LDAP attribute to use to generate the user's display name." : "O atributo LDAP para usar para gerar o nome de exibição do usuário.",
+ "2nd User Display Name Field" : "2º Nome do Campo de Exibição do Usuário",
+ "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Opcional. Um atributo LDAP para ser adicionado ao nome de exibição entre colchetes. Exemplo de resultado »John Doe (john.doe@example.org)«.",
"Base User Tree" : "Árvore de Usuário Base",
"One User Base DN per line" : "Um usuário-base DN por linha",
"User Search Attributes" : "Atributos de Busca de Usuário",