summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/dav/lib/carddav/converter.php17
-rw-r--r--apps/federation/api/ocsauthapi.php8
-rw-r--r--apps/federation/backgroundjob/getsharedsecret.php3
-rw-r--r--apps/federation/backgroundjob/requestsharedsecret.php3
-rw-r--r--apps/files/appinfo/info.xml2
-rw-r--r--apps/files/appinfo/install.php6
-rw-r--r--apps/files/appinfo/update.php14
-rw-r--r--apps/files/css/files.css2
-rw-r--r--apps/files/l10n/de_DE.js2
-rw-r--r--apps/files/l10n/de_DE.json2
-rw-r--r--apps/files/lib/backgroundjob/deleteorphanedtagsjob.php105
-rw-r--r--apps/files/tests/backgroundjob/DeleteOrphanedTagsJobTest.php158
-rw-r--r--apps/files_external/appinfo/application.php1
-rw-r--r--apps/files_external/appinfo/register_command.php2
-rw-r--r--apps/files_external/command/delete.php114
-rw-r--r--apps/files_external/controller/globalstoragescontroller.php8
-rw-r--r--apps/files_external/controller/storagescontroller.php17
-rw-r--r--apps/files_external/controller/userglobalstoragescontroller.php66
-rw-r--r--apps/files_external/controller/userstoragescontroller.php9
-rw-r--r--apps/files_external/js/settings.js115
-rw-r--r--apps/files_external/l10n/cs_CZ.js2
-rw-r--r--apps/files_external/l10n/cs_CZ.json2
-rw-r--r--apps/files_external/l10n/de_DE.js1
-rw-r--r--apps/files_external/l10n/de_DE.json1
-rw-r--r--apps/files_external/l10n/fi_FI.js1
-rw-r--r--apps/files_external/l10n/fi_FI.json1
-rw-r--r--apps/files_external/l10n/he.js2
-rw-r--r--apps/files_external/l10n/he.json2
-rw-r--r--apps/files_external/l10n/it.js2
-rw-r--r--apps/files_external/l10n/it.json2
-rw-r--r--apps/files_external/l10n/ja.js2
-rw-r--r--apps/files_external/l10n/ja.json2
-rw-r--r--apps/files_external/l10n/nl.js2
-rw-r--r--apps/files_external/l10n/nl.json2
-rw-r--r--apps/files_external/l10n/pt_BR.js1
-rw-r--r--apps/files_external/l10n/pt_BR.json1
-rw-r--r--apps/files_external/l10n/pt_PT.js2
-rw-r--r--apps/files_external/l10n/pt_PT.json2
-rw-r--r--apps/files_external/l10n/sq.js2
-rw-r--r--apps/files_external/l10n/sq.json2
-rw-r--r--apps/files_external/lib/auth/authmechanism.php1
-rw-r--r--apps/files_external/lib/auth/iuserprovided.php36
-rw-r--r--apps/files_external/lib/auth/password/logincredentials.php2
-rw-r--r--apps/files_external/lib/auth/password/sessioncredentials.php2
-rw-r--r--apps/files_external/lib/auth/password/userprovided.php88
-rw-r--r--apps/files_external/lib/definitionparameter.php52
-rw-r--r--apps/files_external/lib/failedcache.php126
-rw-r--r--apps/files_external/lib/failedstorage.php5
-rw-r--r--apps/files_external/lib/frontenddefinitiontrait.php8
-rw-r--r--apps/files_external/lib/storageconfig.php1
-rw-r--r--apps/files_external/tests/controller/globalstoragescontrollertest.php3
-rw-r--r--apps/files_external/tests/controller/userstoragescontrollertest.php3
-rw-r--r--apps/files_external/tests/frontenddefinitiontraittest.php2
-rw-r--r--apps/files_external/tests/js/settingsSpec.js77
-rw-r--r--apps/files_sharing/api/share20ocs.php18
-rw-r--r--apps/files_sharing/appinfo/info.xml2
-rw-r--r--apps/files_sharing/appinfo/update.php9
-rw-r--r--apps/files_sharing/lib/migration.php243
-rw-r--r--apps/files_sharing/tests/migrationtest.php288
-rw-r--r--apps/user_ldap/l10n/he.js22
-rw-r--r--apps/user_ldap/l10n/he.json22
-rw-r--r--apps/user_ldap/lib/connection.php2
-rw-r--r--build/integration/features/sharing-v1.feature22
-rw-r--r--core/js/config.php2
-rw-r--r--core/js/js.js4
-rw-r--r--core/js/shareitemmodel.js53
-rw-r--r--core/js/tests/specs/shareitemmodelSpec.js112
-rw-r--r--core/l10n/cs_CZ.js3
-rw-r--r--core/l10n/cs_CZ.json3
-rw-r--r--core/l10n/de_DE.js3
-rw-r--r--core/l10n/de_DE.json3
-rw-r--r--core/l10n/fi_FI.js3
-rw-r--r--core/l10n/fi_FI.json3
-rw-r--r--core/l10n/he.js53
-rw-r--r--core/l10n/he.json53
-rw-r--r--core/l10n/it.js3
-rw-r--r--core/l10n/it.json3
-rw-r--r--core/l10n/ja.js4
-rw-r--r--core/l10n/ja.json4
-rw-r--r--core/l10n/nl.js3
-rw-r--r--core/l10n/nl.json3
-rw-r--r--core/l10n/pt_BR.js3
-rw-r--r--core/l10n/pt_BR.json3
-rw-r--r--core/l10n/pt_PT.js3
-rw-r--r--core/l10n/pt_PT.json3
-rw-r--r--core/l10n/sq.js3
-rw-r--r--core/l10n/sq.json3
-rw-r--r--lib/l10n/de_DE.js2
-rw-r--r--lib/l10n/de_DE.json2
-rw-r--r--lib/l10n/ja.js3
-rw-r--r--lib/l10n/ja.json3
-rw-r--r--lib/l10n/nl.js1
-rw-r--r--lib/l10n/nl.json1
-rw-r--r--lib/l10n/pt_BR.js1
-rw-r--r--lib/l10n/pt_BR.json1
-rw-r--r--lib/private/appframework/dependencyinjection/dicontainer.php10
-rw-r--r--lib/private/appframework/middleware/security/securitymiddleware.php37
-rw-r--r--lib/private/files/cache/cache.php67
-rw-r--r--lib/private/files/cache/movefromcachetrait.php87
-rw-r--r--lib/private/security/csp/contentsecuritypolicy.php199
-rw-r--r--lib/private/security/csp/contentsecuritypolicymanager.php73
-rw-r--r--lib/private/server.php12
-rw-r--r--lib/private/share20/defaultshareprovider.php23
-rw-r--r--lib/private/share20/manager.php45
-rw-r--r--lib/private/templatelayout.php2
-rw-r--r--lib/public/appframework/controller.php2
-rw-r--r--lib/public/appframework/http/contentsecuritypolicy.php335
-rw-r--r--lib/public/appframework/http/emptycontentsecuritypolicy.php387
-rw-r--r--lib/public/files/cache/icache.php4
-rw-r--r--lib/public/files/cache/icacheentry.php2
-rw-r--r--lib/public/files/storagenotavailableexception.php2
-rw-r--r--lib/public/iservercontainer.php7
-rw-r--r--lib/public/security/icontentsecuritypolicymanager.php50
-rw-r--r--lib/public/share/imanager.php4
-rw-r--r--lib/public/share/ishareprovider.php4
-rw-r--r--settings/l10n/de_DE.js7
-rw-r--r--settings/l10n/de_DE.json7
-rw-r--r--settings/l10n/ja.js2
-rw-r--r--settings/l10n/ja.json2
-rw-r--r--settings/l10n/nl.js2
-rw-r--r--settings/l10n/nl.json2
-rw-r--r--settings/l10n/pt_BR.js2
-rw-r--r--settings/l10n/pt_BR.json2
-rw-r--r--settings/l10n/zh_TW.js2
-rw-r--r--settings/l10n/zh_TW.json2
-rw-r--r--settings/personal.php2
-rw-r--r--settings/users.php2
-rw-r--r--tests/lib/appframework/http/ContentSecurityPolicyTest.php17
-rw-r--r--tests/lib/appframework/http/EmptyContentSecurityPolicyTest.php430
-rw-r--r--tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php37
-rw-r--r--tests/lib/files/cache/cache.php4
-rw-r--r--tests/lib/files/cache/movefromcachetraittest.php31
-rw-r--r--tests/lib/security/csp/ContentSecurityPolicyManagerTest.php66
-rw-r--r--tests/lib/server.php2
-rw-r--r--tests/lib/share20/defaultshareprovidertest.php78
-rw-r--r--tests/lib/share20/managertest.php130
136 files changed, 3657 insertions, 593 deletions
diff --git a/apps/dav/lib/carddav/converter.php b/apps/dav/lib/carddav/converter.php
index 06d3cb4f18e..c8d9b94c267 100644
--- a/apps/dav/lib/carddav/converter.php
+++ b/apps/dav/lib/carddav/converter.php
@@ -39,7 +39,7 @@ class Converter {
$displayName = empty($displayName ) ? $uid : $displayName;
$emailAddress = $user->getEMailAddress();
$cloudId = $user->getCloudId();
- $image = $user->getAvatarImage(-1);
+ $image = $this->getAvatarImage($user);
$vCard = new VCard();
$vCard->add(new Text($vCard, 'UID', $uid));
@@ -72,7 +72,7 @@ class Converter {
$displayName = empty($displayName ) ? $uid : $displayName;
$emailAddress = $user->getEMailAddress();
$cloudId = $user->getCloudId();
- $image = $user->getAvatarImage(-1);
+ $image = $this->getAvatarImage($user);
$updated = false;
if($this->propertyNeedsUpdate($vCard, 'FN', $displayName)) {
@@ -155,4 +155,17 @@ class Converter {
return $result;
}
+ /**
+ * @param IUser $user
+ * @return null|IImage
+ */
+ private function getAvatarImage(IUser $user) {
+ try {
+ $image = $user->getAvatarImage(-1);
+ return $image;
+ } catch (\Exception $ex) {
+ return null;
+ }
+ }
+
}
diff --git a/apps/federation/api/ocsauthapi.php b/apps/federation/api/ocsauthapi.php
index 083be1b7ecd..058a5966374 100644
--- a/apps/federation/api/ocsauthapi.php
+++ b/apps/federation/api/ocsauthapi.php
@@ -99,7 +99,7 @@ class OCSAuthAPI {
$token = $this->request->getParam('token');
if ($this->trustedServers->isTrustedServer($url) === false) {
- $this->logger->log(\OCP\Util::ERROR, 'remote server not trusted (' . $url . ') while requesting shared secret');
+ $this->logger->log(\OCP\Util::ERROR, 'remote server not trusted (' . $url . ') while requesting shared secret', ['app' => 'federation']);
return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
}
@@ -107,7 +107,7 @@ class OCSAuthAPI {
// token wins
$localToken = $this->dbHandler->getToken($url);
if (strcmp($localToken, $token) > 0) {
- $this->logger->log(\OCP\Util::ERROR, 'remote server (' . $url . ') presented lower token');
+ $this->logger->log(\OCP\Util::ERROR, 'remote server (' . $url . ') presented lower token', ['app' => 'federation']);
return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
}
@@ -134,12 +134,12 @@ class OCSAuthAPI {
$token = $this->request->getParam('token');
if ($this->trustedServers->isTrustedServer($url) === false) {
- $this->logger->log(\OCP\Util::ERROR, 'remote server not trusted (' . $url . ') while getting shared secret');
+ $this->logger->log(\OCP\Util::ERROR, 'remote server not trusted (' . $url . ') while getting shared secret', ['app' => 'federation']);
return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
}
if ($this->isValidToken($url, $token) === false) {
- $this->logger->log(\OCP\Util::ERROR, 'remote server (' . $url . ') didn\'t send a valid token (got ' . $token . ') while getting shared secret');
+ $this->logger->log(\OCP\Util::ERROR, 'remote server (' . $url . ') didn\'t send a valid token (got ' . $token . ') while getting shared secret', ['app' => 'federation']);
return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
}
diff --git a/apps/federation/backgroundjob/getsharedsecret.php b/apps/federation/backgroundjob/getsharedsecret.php
index cae446f915c..a98a17e323b 100644
--- a/apps/federation/backgroundjob/getsharedsecret.php
+++ b/apps/federation/backgroundjob/getsharedsecret.php
@@ -151,6 +151,9 @@ class GetSharedSecret extends QueuedJob{
} catch (ClientException $e) {
$status = $e->getCode();
$this->logger->logException($e);
+ } catch (\Exception $e) {
+ $status = HTTP::STATUS_INTERNAL_SERVER_ERROR;
+ $this->logger->logException($e);
}
// if we received a unexpected response we try again later
diff --git a/apps/federation/backgroundjob/requestsharedsecret.php b/apps/federation/backgroundjob/requestsharedsecret.php
index 92305b7e8ea..2db5d09545a 100644
--- a/apps/federation/backgroundjob/requestsharedsecret.php
+++ b/apps/federation/backgroundjob/requestsharedsecret.php
@@ -149,6 +149,9 @@ class RequestSharedSecret extends QueuedJob {
} catch (ClientException $e) {
$status = $e->getCode();
$this->logger->logException($e);
+ } catch (\Exception $e) {
+ $status = HTTP::STATUS_INTERNAL_SERVER_ERROR;
+ $this->logger->logException($e);
}
// if we received a unexpected response we try again later
diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml
index 37ee564057a..c0db1783235 100644
--- a/apps/files/appinfo/info.xml
+++ b/apps/files/appinfo/info.xml
@@ -7,7 +7,7 @@
<author>Robin Appelman, Vincent Petry</author>
<standalone/>
<default_enable/>
- <version>1.4.1</version>
+ <version>1.4.2</version>
<types>
<filesystem/>
</types>
diff --git a/apps/files/appinfo/install.php b/apps/files/appinfo/install.php
index 485a5f2976d..b9a893d1ee8 100644
--- a/apps/files/appinfo/install.php
+++ b/apps/files/appinfo/install.php
@@ -20,7 +20,5 @@
*/
// Cron job for scanning user storages
-$jobList = \OC::$server->getJobList();
-$job = 'OCA\Files\BackgroundJob\ScanFiles';
-\OC::$server->getJobList()->add($job);
-
+\OC::$server->getJobList()->add('OCA\Files\BackgroundJob\ScanFiles');
+\OC::$server->getJobList()->add('OCA\Files\BackgroundJob\DeleteOrphanedTagsJob');
diff --git a/apps/files/appinfo/update.php b/apps/files/appinfo/update.php
index df13696ab42..003f6916ac5 100644
--- a/apps/files/appinfo/update.php
+++ b/apps/files/appinfo/update.php
@@ -98,15 +98,5 @@ if ($installedVersion === '1.1.9' && (
}
// Add cron job for scanning user storages
-$jobList = \OC::$server->getJobList();
-$job = 'OCA\Files\BackgroundJob\ScanFiles';
-\OC::$server->getJobList()->add($job);
-
-/**
- * migrate old constant DEBUG to new config value 'debug'
- *
- * TODO: remove this in ownCloud 8.3
- */
-if(defined('DEBUG') && DEBUG === true) {
- \OC::$server->getConfig()->setSystemValue('debug', true);
-}
+\OC::$server->getJobList()->add('OCA\Files\BackgroundJob\ScanFiles');
+\OC::$server->getJobList()->add('OCA\Files\BackgroundJob\DeleteOrphanedTagsJob');
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 61148428f79..4929039f837 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -59,7 +59,7 @@
/* fit app list view heights */
.app-files #app-content>.viewcontainer {
- min-height: 100%;
+ min-height: 0%;
}
.app-files #app-content {
diff --git a/apps/files/l10n/de_DE.js b/apps/files/l10n/de_DE.js
index 15b82e0f181..ce8a9132a0c 100644
--- a/apps/files/l10n/de_DE.js
+++ b/apps/files/l10n/de_DE.js
@@ -92,6 +92,7 @@ OC.L10N.register(
"%2$s deleted %1$s" : "%2$s hat %1$s gelöscht",
"You restored %1$s" : "Sie haben %1$s wiederhergestellt",
"%2$s restored %1$s" : "%2$s wiederhergestellt %1$s",
+ "Changed by %2$s" : "Geändert von %2$s",
"Deleted by %2$s" : "Gelöscht durch %2$s",
"Restored by %2$s" : "Wiederhergestellt durch %2$s",
"Upload (max. %s)" : "Hochladen (max. %s)",
@@ -99,6 +100,7 @@ OC.L10N.register(
"Maximum upload size" : "Maximale Upload-Größe",
"max. possible: " : "maximal möglich:",
"Save" : "Speichern",
+ "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>" : "Benutzen Sie diese Adresse, um <a href=\"%s\" target=\"_blank\">über WebDAV auf Ihre Dateien zuzugreifen</a>",
diff --git a/apps/files/l10n/de_DE.json b/apps/files/l10n/de_DE.json
index 87e47454b62..f6a3abc9891 100644
--- a/apps/files/l10n/de_DE.json
+++ b/apps/files/l10n/de_DE.json
@@ -90,6 +90,7 @@
"%2$s deleted %1$s" : "%2$s hat %1$s gelöscht",
"You restored %1$s" : "Sie haben %1$s wiederhergestellt",
"%2$s restored %1$s" : "%2$s wiederhergestellt %1$s",
+ "Changed by %2$s" : "Geändert von %2$s",
"Deleted by %2$s" : "Gelöscht durch %2$s",
"Restored by %2$s" : "Wiederhergestellt durch %2$s",
"Upload (max. %s)" : "Hochladen (max. %s)",
@@ -97,6 +98,7 @@
"Maximum upload size" : "Maximale Upload-Größe",
"max. possible: " : "maximal möglich:",
"Save" : "Speichern",
+ "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>" : "Benutzen Sie diese Adresse, um <a href=\"%s\" target=\"_blank\">über WebDAV auf Ihre Dateien zuzugreifen</a>",
diff --git a/apps/files/lib/backgroundjob/deleteorphanedtagsjob.php b/apps/files/lib/backgroundjob/deleteorphanedtagsjob.php
new file mode 100644
index 00000000000..33f455b5b40
--- /dev/null
+++ b/apps/files/lib/backgroundjob/deleteorphanedtagsjob.php
@@ -0,0 +1,105 @@
+<?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\Files\BackgroundJob;
+
+use OC\BackgroundJob\TimedJob;
+
+/**
+ * Delete all share entries that have no matching entries in the file cache table.
+ */
+class DeleteOrphanedTagsJob extends TimedJob {
+
+ /** @var \OCP\IDBConnection */
+ protected $connection;
+
+ /** @var \OCP\ILogger */
+ protected $logger;
+
+ /**
+ * Default interval in minutes
+ *
+ * @var int $defaultIntervalMin
+ **/
+ protected $defaultIntervalMin = 60;
+
+ /**
+ * sets the correct interval for this timed job
+ */
+ public function __construct() {
+ $this->interval = $this->defaultIntervalMin * 60;
+ $this->connection = \OC::$server->getDatabaseConnection();
+ $this->logger = \OC::$server->getLogger();
+ }
+
+ /**
+ * Makes the background job do its work
+ *
+ * @param array $argument unused argument
+ */
+ public function run($argument) {
+ $this->cleanSystemTags();
+ $this->cleanUserTags();
+ }
+
+ /**
+ * Deleting orphaned system tag mappings
+ *
+ * @return int Number of deleted entries
+ */
+ protected function cleanSystemTags() {
+ $subQuery = $this->connection->getQueryBuilder();
+ $subQuery->select($subQuery->expr()->literal('1'))
+ ->from('filecache', 'f')
+ ->where($subQuery->expr()->eq('objectid', 'f.fileid'));
+
+ $query = $this->connection->getQueryBuilder();
+ $deletedEntries = $query->delete('systemtag_object_mapping')
+ ->where($query->expr()->eq('objecttype', $query->expr()->literal('files')))
+ ->andWhere($query->expr()->isNull($query->createFunction('(' . $subQuery->getSql() . ')')))
+ ->execute();
+
+ $this->logger->debug("$deletedEntries orphaned system tag relations deleted", ['app' => 'DeleteOrphanedTagsJob']);
+ return $deletedEntries;
+ }
+
+ /**
+ * Deleting orphaned user tag mappings
+ *
+ * @return int Number of deleted entries
+ */
+ protected function cleanUserTags() {
+ $subQuery = $this->connection->getQueryBuilder();
+ $subQuery->select($subQuery->expr()->literal('1'))
+ ->from('filecache', 'f')
+ ->where($subQuery->expr()->eq('objid', 'f.fileid'));
+
+ $query = $this->connection->getQueryBuilder();
+ $deletedEntries = $query->delete('vcategory_to_object')
+ ->where($query->expr()->eq('type', $query->expr()->literal('files')))
+ ->andWhere($query->expr()->isNull($query->createFunction('(' . $subQuery->getSql() . ')')))
+ ->execute();
+
+ $this->logger->debug("$deletedEntries orphaned user tag relations deleted", ['app' => 'DeleteOrphanedTagsJob']);
+ return $deletedEntries;
+ }
+
+}
diff --git a/apps/files/tests/backgroundjob/DeleteOrphanedTagsJobTest.php b/apps/files/tests/backgroundjob/DeleteOrphanedTagsJobTest.php
new file mode 100644
index 00000000000..d2e9d77cb20
--- /dev/null
+++ b/apps/files/tests/backgroundjob/DeleteOrphanedTagsJobTest.php
@@ -0,0 +1,158 @@
+<?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\Files\Tests\BackgroundJob;
+
+use OCA\Files\BackgroundJob\DeleteOrphanedTagsJob;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+
+/**
+ * Class DeleteOrphanedTagsJobTest
+ *
+ * @group DB
+ *
+ * @package Test\BackgroundJob
+ */
+class DeleteOrphanedTagsJobTest extends \Test\TestCase {
+
+ /** @var \OCP\IDBConnection */
+ protected $connection;
+
+ protected function setup() {
+ parent::setUp();
+ $this->connection = \OC::$server->getDatabaseConnection();
+ }
+
+ protected function cleanMapping($table) {
+ $query = $this->connection->getQueryBuilder();
+ $query->delete($table)->execute();
+ }
+
+ protected function getMappings($table) {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('*')
+ ->from($table);
+ $result = $query->execute();
+ $mapping = $result->fetchAll();
+ $result->closeCursor();
+
+ return $mapping;
+ }
+
+ /**
+ * Test clearing orphaned system tag mappings
+ */
+ public function testClearSystemTagMappings() {
+ $this->cleanMapping('systemtag_object_mapping');
+
+ $query = $this->connection->getQueryBuilder();
+ $query->insert('filecache')
+ ->values([
+ 'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
+ 'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
+ 'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
+ ])->execute();
+ $fileId = $query->getLastInsertId();
+
+ // Existing file
+ $query = $this->connection->getQueryBuilder();
+ $query->insert('systemtag_object_mapping')
+ ->values([
+ 'objectid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
+ 'objecttype' => $query->createNamedParameter('files'),
+ 'systemtagid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
+ ])->execute();
+
+ // Non-existing file
+ $query = $this->connection->getQueryBuilder();
+ $query->insert('systemtag_object_mapping')
+ ->values([
+ 'objectid' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
+ 'objecttype' => $query->createNamedParameter('files'),
+ 'systemtagid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
+ ])->execute();
+
+ $mapping = $this->getMappings('systemtag_object_mapping');
+ $this->assertCount(2, $mapping);
+
+ $job = new DeleteOrphanedTagsJob();
+ $this->invokePrivate($job, 'cleanSystemTags');
+
+ $mapping = $this->getMappings('systemtag_object_mapping');
+ $this->assertCount(1, $mapping);
+
+ $query = $this->connection->getQueryBuilder();
+ $query->delete('filecache')
+ ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
+ ->execute();
+ $this->cleanMapping('systemtag_object_mapping');
+ }
+
+ /**
+ * Test clearing orphaned system tag mappings
+ */
+ public function testClearUserTagMappings() {
+ $this->cleanMapping('vcategory_to_object');
+
+ $query = $this->connection->getQueryBuilder();
+ $query->insert('filecache')
+ ->values([
+ 'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
+ 'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
+ 'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
+ ])->execute();
+ $fileId = $query->getLastInsertId();
+
+ // Existing file
+ $query = $this->connection->getQueryBuilder();
+ $query->insert('vcategory_to_object')
+ ->values([
+ 'objid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
+ 'type' => $query->createNamedParameter('files'),
+ 'categoryid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
+ ])->execute();
+
+ // Non-existing file
+ $query = $this->connection->getQueryBuilder();
+ $query->insert('vcategory_to_object')
+ ->values([
+ 'objid' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
+ 'type' => $query->createNamedParameter('files'),
+ 'categoryid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
+ ])->execute();
+
+ $mapping = $this->getMappings('vcategory_to_object');
+ $this->assertCount(2, $mapping);
+
+ $job = new DeleteOrphanedTagsJob();
+ $this->invokePrivate($job, 'cleanUserTags');
+
+ $mapping = $this->getMappings('vcategory_to_object');
+ $this->assertCount(1, $mapping);
+
+ $query = $this->connection->getQueryBuilder();
+ $query->delete('filecache')
+ ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
+ ->execute();
+ $this->cleanMapping('vcategory_to_object');
+ }
+
+}
diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php
index 1571178596b..1bf258c48b4 100644
--- a/apps/files_external/appinfo/application.php
+++ b/apps/files_external/appinfo/application.php
@@ -109,6 +109,7 @@ class Application extends App {
$container->query('OCA\Files_External\Lib\Auth\Password\Password'),
$container->query('OCA\Files_External\Lib\Auth\Password\SessionCredentials'),
$container->query('OCA\Files_External\Lib\Auth\Password\LoginCredentials'),
+ $container->query('OCA\Files_External\Lib\Auth\Password\UserProvided'),
// AuthMechanism::SCHEME_OAUTH1 mechanisms
$container->query('OCA\Files_External\Lib\Auth\OAuth1\OAuth1'),
diff --git a/apps/files_external/appinfo/register_command.php b/apps/files_external/appinfo/register_command.php
index be32cd410f8..d85906e3831 100644
--- a/apps/files_external/appinfo/register_command.php
+++ b/apps/files_external/appinfo/register_command.php
@@ -25,6 +25,7 @@ use OCA\Files_External\Command\Config;
use OCA\Files_External\Command\Option;
use OCA\Files_External\Command\Import;
use OCA\Files_External\Command\Export;
+use OCA\Files_External\Command\Delete;
$userManager = OC::$server->getUserManager();
$userSession = OC::$server->getUserSession();
@@ -42,3 +43,4 @@ $application->add(new Config($globalStorageService));
$application->add(new Option($globalStorageService));
$application->add(new Import($globalStorageService, $userStorageService, $userSession, $userManager, $importLegacyStorageService, $backendService));
$application->add(new Export($globalStorageService, $userStorageService, $userSession, $userManager));
+$application->add(new Delete($globalStorageService, $userStorageService, $userSession, $userManager));
diff --git a/apps/files_external/command/delete.php b/apps/files_external/command/delete.php
new file mode 100644
index 00000000000..bdbfcf8bb55
--- /dev/null
+++ b/apps/files_external/command/delete.php
@@ -0,0 +1,114 @@
+<?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\StorageConfig;
+use OCA\Files_external\NotFoundException;
+use OCA\Files_external\Service\GlobalStoragesService;
+use OCA\Files_external\Service\UserStoragesService;
+use OCP\IUserManager;
+use OCP\IUserSession;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Helper\TableHelper;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+
+class Delete extends Base {
+ /**
+ * @var GlobalStoragesService
+ */
+ protected $globalService;
+
+ /**
+ * @var UserStoragesService
+ */
+ protected $userService;
+
+ /**
+ * @var IUserSession
+ */
+ protected $userSession;
+
+ /**
+ * @var IUserManager
+ */
+ protected $userManager;
+
+ function __construct(GlobalStoragesService $globalService, UserStoragesService $userService, IUserSession $userSession, IUserManager $userManager) {
+ parent::__construct();
+ $this->globalService = $globalService;
+ $this->userService = $userService;
+ $this->userSession = $userSession;
+ $this->userManager = $userManager;
+ }
+
+ protected function configure() {
+ $this
+ ->setName('files_external:delete')
+ ->setDescription('Delete an external mount')
+ ->addArgument(
+ 'mount_id',
+ InputArgument::REQUIRED,
+ 'The id of the mount to edit'
+ )->addOption(
+ 'yes',
+ 'y',
+ InputOption::VALUE_NONE,
+ 'Skip confirmation'
+ );
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $mountId = $input->getArgument('mount_id');
+ 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;
+ }
+
+ $noConfirm = $input->getOption('yes');
+
+ if (!$noConfirm) {
+ $listCommand = new ListCommand($this->globalService, $this->userService, $this->userSession, $this->userManager);
+ $listInput = new ArrayInput([], $listCommand->getDefinition());
+ $listInput->setOption('output', $input->getOption('output'));
+ $listCommand->listMounts(null, [$mount], $listInput, $output);
+
+ $questionHelper = $this->getHelper('question');
+ $question = new ConfirmationQuestion('Delete this mount? [y/N] ', false);
+
+ if (!$questionHelper->ask($input, $output, $question)) {
+ return;
+ }
+ }
+
+ $this->globalService->removeStorage($mountId);
+ }
+}
diff --git a/apps/files_external/controller/globalstoragescontroller.php b/apps/files_external/controller/globalstoragescontroller.php
index 64bbf0feb3f..069e41a96b8 100644
--- a/apps/files_external/controller/globalstoragescontroller.php
+++ b/apps/files_external/controller/globalstoragescontroller.php
@@ -24,6 +24,7 @@ namespace OCA\Files_External\Controller;
use \OCP\IConfig;
+use OCP\ILogger;
use \OCP\IUserSession;
use \OCP\IRequest;
use \OCP\IL10N;
@@ -46,18 +47,21 @@ class GlobalStoragesController extends StoragesController {
* @param IRequest $request request object
* @param IL10N $l10n l10n service
* @param GlobalStoragesService $globalStoragesService storage service
+ * @param ILogger $logger
*/
public function __construct(
$AppName,
IRequest $request,
IL10N $l10n,
- GlobalStoragesService $globalStoragesService
+ GlobalStoragesService $globalStoragesService,
+ ILogger $logger
) {
parent::__construct(
$AppName,
$request,
$l10n,
- $globalStoragesService
+ $globalStoragesService,
+ $logger
);
}
diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php
index 64b989f0c77..db1cdeb23b9 100644
--- a/apps/files_external/controller/storagescontroller.php
+++ b/apps/files_external/controller/storagescontroller.php
@@ -25,6 +25,8 @@ namespace OCA\Files_External\Controller;
use \OCP\IConfig;
+use OCP\ILogger;
+use OCP\IUser;
use \OCP\IUserSession;
use \OCP\IRequest;
use \OCP\IL10N;
@@ -60,22 +62,30 @@ abstract class StoragesController extends Controller {
protected $service;
/**
+ * @var ILogger
+ */
+ protected $logger;
+
+ /**
* Creates a new storages controller.
*
* @param string $AppName application name
* @param IRequest $request request object
* @param IL10N $l10n l10n service
* @param StoragesService $storagesService storage service
+ * @param ILogger $logger
*/
public function __construct(
$AppName,
IRequest $request,
IL10N $l10n,
- StoragesService $storagesService
+ StoragesService $storagesService,
+ ILogger $logger
) {
parent::__construct($AppName, $request);
$this->l10n = $l10n;
$this->service = $storagesService;
+ $this->logger = $logger;
}
/**
@@ -114,6 +124,7 @@ abstract class StoragesController extends Controller {
$priority
);
} catch (\InvalidArgumentException $e) {
+ $this->logger->logException($e);
return new DataResponse(
[
'message' => (string)$this->l10n->t('Invalid backend or authentication mechanism class')
@@ -127,7 +138,7 @@ abstract class StoragesController extends Controller {
* Validate storage config
*
* @param StorageConfig $storage storage config
- *
+ *1
* @return DataResponse|null returns response in case of validation error
*/
protected function validate(StorageConfig $storage) {
@@ -268,7 +279,7 @@ abstract class StoragesController extends Controller {
* @return DataResponse
*/
public function index() {
- $storages = $this->service->getAllStorages();
+ $storages = $this->service->getStorages();
return new DataResponse(
$storages,
diff --git a/apps/files_external/controller/userglobalstoragescontroller.php b/apps/files_external/controller/userglobalstoragescontroller.php
index 6d4548754df..c6b51d94047 100644
--- a/apps/files_external/controller/userglobalstoragescontroller.php
+++ b/apps/files_external/controller/userglobalstoragescontroller.php
@@ -22,10 +22,12 @@
namespace OCA\Files_External\Controller;
use OCA\Files_External\Lib\Auth\AuthMechanism;
+use OCA\Files_External\Lib\Auth\IUserProvided;
+use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
+use OCP\ILogger;
use \OCP\IRequest;
use \OCP\IL10N;
use \OCP\AppFramework\Http\DataResponse;
-use \OCP\AppFramework\Controller;
use \OCP\AppFramework\Http;
use \OCA\Files_external\Service\UserGlobalStoragesService;
use \OCA\Files_external\NotFoundException;
@@ -56,13 +58,15 @@ class UserGlobalStoragesController extends StoragesController {
IRequest $request,
IL10N $l10n,
UserGlobalStoragesService $userGlobalStoragesService,
- IUserSession $userSession
+ IUserSession $userSession,
+ ILogger $logger
) {
parent::__construct(
$AppName,
$request,
$l10n,
- $userGlobalStoragesService
+ $userGlobalStoragesService,
+ $logger
);
$this->userSession = $userSession;
}
@@ -128,6 +132,54 @@ class UserGlobalStoragesController extends StoragesController {
}
/**
+ * Update an external storage entry.
+ * Only allows setting user provided backend fields
+ *
+ * @param int $id storage id
+ * @param array $backendOptions backend-specific options
+ *
+ * @return DataResponse
+ *
+ * @NoAdminRequired
+ */
+ public function update(
+ $id,
+ $backendOptions
+ ) {
+ try {
+ $storage = $this->service->getStorage($id);
+ $authMechanism = $storage->getAuthMechanism();
+ if ($authMechanism instanceof IUserProvided) {
+ $authMechanism->saveBackendOptions($this->userSession->getUser(), $id, $backendOptions);
+ $authMechanism->manipulateStorageConfig($storage, $this->userSession->getUser());
+ } else {
+ return new DataResponse(
+ [
+ 'message' => (string)$this->l10n->t('Storage with id "%i" is not user editable', array($id))
+ ],
+ Http::STATUS_FORBIDDEN
+ );
+ }
+ } catch (NotFoundException $e) {
+ return new DataResponse(
+ [
+ 'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ $this->updateStorageStatus($storage);
+ $this->sanitizeStorage($storage);
+
+ return new DataResponse(
+ $storage,
+ Http::STATUS_OK
+ );
+
+ }
+
+ /**
* Remove sensitive data from a StorageConfig before returning it to the user
*
* @param StorageConfig $storage
@@ -135,6 +187,14 @@ class UserGlobalStoragesController extends StoragesController {
protected function sanitizeStorage(StorageConfig $storage) {
$storage->setBackendOptions([]);
$storage->setMountOptions([]);
+
+ if ($storage->getAuthMechanism() instanceof IUserProvided) {
+ try {
+ $storage->getAuthMechanism()->manipulateStorageConfig($storage, $this->userSession->getUser());
+ } catch (InsufficientDataForMeaningfulAnswerException $e) {
+ // not configured yet
+ }
+ }
}
}
diff --git a/apps/files_external/controller/userstoragescontroller.php b/apps/files_external/controller/userstoragescontroller.php
index 741e906dec1..2a2a0bc63a6 100644
--- a/apps/files_external/controller/userstoragescontroller.php
+++ b/apps/files_external/controller/userstoragescontroller.php
@@ -25,6 +25,8 @@ namespace OCA\Files_External\Controller;
use OCA\Files_External\Lib\Auth\AuthMechanism;
use \OCP\IConfig;
+use OCP\ILogger;
+use OCP\IUser;
use \OCP\IUserSession;
use \OCP\IRequest;
use \OCP\IL10N;
@@ -54,19 +56,22 @@ class UserStoragesController extends StoragesController {
* @param IL10N $l10n l10n service
* @param UserStoragesService $userStoragesService storage service
* @param IUserSession $userSession
+ * @param ILogger $logger
*/
public function __construct(
$AppName,
IRequest $request,
IL10N $l10n,
UserStoragesService $userStoragesService,
- IUserSession $userSession
+ IUserSession $userSession,
+ ILogger $logger
) {
parent::__construct(
$AppName,
$request,
$l10n,
- $userStoragesService
+ $userStoragesService,
+ $logger
);
$this->userSession = $userSession;
}
diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js
index 2f879e1c850..ccb1e858fa0 100644
--- a/apps/files_external/js/settings.js
+++ b/apps/files_external/js/settings.js
@@ -59,10 +59,24 @@ function highlightBorder($element, highlight) {
return highlight;
}
+function isInputValid($input) {
+ var optional = $input.hasClass('optional');
+ switch ($input.attr('type')) {
+ case 'text':
+ case 'password':
+ if ($input.val() === '' && !optional) {
+ return false;
+ }
+ break;
+ }
+ return true;
+}
+
function highlightInput($input) {
- if ($input.attr('type') === 'text' || $input.attr('type') === 'password') {
- return highlightBorder($input,
- ($input.val() === '' && !$input.hasClass('optional')));
+ switch ($input.attr('type')) {
+ case 'text':
+ case 'password':
+ return highlightBorder($input, !isInputValid($input));
}
}
@@ -192,6 +206,12 @@ StorageConfig.Status = {
ERROR: 1,
INDETERMINATE: 2
};
+StorageConfig.Visibility = {
+ NONE: 0,
+ PERSONAL: 1,
+ ADMIN: 2,
+ DEFAULT: 3
+};
/**
* @memberof OCA.External.Settings
*/
@@ -343,6 +363,9 @@ StorageConfig.prototype = {
if (this.mountPoint === '') {
return false;
}
+ if (!this.backend) {
+ return false;
+ }
if (this.errors) {
return false;
}
@@ -419,6 +442,21 @@ UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
});
/**
+ * @class OCA.External.Settings.UserGlobalStorageConfig
+ * @augments OCA.External.Settings.StorageConfig
+ *
+ * @classdesc User external storage config
+ */
+var UserGlobalStorageConfig = function (id) {
+ this.id = id;
+};
+UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
+ /** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
+
+ _url: 'apps/files_external/userglobalstorages'
+});
+
+/**
* @class OCA.External.Settings.MountOptionsDropdown
*
* @classdesc Dropdown for mount options
@@ -734,7 +772,7 @@ MountConfigListView.prototype = _.extend({
$.each(authMechanismConfiguration['configuration'], _.partial(
this.writeParameterInput, $td, _, _, ['auth-param']
- ));
+ ).bind(this));
this.trigger('selectAuthMechanism',
$tr, authMechanism, authMechanismConfiguration['scheme'], onCompletion
@@ -756,6 +794,7 @@ MountConfigListView.prototype = _.extend({
var $tr = this.$el.find('tr#addMountPoint');
this.$el.find('tbody').append($tr.clone());
+ $tr.data('storageConfig', storageConfig);
$tr.find('td').last().attr('class', 'remove');
$tr.find('td.mountOptionsToggle').removeClass('hidden');
$tr.find('td').last().removeAttr('style');
@@ -776,8 +815,9 @@ MountConfigListView.prototype = _.extend({
$tr.find('.backend').data('identifier', backend.identifier);
var selectAuthMechanism = $('<select class="selectAuthMechanism"></select>');
+ var neededVisibility = (this._isPersonal) ? StorageConfig.Visibility.PERSONAL : StorageConfig.Visibility.ADMIN;
$.each(this._allAuthMechanisms, function(authIdentifier, authMechanism) {
- if (backend.authSchemes[authMechanism.scheme]) {
+ if (backend.authSchemes[authMechanism.scheme] && (authMechanism.visibility & neededVisibility)) {
selectAuthMechanism.append(
$('<option value="'+authMechanism.identifier+'" data-scheme="'+authMechanism.scheme+'">'+authMechanism.name+'</option>')
);
@@ -791,7 +831,7 @@ MountConfigListView.prototype = _.extend({
$tr.find('td.authentication').append(selectAuthMechanism);
var $td = $tr.find('td.configuration');
- $.each(backend.configuration, _.partial(this.writeParameterInput, $td));
+ $.each(backend.configuration, _.partial(this.writeParameterInput, $td).bind(this));
this.trigger('selectBackend', $tr, backend.identifier, onCompletion);
this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion);
@@ -852,8 +892,14 @@ MountConfigListView.prototype = _.extend({
success: function(result) {
var onCompletion = jQuery.Deferred();
$.each(result, function(i, storageParams) {
+ var storageConfig;
+ var isUserGlobal = storageParams.type === 'system' && self._isPersonal;
storageParams.mountPoint = storageParams.mountPoint.substr(1); // trim leading slash
- var storageConfig = new self._storageConfigClass();
+ if (isUserGlobal) {
+ storageConfig = new UserGlobalStorageConfig();
+ } else {
+ storageConfig = new self._storageConfigClass();
+ }
_.extend(storageConfig, storageParams);
var $tr = self.newStorage(storageConfig, onCompletion);
@@ -864,12 +910,16 @@ MountConfigListView.prototype = _.extend({
var $authentication = $tr.find('.authentication');
$authentication.text($authentication.find('select option:selected').text());
- // userglobal storages do not expose configuration data
- $tr.find('.configuration').text(t('files_external', 'Admin defined'));
-
// disable any other inputs
$tr.find('.mountOptionsToggle, .remove').empty();
- $tr.find('input, select, button').attr('disabled', 'disabled');
+ $tr.find('input:not(.user_provided), select:not(.user_provided)').attr('disabled', 'disabled');
+
+ if (isUserGlobal) {
+ $tr.find('.configuration').find(':not(.user_provided)').remove();
+ } else {
+ // userglobal storages do not expose configuration data
+ $tr.find('.configuration').text(t('files_external', 'Admin defined'));
+ }
});
onCompletion.resolve();
}
@@ -904,22 +954,40 @@ MountConfigListView.prototype = _.extend({
* @return {jQuery} newly created input
*/
writeParameterInput: function($td, parameter, placeholder, classes) {
+ var hasFlag = function(flag) {
+ return placeholder.indexOf(flag) !== -1;
+ };
classes = $.isArray(classes) ? classes : [];
classes.push('added');
if (placeholder.indexOf('&') === 0) {
classes.push('optional');
placeholder = placeholder.substring(1);
}
+
+ if (hasFlag('@')) {
+ if (this._isPersonal) {
+ classes.push('user_provided');
+ } else {
+ return;
+ }
+ }
+
var newElement;
- if (placeholder.indexOf('*') === 0) {
- newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
- } else if (placeholder.indexOf('!') === 0) {
+
+ var trimmedPlaceholder = placeholder;
+ var flags = ['@', '*', '!', '#', '&']; // used to determine what kind of parameter
+ while(flags.indexOf(trimmedPlaceholder[0]) !== -1) {
+ trimmedPlaceholder = trimmedPlaceholder.substr(1);
+ }
+ if (hasFlag('*')) {
+ newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
+ } else if (hasFlag('!')) {
var checkboxId = _.uniqueId('checkbox_');
- newElement = $('<input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" /><label for="'+checkboxId+'">'+placeholder.substring(1)+'</label>');
- } else if (placeholder.indexOf('#') === 0) {
+ newElement = $('<input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" /><label for="'+checkboxId+'">'+ trimmedPlaceholder+'</label>');
+ } else if (hasFlag('#')) {
newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />');
} else {
- newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
+ newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
}
highlightInput(newElement);
$td.append(newElement);
@@ -938,7 +1006,12 @@ MountConfigListView.prototype = _.extend({
// new entry
storageId = null;
}
- var storage = new this._storageConfigClass(storageId);
+
+ var storage = $tr.data('storageConfig');
+ if (!storage) {
+ storage = new this._storageConfigClass(storageId);
+ }
+ storage.errors = null;
storage.mountPoint = $tr.find('.mountPoint input').val();
storage.backend = $tr.find('.backend').data('identifier');
storage.authMechanism = $tr.find('.selectAuthMechanism').val();
@@ -952,7 +1025,7 @@ MountConfigListView.prototype = _.extend({
if ($input.attr('type') === 'button') {
return;
}
- if ($input.val() === '' && !$input.hasClass('optional')) {
+ if (!isInputValid($input) && !$input.hasClass('optional')) {
missingOptions.push(parameter);
return;
}
@@ -980,7 +1053,7 @@ MountConfigListView.prototype = _.extend({
var users = [];
var multiselect = getSelection($tr);
$.each(multiselect, function(index, value) {
- var pos = value.indexOf('(group)');
+ var pos = (value.indexOf)?value.indexOf('(group)'): -1;
if (pos !== -1) {
groups.push(value.substr(0, pos));
} else {
@@ -1043,7 +1116,7 @@ MountConfigListView.prototype = _.extend({
saveStorageConfig:function($tr, callback, concurrentTimer) {
var self = this;
var storage = this.getStorageConfig($tr);
- if (!storage.validate()) {
+ if (!storage || !storage.validate()) {
return false;
}
diff --git a/apps/files_external/l10n/cs_CZ.js b/apps/files_external/l10n/cs_CZ.js
index e9a24ba73f8..5ac3b82d67f 100644
--- a/apps/files_external/l10n/cs_CZ.js
+++ b/apps/files_external/l10n/cs_CZ.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "Neuspokojivé parametry ověřovacího mechanismu",
"Insufficient data: %s" : "Nedostatečná data: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Úložiště s id \"%i\" uživatelé nemohou upravovat",
"Personal" : "Osobní",
"System" : "Systém",
"Grant access" : "Povolit přístup",
@@ -63,6 +64,7 @@ OC.L10N.register(
"Login credentials" : "Přihlašovací údaje",
"Username and password" : "Uživatelské jméno a heslo",
"Session credentials" : "Přihlašovací údaje sezení",
+ "User provided" : "Poskytnuto uživatelem",
"RSA public key" : "RSA veřejný klíč",
"Public key" : "Veřejný klíč",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/cs_CZ.json b/apps/files_external/l10n/cs_CZ.json
index aa991682b7d..a21b78f02c7 100644
--- a/apps/files_external/l10n/cs_CZ.json
+++ b/apps/files_external/l10n/cs_CZ.json
@@ -16,6 +16,7 @@
"Unsatisfied authentication mechanism parameters" : "Neuspokojivé parametry ověřovacího mechanismu",
"Insufficient data: %s" : "Nedostatečná data: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Úložiště s id \"%i\" uživatelé nemohou upravovat",
"Personal" : "Osobní",
"System" : "Systém",
"Grant access" : "Povolit přístup",
@@ -61,6 +62,7 @@
"Login credentials" : "Přihlašovací údaje",
"Username and password" : "Uživatelské jméno a heslo",
"Session credentials" : "Přihlašovací údaje sezení",
+ "User provided" : "Poskytnuto uživatelem",
"RSA public key" : "RSA veřejný klíč",
"Public key" : "Veřejný klíč",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/de_DE.js b/apps/files_external/l10n/de_DE.js
index f3cbcd2be6e..ef860918474 100644
--- a/apps/files_external/l10n/de_DE.js
+++ b/apps/files_external/l10n/de_DE.js
@@ -73,6 +73,7 @@ OC.L10N.register(
"Scope" : "Anwendungsbereich",
"External Storage" : "Externer Speicher",
"Folder name" : "Ordnername",
+ "Authentication" : "Authentifizierung",
"Configuration" : "Konfiguration",
"Available for" : "Verfügbar für",
"Add storage" : "Speicher hinzufügen",
diff --git a/apps/files_external/l10n/de_DE.json b/apps/files_external/l10n/de_DE.json
index d366b1f0d9f..75a84334764 100644
--- a/apps/files_external/l10n/de_DE.json
+++ b/apps/files_external/l10n/de_DE.json
@@ -71,6 +71,7 @@
"Scope" : "Anwendungsbereich",
"External Storage" : "Externer Speicher",
"Folder name" : "Ordnername",
+ "Authentication" : "Authentifizierung",
"Configuration" : "Konfiguration",
"Available for" : "Verfügbar für",
"Add storage" : "Speicher hinzufügen",
diff --git a/apps/files_external/l10n/fi_FI.js b/apps/files_external/l10n/fi_FI.js
index f7ec6fea3b3..23b9227348c 100644
--- a/apps/files_external/l10n/fi_FI.js
+++ b/apps/files_external/l10n/fi_FI.js
@@ -45,6 +45,7 @@ OC.L10N.register(
"Password" : "Salasana",
"Rackspace" : "Rackspace",
"API key" : "API-avain",
+ "Login credentials" : "Kirjautumistiedot",
"Username and password" : "Käyttäjätunnus ja salasana",
"Session credentials" : "Istunnon tunnistetiedot",
"RSA public key" : "Julkinen RSA-avain",
diff --git a/apps/files_external/l10n/fi_FI.json b/apps/files_external/l10n/fi_FI.json
index 368121feae8..2c778205e10 100644
--- a/apps/files_external/l10n/fi_FI.json
+++ b/apps/files_external/l10n/fi_FI.json
@@ -43,6 +43,7 @@
"Password" : "Salasana",
"Rackspace" : "Rackspace",
"API key" : "API-avain",
+ "Login credentials" : "Kirjautumistiedot",
"Username and password" : "Käyttäjätunnus ja salasana",
"Session credentials" : "Istunnon tunnistetiedot",
"RSA public key" : "Julkinen RSA-avain",
diff --git a/apps/files_external/l10n/he.js b/apps/files_external/l10n/he.js
index 3da4dd67776..83ff5a524d0 100644
--- a/apps/files_external/l10n/he.js
+++ b/apps/files_external/l10n/he.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "פרמטרים של מכניזם אימות אינם מספקים",
"Insufficient data: %s" : "מידע לא מספק: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "האחסון עם זהות \"%i\" לא ניתן לעריכה",
"Personal" : "אישי",
"System" : "מערכת",
"Grant access" : "הענקת גישה",
@@ -63,6 +64,7 @@ OC.L10N.register(
"Login credentials" : "פרטי הכניסה",
"Username and password" : "שם משתמש וסיסמא",
"Session credentials" : "אישורי סשן",
+ "User provided" : "סופק על ידי משתמש",
"RSA public key" : "מפתח ציבורי RSA",
"Public key" : "מפתח ציבורי",
"Amazon S3" : "אמזון S3",
diff --git a/apps/files_external/l10n/he.json b/apps/files_external/l10n/he.json
index 9666346681c..71e07d5f6af 100644
--- a/apps/files_external/l10n/he.json
+++ b/apps/files_external/l10n/he.json
@@ -16,6 +16,7 @@
"Unsatisfied authentication mechanism parameters" : "פרמטרים של מכניזם אימות אינם מספקים",
"Insufficient data: %s" : "מידע לא מספק: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "האחסון עם זהות \"%i\" לא ניתן לעריכה",
"Personal" : "אישי",
"System" : "מערכת",
"Grant access" : "הענקת גישה",
@@ -61,6 +62,7 @@
"Login credentials" : "פרטי הכניסה",
"Username and password" : "שם משתמש וסיסמא",
"Session credentials" : "אישורי סשן",
+ "User provided" : "סופק על ידי משתמש",
"RSA public key" : "מפתח ציבורי RSA",
"Public key" : "מפתח ציבורי",
"Amazon S3" : "אמזון S3",
diff --git a/apps/files_external/l10n/it.js b/apps/files_external/l10n/it.js
index 54192813b2a..c27a92d76f4 100644
--- a/apps/files_external/l10n/it.js
+++ b/apps/files_external/l10n/it.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "Parametri del meccanismo di autenticazione non soddisfatti",
"Insufficient data: %s" : "Dati insufficienti: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "L'archiviazione con ID \"%i\" non è modificabile dall'utente",
"Personal" : "Personale",
"System" : "Sistema",
"Grant access" : "Concedi l'accesso",
@@ -63,6 +64,7 @@ OC.L10N.register(
"Login credentials" : "Credenziali di accesso",
"Username and password" : "Nome utente e password",
"Session credentials" : "Credenziali di sessione",
+ "User provided" : "Fornita dall'utente",
"RSA public key" : "Chiave pubblica RSA",
"Public key" : "Chiave pubblica",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/it.json b/apps/files_external/l10n/it.json
index 3bc5ee133fc..cf7775f1d0e 100644
--- a/apps/files_external/l10n/it.json
+++ b/apps/files_external/l10n/it.json
@@ -16,6 +16,7 @@
"Unsatisfied authentication mechanism parameters" : "Parametri del meccanismo di autenticazione non soddisfatti",
"Insufficient data: %s" : "Dati insufficienti: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "L'archiviazione con ID \"%i\" non è modificabile dall'utente",
"Personal" : "Personale",
"System" : "Sistema",
"Grant access" : "Concedi l'accesso",
@@ -61,6 +62,7 @@
"Login credentials" : "Credenziali di accesso",
"Username and password" : "Nome utente e password",
"Session credentials" : "Credenziali di sessione",
+ "User provided" : "Fornita dall'utente",
"RSA public key" : "Chiave pubblica RSA",
"Public key" : "Chiave pubblica",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/ja.js b/apps/files_external/l10n/ja.js
index 1deff5971a9..fafe03c4fa0 100644
--- a/apps/files_external/l10n/ja.js
+++ b/apps/files_external/l10n/ja.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "認証のためのパラメータが不十分です",
"Insufficient data: %s" : "データが不足しています: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "ストレージID \"%i\" はユーザーが編集できません",
"Personal" : "個人",
"System" : "システム",
"Grant access" : "アクセスを許可",
@@ -60,6 +61,7 @@ OC.L10N.register(
"Identity endpoint URL" : "認証エンドポイントURL",
"Rackspace" : "Rackspace",
"API key" : "APIキー",
+ "Login credentials" : "ログイン資格情報",
"Username and password" : "ユーザー名とパスワード",
"Session credentials" : "セッション資格情報",
"RSA public key" : "RSA公開鍵",
diff --git a/apps/files_external/l10n/ja.json b/apps/files_external/l10n/ja.json
index 744a63c929b..0d1f1729dae 100644
--- a/apps/files_external/l10n/ja.json
+++ b/apps/files_external/l10n/ja.json
@@ -16,6 +16,7 @@
"Unsatisfied authentication mechanism parameters" : "認証のためのパラメータが不十分です",
"Insufficient data: %s" : "データが不足しています: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "ストレージID \"%i\" はユーザーが編集できません",
"Personal" : "個人",
"System" : "システム",
"Grant access" : "アクセスを許可",
@@ -58,6 +59,7 @@
"Identity endpoint URL" : "認証エンドポイントURL",
"Rackspace" : "Rackspace",
"API key" : "APIキー",
+ "Login credentials" : "ログイン資格情報",
"Username and password" : "ユーザー名とパスワード",
"Session credentials" : "セッション資格情報",
"RSA public key" : "RSA公開鍵",
diff --git a/apps/files_external/l10n/nl.js b/apps/files_external/l10n/nl.js
index adc2302d9c6..a6dd5164992 100644
--- a/apps/files_external/l10n/nl.js
+++ b/apps/files_external/l10n/nl.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "Onvoldoende authenticatiemechanisme parameters",
"Insufficient data: %s" : "Onvoldoende gegevens: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Opslag met id \"%i\" is niet te bewerken door gebruiker",
"Personal" : "Persoonlijk",
"System" : "Systeem",
"Grant access" : "Sta toegang toe",
@@ -63,6 +64,7 @@ OC.L10N.register(
"Login credentials" : "Inloggegevens",
"Username and password" : "Gebruikersnaam en wachtwoord",
"Session credentials" : "Sessie inloggegevens",
+ "User provided" : "Gebruiker gaf op",
"RSA public key" : "RSA publieke sleutel",
"Public key" : "Publieke sleutel",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/nl.json b/apps/files_external/l10n/nl.json
index da7353a4bde..03b8953a62b 100644
--- a/apps/files_external/l10n/nl.json
+++ b/apps/files_external/l10n/nl.json
@@ -16,6 +16,7 @@
"Unsatisfied authentication mechanism parameters" : "Onvoldoende authenticatiemechanisme parameters",
"Insufficient data: %s" : "Onvoldoende gegevens: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Opslag met id \"%i\" is niet te bewerken door gebruiker",
"Personal" : "Persoonlijk",
"System" : "Systeem",
"Grant access" : "Sta toegang toe",
@@ -61,6 +62,7 @@
"Login credentials" : "Inloggegevens",
"Username and password" : "Gebruikersnaam en wachtwoord",
"Session credentials" : "Sessie inloggegevens",
+ "User provided" : "Gebruiker gaf op",
"RSA public key" : "RSA publieke sleutel",
"Public key" : "Publieke sleutel",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/pt_BR.js b/apps/files_external/l10n/pt_BR.js
index 46b10051238..4a301a306ab 100644
--- a/apps/files_external/l10n/pt_BR.js
+++ b/apps/files_external/l10n/pt_BR.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "Parâmetros de mecanismos de autenticação não satisfeitos",
"Insufficient data: %s" : "Dados insuficientes: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Armazenamento com ID \"%i\" não é editável pelo usuário",
"Personal" : "Pessoal",
"System" : "Sistema",
"Grant access" : "Permitir acesso",
diff --git a/apps/files_external/l10n/pt_BR.json b/apps/files_external/l10n/pt_BR.json
index af71fdb1b47..26c20fcb3c1 100644
--- a/apps/files_external/l10n/pt_BR.json
+++ b/apps/files_external/l10n/pt_BR.json
@@ -16,6 +16,7 @@
"Unsatisfied authentication mechanism parameters" : "Parâmetros de mecanismos de autenticação não satisfeitos",
"Insufficient data: %s" : "Dados insuficientes: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Armazenamento com ID \"%i\" não é editável pelo usuário",
"Personal" : "Pessoal",
"System" : "Sistema",
"Grant access" : "Permitir acesso",
diff --git a/apps/files_external/l10n/pt_PT.js b/apps/files_external/l10n/pt_PT.js
index e80971ccff5..3065f6780af 100644
--- a/apps/files_external/l10n/pt_PT.js
+++ b/apps/files_external/l10n/pt_PT.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "Parâmetros do mecanismo de autenticação inválidos",
"Insufficient data: %s" : "Dados insuficientes: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Armazenamento com id \"%i\" não é editável pelo utilizador",
"Personal" : "Pessoal",
"System" : "Sistema",
"Grant access" : "Conceder acesso",
@@ -63,6 +64,7 @@ OC.L10N.register(
"Login credentials" : "Credenciais de login",
"Username and password" : "Nome de utilizador e palavra-passe",
"Session credentials" : "Credenciais da sessão",
+ "User provided" : "Utilizador fornecido",
"RSA public key" : "Chave pública RSA",
"Public key" : "Chave pública",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/pt_PT.json b/apps/files_external/l10n/pt_PT.json
index c2a4dd9848d..2525f001dea 100644
--- a/apps/files_external/l10n/pt_PT.json
+++ b/apps/files_external/l10n/pt_PT.json
@@ -16,6 +16,7 @@
"Unsatisfied authentication mechanism parameters" : "Parâmetros do mecanismo de autenticação inválidos",
"Insufficient data: %s" : "Dados insuficientes: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Armazenamento com id \"%i\" não é editável pelo utilizador",
"Personal" : "Pessoal",
"System" : "Sistema",
"Grant access" : "Conceder acesso",
@@ -61,6 +62,7 @@
"Login credentials" : "Credenciais de login",
"Username and password" : "Nome de utilizador e palavra-passe",
"Session credentials" : "Credenciais da sessão",
+ "User provided" : "Utilizador fornecido",
"RSA public key" : "Chave pública RSA",
"Public key" : "Chave pública",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/sq.js b/apps/files_external/l10n/sq.js
index da6ccf917f5..96438f293bd 100644
--- a/apps/files_external/l10n/sq.js
+++ b/apps/files_external/l10n/sq.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "Parametra mekanizmi mirëfilltësimi të papërmbushur",
"Insufficient data: %s" : "Të dhëna të pamjaftueshme: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Depozita me id \"%i\" s’është e përpunueshme nga përdoruesi",
"Personal" : "Personale",
"System" : "Sistem",
"Grant access" : "Akordoji hyrje",
@@ -62,6 +63,7 @@ OC.L10N.register(
"Login credentials" : "Kredenciale hyrjesh",
"Username and password" : "Emër përdoruesi dhe fjalëkalim",
"Session credentials" : "Kredenciale sesioni",
+ "User provided" : "Dhënë nga përdoruesi",
"RSA public key" : "Kyç publik RSA ",
"Public key" : "Kyç publik",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/l10n/sq.json b/apps/files_external/l10n/sq.json
index 068dfd351b5..e3e21598d7a 100644
--- a/apps/files_external/l10n/sq.json
+++ b/apps/files_external/l10n/sq.json
@@ -16,6 +16,7 @@
"Unsatisfied authentication mechanism parameters" : "Parametra mekanizmi mirëfilltësimi të papërmbushur",
"Insufficient data: %s" : "Të dhëna të pamjaftueshme: %s",
"%s" : "%s",
+ "Storage with id \"%i\" is not user editable" : "Depozita me id \"%i\" s’është e përpunueshme nga përdoruesi",
"Personal" : "Personale",
"System" : "Sistem",
"Grant access" : "Akordoji hyrje",
@@ -60,6 +61,7 @@
"Login credentials" : "Kredenciale hyrjesh",
"Username and password" : "Emër përdoruesi dhe fjalëkalim",
"Session credentials" : "Kredenciale sesioni",
+ "User provided" : "Dhënë nga përdoruesi",
"RSA public key" : "Kyç publik RSA ",
"Public key" : "Kyç publik",
"Amazon S3" : "Amazon S3",
diff --git a/apps/files_external/lib/auth/authmechanism.php b/apps/files_external/lib/auth/authmechanism.php
index 72b56e0bc06..36e55de92c5 100644
--- a/apps/files_external/lib/auth/authmechanism.php
+++ b/apps/files_external/lib/auth/authmechanism.php
@@ -95,6 +95,7 @@ class AuthMechanism implements \JsonSerializable {
$data += $this->jsonSerializeIdentifier();
$data['scheme'] = $this->getScheme();
+ $data['visibility'] = $this->getVisibility();
return $data;
}
diff --git a/apps/files_external/lib/auth/iuserprovided.php b/apps/files_external/lib/auth/iuserprovided.php
new file mode 100644
index 00000000000..6b7eab4e2a7
--- /dev/null
+++ b/apps/files_external/lib/auth/iuserprovided.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files_External\Lib\Auth;
+
+use OCP\IUser;
+
+/**
+ * For auth mechanisms where the user needs to provide credentials
+ */
+interface IUserProvided {
+ /**
+ * @param IUser $user the user for which to save the user provided options
+ * @param int $mountId the mount id to save the options for
+ * @param array $options the user provided options
+ */
+ public function saveBackendOptions(IUser $user, $mountId, array $options);
+}
diff --git a/apps/files_external/lib/auth/password/logincredentials.php b/apps/files_external/lib/auth/password/logincredentials.php
index 99cac3f4202..25bd66fb41a 100644
--- a/apps/files_external/lib/auth/password/logincredentials.php
+++ b/apps/files_external/lib/auth/password/logincredentials.php
@@ -52,7 +52,7 @@ class LoginCredentials extends AuthMechanism {
$this
->setIdentifier('password::logincredentials')
->setScheme(self::SCHEME_PASSWORD)
- ->setText($l->t('Login credentials'))
+ ->setText($l->t('Log-in credentials, save in database'))
->addParameters([
])
;
diff --git a/apps/files_external/lib/auth/password/sessioncredentials.php b/apps/files_external/lib/auth/password/sessioncredentials.php
index 3fb8b8526cc..d8e8443418a 100644
--- a/apps/files_external/lib/auth/password/sessioncredentials.php
+++ b/apps/files_external/lib/auth/password/sessioncredentials.php
@@ -50,7 +50,7 @@ class SessionCredentials extends AuthMechanism {
$this
->setIdentifier('password::sessioncredentials')
->setScheme(self::SCHEME_PASSWORD)
- ->setText($l->t('Session credentials'))
+ ->setText($l->t('Log-in credentials, save in session'))
->addParameters([
])
;
diff --git a/apps/files_external/lib/auth/password/userprovided.php b/apps/files_external/lib/auth/password/userprovided.php
new file mode 100644
index 00000000000..2f277163184
--- /dev/null
+++ b/apps/files_external/lib/auth/password/userprovided.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files_External\Lib\Auth\Password;
+
+use OCA\Files_External\Lib\Auth\IUserProvided;
+use OCA\Files_External\Lib\DefinitionParameter;
+use OCA\Files_External\Service\BackendService;
+use OCP\IL10N;
+use OCP\IUser;
+use OCA\Files_External\Lib\Auth\AuthMechanism;
+use OCA\Files_External\Lib\StorageConfig;
+use OCP\Security\ICredentialsManager;
+use OCP\Files\Storage;
+use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
+
+/**
+ * User provided Username and Password
+ */
+class UserProvided extends AuthMechanism implements IUserProvided {
+
+ const CREDENTIALS_IDENTIFIER_PREFIX = 'password::userprovided/';
+
+ /** @var ICredentialsManager */
+ protected $credentialsManager;
+
+ public function __construct(IL10N $l, ICredentialsManager $credentialsManager) {
+ $this->credentialsManager = $credentialsManager;
+
+ $this
+ ->setIdentifier('password::userprovided')
+ ->setVisibility(BackendService::VISIBILITY_ADMIN)
+ ->setScheme(self::SCHEME_PASSWORD)
+ ->setText($l->t('User entered, store in database'))
+ ->addParameters([
+ (new DefinitionParameter('user', $l->t('Username')))
+ ->setFlag(DefinitionParameter::FLAG_USER_PROVIDED),
+ (new DefinitionParameter('password', $l->t('Password')))
+ ->setType(DefinitionParameter::VALUE_PASSWORD)
+ ->setFlag(DefinitionParameter::FLAG_USER_PROVIDED),
+ ]);
+ }
+
+ private function getCredentialsIdentifier($storageId) {
+ return self::CREDENTIALS_IDENTIFIER_PREFIX . $storageId;
+ }
+
+ public function saveBackendOptions(IUser $user, $id, array $options) {
+ $this->credentialsManager->store($user->getUID(), $this->getCredentialsIdentifier($id), [
+ 'user' => $options['user'], // explicitly copy the fields we want instead of just passing the entire $options array
+ 'password' => $options['password'] // this way we prevent users from being able to modify any other field
+ ]);
+ }
+
+ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) {
+ if (!isset($user)) {
+ throw new InsufficientDataForMeaningfulAnswerException('No credentials saved');
+ }
+ $uid = $user->getUID();
+ $credentials = $this->credentialsManager->retrieve($uid, $this->getCredentialsIdentifier($storage->getId()));
+
+ if (!isset($credentials)) {
+ throw new InsufficientDataForMeaningfulAnswerException('No credentials saved');
+ }
+
+ $storage->setBackendOption('user', $credentials['user']);
+ $storage->setBackendOption('password', $credentials['password']);
+ }
+
+}
diff --git a/apps/files_external/lib/definitionparameter.php b/apps/files_external/lib/definitionparameter.php
index 59a098e6320..dc7985837f5 100644
--- a/apps/files_external/lib/definitionparameter.php
+++ b/apps/files_external/lib/definitionparameter.php
@@ -35,6 +35,7 @@ class DefinitionParameter implements \JsonSerializable {
/** Flag constants */
const FLAG_NONE = 0;
const FLAG_OPTIONAL = 1;
+ const FLAG_USER_PROVIDED = 2;
/** @var string name of parameter */
private $name;
@@ -121,7 +122,7 @@ class DefinitionParameter implements \JsonSerializable {
* @return bool
*/
public function isFlagSet($flag) {
- return (bool) $this->flags & $flag;
+ return (bool)($this->flags & $flag);
}
/**
@@ -143,15 +144,20 @@ class DefinitionParameter implements \JsonSerializable {
break;
}
- switch ($this->getFlags()) {
- case self::FLAG_OPTIONAL:
- $prefix = '&' . $prefix;
- break;
+ if ($this->isFlagSet(self::FLAG_OPTIONAL)) {
+ $prefix = '&' . $prefix;
+ }
+ if ($this->isFlagSet(self::FLAG_USER_PROVIDED)) {
+ $prefix = '@' . $prefix;
}
return $prefix . $this->getText();
}
+ public function isOptional() {
+ return $this->isFlagSet(self::FLAG_OPTIONAL) || $this->isFlagSet(self::FLAG_USER_PROVIDED);
+ }
+
/**
* Validate a parameter value against this
* Convert type as necessary
@@ -160,28 +166,26 @@ class DefinitionParameter implements \JsonSerializable {
* @return bool success
*/
public function validateValue(&$value) {
- $optional = $this->getFlags() & self::FLAG_OPTIONAL;
-
switch ($this->getType()) {
- case self::VALUE_BOOLEAN:
- if (!is_bool($value)) {
- switch ($value) {
- case 'true':
- $value = true;
- break;
- case 'false':
- $value = false;
- break;
- default:
+ case self::VALUE_BOOLEAN:
+ if (!is_bool($value)) {
+ switch ($value) {
+ case 'true':
+ $value = true;
+ break;
+ case 'false':
+ $value = false;
+ break;
+ default:
+ return false;
+ }
+ }
+ break;
+ default:
+ if (!$value && !$this->isOptional()) {
return false;
}
- }
- break;
- default:
- if (!$value && !$optional) {
- return false;
- }
- break;
+ break;
}
return true;
}
diff --git a/apps/files_external/lib/failedcache.php b/apps/files_external/lib/failedcache.php
new file mode 100644
index 00000000000..9e24c12f4b5
--- /dev/null
+++ b/apps/files_external/lib/failedcache.php
@@ -0,0 +1,126 @@
+<?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\Lib;
+
+use OC\Files\Cache\CacheEntry;
+use OCP\Files\Cache\ICache;
+
+/**
+ * Storage placeholder to represent a missing precondition, storage unavailable
+ */
+class FailedCache implements ICache {
+
+ public function getNumericStorageId() {
+ return -1;
+ }
+
+ public function get($file) {
+ if ($file === '') {
+ return new CacheEntry([
+ 'fileid' => -1,
+ 'size' => 0,
+ 'mimetype' => 'httpd/unix-directory',
+ 'mimepart' => 'httpd',
+ 'permissions' => 0,
+ 'mtime' => time()
+ ]);
+ } else {
+ return false;
+ }
+ }
+
+ public function getFolderContents($folder) {
+ return [];
+ }
+
+ public function getFolderContentsById($fileId) {
+ return [];
+ }
+
+ public function put($file, array $data) {
+ return;
+ }
+
+ public function update($id, array $data) {
+ return;
+ }
+
+ public function getId($file) {
+ return -1;
+ }
+
+ public function getParentId($file) {
+ return -1;
+ }
+
+ public function inCache($file) {
+ return false;
+ }
+
+ public function remove($file) {
+ return;
+ }
+
+ public function move($source, $target) {
+ return;
+ }
+
+ public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
+ return;
+ }
+
+ public function clear() {
+ return;
+ }
+
+ public function getStatus($file) {
+ return ICache::NOT_FOUND;
+ }
+
+ public function search($pattern) {
+ return [];
+ }
+
+ public function searchByMime($mimetype) {
+ return [];
+ }
+
+ public function searchByTag($tag, $userId) {
+ return [];
+ }
+
+ public function getAll() {
+ return [];
+ }
+
+ public function getIncomplete() {
+ return [];
+ }
+
+ public function getPathById($id) {
+ return null;
+ }
+
+ public function normalize($path) {
+ return $path;
+ }
+}
diff --git a/apps/files_external/lib/failedstorage.php b/apps/files_external/lib/failedstorage.php
index 95fe179f570..7bcbfc31902 100644
--- a/apps/files_external/lib/failedstorage.php
+++ b/apps/files_external/lib/failedstorage.php
@@ -174,7 +174,7 @@ class FailedStorage extends Common {
}
public function verifyPath($path, $fileName) {
- throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
+ return true;
}
public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
@@ -205,4 +205,7 @@ class FailedStorage extends Common {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
+ public function getCache($path = '', $storage = null) {
+ return new FailedCache();
+ }
}
diff --git a/apps/files_external/lib/frontenddefinitiontrait.php b/apps/files_external/lib/frontenddefinitiontrait.php
index eedd433f2d7..fc47a9515f3 100644
--- a/apps/files_external/lib/frontenddefinitiontrait.php
+++ b/apps/files_external/lib/frontenddefinitiontrait.php
@@ -136,10 +136,12 @@ trait FrontendDefinitionTrait {
public function validateStorageDefinition(StorageConfig $storage) {
foreach ($this->getParameters() as $name => $parameter) {
$value = $storage->getBackendOption($name);
- if (!$parameter->validateValue($value)) {
- return false;
+ if (!is_null($value) || !$parameter->isOptional()) {
+ if (!$parameter->validateValue($value)) {
+ return false;
+ }
+ $storage->setBackendOption($name, $value);
}
- $storage->setBackendOption($name, $value);
}
return true;
}
diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php
index 33646e603c3..7f716893842 100644
--- a/apps/files_external/lib/storageconfig.php
+++ b/apps/files_external/lib/storageconfig.php
@@ -406,6 +406,7 @@ class StorageConfig implements \JsonSerializable {
if (!is_null($this->statusMessage)) {
$result['statusMessage'] = $this->statusMessage;
}
+ $result['type'] = ($this->getType() === self::MOUNT_TYPE_PERSONAl) ? 'personal': 'system';
return $result;
}
}
diff --git a/apps/files_external/tests/controller/globalstoragescontrollertest.php b/apps/files_external/tests/controller/globalstoragescontrollertest.php
index a3c911b511c..9256569f22a 100644
--- a/apps/files_external/tests/controller/globalstoragescontrollertest.php
+++ b/apps/files_external/tests/controller/globalstoragescontrollertest.php
@@ -41,7 +41,8 @@ class GlobalStoragesControllerTest extends StoragesControllerTest {
'files_external',
$this->getMock('\OCP\IRequest'),
$this->getMock('\OCP\IL10N'),
- $this->service
+ $this->service,
+ $this->getMock('\OCP\ILogger')
);
}
}
diff --git a/apps/files_external/tests/controller/userstoragescontrollertest.php b/apps/files_external/tests/controller/userstoragescontrollertest.php
index 671e019fea0..342f6b85385 100644
--- a/apps/files_external/tests/controller/userstoragescontrollertest.php
+++ b/apps/files_external/tests/controller/userstoragescontrollertest.php
@@ -49,7 +49,8 @@ class UserStoragesControllerTest extends StoragesControllerTest {
$this->getMock('\OCP\IRequest'),
$this->getMock('\OCP\IL10N'),
$this->service,
- $this->getMock('\OCP\IUserSession')
+ $this->getMock('\OCP\IUserSession'),
+ $this->getMock('\OCP\ILogger')
);
}
diff --git a/apps/files_external/tests/frontenddefinitiontraittest.php b/apps/files_external/tests/frontenddefinitiontraittest.php
index 209b1abc7e0..27f49556330 100644
--- a/apps/files_external/tests/frontenddefinitiontraittest.php
+++ b/apps/files_external/tests/frontenddefinitiontraittest.php
@@ -61,6 +61,8 @@ class FrontendDefinitionTraitTest extends \Test\TestCase {
->getMock();
$param->method('getName')
->willReturn($name);
+ $param->method('isOptional')
+ ->willReturn(false);
$param->expects($this->once())
->method('validateValue')
->willReturn($valid);
diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js
index b6372649fb8..b2b5e1f57ec 100644
--- a/apps/files_external/tests/js/settingsSpec.js
+++ b/apps/files_external/tests/js/settingsSpec.js
@@ -37,6 +37,7 @@ describe('OCA.External.Settings tests', function() {
'<option disable selected>Add storage</option>' +
'<option value="\\OC\\TestBackend">Test Backend</option>' +
'<option value="\\OC\\AnotherTestBackend">Another Test Backend</option>' +
+ '<option value="\\OC\\InputsTestBackend">Inputs test backend</option>' +
'</select>' +
'</td>' +
'<td class="authentication"></td>' +
@@ -76,6 +77,22 @@ describe('OCA.External.Settings tests', function() {
'builtin': true,
},
'priority': 12
+ },
+ '\\OC\\InputsTestBackend': {
+ 'identifier': '\\OC\\InputsTestBackend',
+ 'name': 'Inputs test backend',
+ 'configuration': {
+ 'field_text': 'Text field',
+ 'field_password': '*Password field',
+ 'field_bool': '!Boolean field',
+ 'field_hidden': '#Hidden field',
+ 'field_text_optional': '&Text field optional',
+ 'field_password_optional': '&*Password field optional'
+ },
+ 'authSchemes': {
+ 'builtin': true,
+ },
+ 'priority': 13
}
}
);
@@ -87,6 +104,7 @@ describe('OCA.External.Settings tests', function() {
'configuration': {
},
'scheme': 'builtin',
+ 'visibility': 3
},
});
@@ -190,13 +208,70 @@ describe('OCA.External.Settings tests', function() {
expect(fakeServer.requests.length).toEqual(1);
});
// TODO: tests with "applicableUsers" and "applicableGroups"
- // TODO: test with non-optional config parameters
// TODO: test with missing mount point value
// TODO: test with personal mounts (no applicable fields)
// TODO: test save triggers: paste, keyup, checkbox
// TODO: test "custom" field with addScript
// TODO: status indicator
});
+ describe('validate storage configuration', function() {
+ var $tr;
+
+ beforeEach(function() {
+ $tr = view.$el.find('tr:first');
+ selectBackend('\\OC\\InputsTestBackend');
+ });
+
+ it('lists missing fields in storage errors', function() {
+ var storage = view.getStorageConfig($tr);
+
+ expect(storage.errors).toEqual({
+ backendOptions: ['field_text', 'field_password']
+ });
+ });
+
+ it('highlights missing non-optional fields', function() {
+ _.each([
+ 'field_text',
+ 'field_password'
+ ], function(param) {
+ expect($tr.find('input[data-parameter='+param+']').hasClass('warning-input')).toBe(true);
+ });
+ _.each([
+ 'field_bool',
+ 'field_hidden',
+ 'field_text_optional',
+ 'field_password_optional'
+ ], function(param) {
+ expect($tr.find('input[data-parameter='+param+']').hasClass('warning-input')).toBe(false);
+ });
+ });
+
+ it('validates correct storage', function() {
+ $tr.find('[name=mountPoint]').val('mountpoint');
+
+ $tr.find('input[data-parameter=field_text]').val('foo');
+ $tr.find('input[data-parameter=field_password]').val('bar');
+ $tr.find('input[data-parameter=field_text_optional]').val('foobar');
+ // don't set field_password_optional
+ $tr.find('input[data-parameter=field_hidden]').val('baz');
+
+ var storage = view.getStorageConfig($tr);
+
+ expect(storage.validate()).toBe(true);
+ });
+
+ it('checks missing mount point', function() {
+ $tr.find('[name=mountPoint]').val('');
+
+ $tr.find('input[data-parameter=field_text]').val('foo');
+ $tr.find('input[data-parameter=field_password]').val('bar');
+
+ var storage = view.getStorageConfig($tr);
+
+ expect(storage.validate()).toBe(false);
+ });
+ });
describe('update storage', function() {
// TODO
});
diff --git a/apps/files_sharing/api/share20ocs.php b/apps/files_sharing/api/share20ocs.php
index 2dadc0888ec..8fe8991f9c9 100644
--- a/apps/files_sharing/api/share20ocs.php
+++ b/apps/files_sharing/api/share20ocs.php
@@ -329,9 +329,13 @@ class Share20OCS {
return new \OC_OCS_Result($share);
}
- private function getSharedWithMe() {
- $userShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_USER, -1, 0);
- $groupShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_GROUP, -1, 0);
+ /**
+ * @param \OCP\Files\File|\OCP\Files\Folder $node
+ * @return \OC_OCS_Result
+ */
+ private function getSharedWithMe($node = null) {
+ $userShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_USER, $node, -1, 0);
+ $groupShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_GROUP, $node, -1, 0);
$shares = array_merge($userShares, $groupShares);
@@ -390,10 +394,6 @@ class Share20OCS {
$subfiles = $this->request->getParam('subfiles');
$path = $this->request->getParam('path', null);
- if ($sharedWithMe === 'true') {
- return $this->getSharedWithMe();
- }
-
if ($path !== null) {
$userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID());
try {
@@ -403,6 +403,10 @@ class Share20OCS {
}
}
+ if ($sharedWithMe === 'true') {
+ return $this->getSharedWithMe($path);
+ }
+
if ($subfiles === 'true') {
return $this->getSharesInDir($path);
}
diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml
index bb16345f101..17826be47b4 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.8.1</version>
+ <version>0.9.0</version>
<types>
<filesystem/>
</types>
diff --git a/apps/files_sharing/appinfo/update.php b/apps/files_sharing/appinfo/update.php
index 549b25dae83..d754a95705c 100644
--- a/apps/files_sharing/appinfo/update.php
+++ b/apps/files_sharing/appinfo/update.php
@@ -24,10 +24,11 @@ use OCA\Files_Sharing\Migration;
$installedVersion = \OC::$server->getConfig()->getAppValue('files_sharing', 'installed_version');
-// Migration OC7 -> OC8
-if (version_compare($installedVersion, '0.6.0', '<')) {
- $m = new Migration();
- $m->addAcceptRow();
+// Migration OC8.2 -> OC9
+if (version_compare($installedVersion, '0.9.0', '<')) {
+ $m = new Migration(\OC::$server->getDatabaseConnection());
+ $m->removeReShares();
+ $m->updateInitiatorInfo();
}
\OC::$server->getJobList()->add('OCA\Files_sharing\Lib\DeleteOrphanedSharesJob');
diff --git a/apps/files_sharing/lib/migration.php b/apps/files_sharing/lib/migration.php
index 0c5f46a5b3f..90e0dead480 100644
--- a/apps/files_sharing/lib/migration.php
+++ b/apps/files_sharing/lib/migration.php
@@ -1,7 +1,6 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
- * @author Morris Jobke <hey@morrisjobke.de>
*
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @license AGPL-3.0
@@ -20,22 +19,246 @@
*
*/
- namespace OCA\Files_Sharing;
+namespace OCA\Files_Sharing;
+use Doctrine\DBAL\Connection;
+use OCP\IDBConnection;
+use OC\Cache\CappedMemoryCache;
+
+/**
+ * Class Migration
+ *
+ * @package OCA\Files_Sharing
+ * @group DB
+ */
class Migration {
+ /** @var IDBConnection */
+ private $connection;
+
+ /** @var array with all shares we already saw */
+ private $shareCache;
+
+ /** @var string */
+ private $table = 'share';
+
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+
+ // We cache up to 10k share items (~20MB)
+ $this->shareCache = new CappedMemoryCache(10000);
+ }
+
+ /**
+ * move all re-shares to the owner in order to have a flat list of shares
+ * upgrade from oC 8.2 to 9.0 with the new sharing
+ */
+ public function removeReShares() {
+
+ while(true) {
+ $reShares = $this->getReShares(1000);
+
+ if (empty($reShares)) {
+ break;
+ }
+
+ // Update the cache
+ foreach($reShares as $reShare) {
+ $this->shareCache[$reShare['id']] = $reShare;
+ }
+
+ $owners = [];
+ foreach ($reShares as $share) {
+ $owners[$share['id']] = [
+ 'owner' => $this->findOwner($share),
+ 'initiator' => $share['uid_owner']
+ ];
+ }
+ $this->updateOwners($owners);
+
+ //Clear the cache of the shares we just updated so we have more room
+ foreach($owners as $id => $owner) {
+ unset($this->shareCache[$id]);
+ }
+ }
+ }
/**
- * set accepted to 1 for all external shares. At this point in time we only
- * have shares from the first version of server-to-server sharing so all should
- * be accepted
+ * update all owner information so that all shares have an owner
+ * and an initiator for the upgrade from oC 8.2 to 9.0 with the new sharing
*/
- public function addAcceptRow() {
- $statement = 'UPDATE `*PREFIX*share_external` SET `accepted` = 1';
- $connection = \OC::$server->getDatabaseConnection();
- $query = $connection->prepare($statement);
- $query->execute();
+ public function updateInitiatorInfo() {
+ while (true) {
+ $shares = $this->getMissingInitiator(1000);
+
+ if (empty($shares)) {
+ break;
+ }
+
+ $owners = [];
+ foreach ($shares as $share) {
+ $owners[$share['id']] = [
+ 'owner' => $share['uid_owner'],
+ 'initiator' => $share['uid_owner']
+ ];
+ }
+ $this->updateOwners($owners);
+ }
}
+ /**
+ * find the owner of a re-shared file/folder
+ *
+ * @param array $share
+ * @return array
+ */
+ private function findOwner($share) {
+ $currentShare = $share;
+ while(!is_null($currentShare['parent'])) {
+ if (isset($this->shareCache[$currentShare['parent']])) {
+ $currentShare = $this->shareCache[$currentShare['parent']];
+ } else {
+ $currentShare = $this->getShare((int)$currentShare['parent']);
+ $this->shareCache[$currentShare['id']] = $currentShare;
+ }
+ }
+
+ return $currentShare['uid_owner'];
+ }
+
+ /**
+ * Get $n re-shares from the database
+ *
+ * @param int $n The max number of shares to fetch
+ * @return array
+ */
+ private function getReShares($n = 1000) {
+ $query = $this->connection->getQueryBuilder();
+ $query->select(['id', 'parent', 'uid_owner'])
+ ->from($this->table)
+ ->where($query->expr()->in(
+ 'share_type',
+ $query->createNamedParameter(
+ [
+ \OCP\Share::SHARE_TYPE_USER,
+ \OCP\Share::SHARE_TYPE_GROUP,
+ \OCP\Share::SHARE_TYPE_LINK
+ ],
+ Connection::PARAM_INT_ARRAY
+ )
+ ))
+ ->andWhere($query->expr()->in(
+ 'item_type',
+ $query->createNamedParameter(
+ ['file', 'folder'],
+ Connection::PARAM_STR_ARRAY
+ )
+ ))
+ ->andWhere($query->expr()->isNotNull('parent'))
+ ->orderBy('id', 'asc')
+ ->setMaxResults($n);
+ $result = $query->execute();
+ $shares = $result->fetchAll();
+ $result->closeCursor();
+
+ $ordered = [];
+ foreach ($shares as $share) {
+ $ordered[(int)$share['id']] = $share;
+ }
+
+ return $ordered;
+ }
+
+ /**
+ * Get $n re-shares from the database
+ *
+ * @param int $n The max number of shares to fetch
+ * @return array
+ */
+ private function getMissingInitiator($n = 1000) {
+ $query = $this->connection->getQueryBuilder();
+ $query->select(['id', 'uid_owner'])
+ ->from($this->table)
+ ->where($query->expr()->in(
+ 'share_type',
+ $query->createNamedParameter(
+ [
+ \OCP\Share::SHARE_TYPE_USER,
+ \OCP\Share::SHARE_TYPE_GROUP,
+ \OCP\Share::SHARE_TYPE_LINK
+ ],
+ Connection::PARAM_INT_ARRAY
+ )
+ ))
+ ->andWhere($query->expr()->in(
+ 'item_type',
+ $query->createNamedParameter(
+ ['file', 'folder'],
+ Connection::PARAM_STR_ARRAY
+ )
+ ))
+ ->andWhere($query->expr()->isNull('uid_initiator'))
+ ->orderBy('id', 'asc')
+ ->setMaxResults($n);
+ $result = $query->execute();
+ $shares = $result->fetchAll();
+ $result->closeCursor();
+
+ $ordered = [];
+ foreach ($shares as $share) {
+ $ordered[(int)$share['id']] = $share;
+ }
+
+ return $ordered;
+ }
+
+ /**
+ * get a specific share
+ *
+ * @param int $id
+ * @return array
+ */
+ private function getShare($id) {
+ $query = $this->connection->getQueryBuilder();
+ $query->select(['id', 'parent', 'uid_owner'])
+ ->from($this->table)
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+ $result = $query->execute();
+ $share = $result->fetchAll();
+ $result->closeCursor();
+
+ return $share[0];
+ }
+
+ /**
+ * update database with the new owners
+ *
+ * @param array $owners
+ * @throws \Exception
+ */
+ private function updateOwners($owners) {
+
+ $this->connection->beginTransaction();
+
+ try {
+
+ foreach ($owners as $id => $owner) {
+ $query = $this->connection->getQueryBuilder();
+ $query->update($this->table)
+ ->set('parent', $query->createNamedParameter(null))
+ ->set('uid_owner', $query->createNamedParameter($owner['owner']))
+ ->set('uid_initiator', $query->createNamedParameter($owner['initiator']))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
+ ->execute();
+ }
+
+ $this->connection->commit();
+
+ } catch (\Exception $e) {
+ $this->connection->rollBack();
+ throw $e;
+ }
+
+ }
}
diff --git a/apps/files_sharing/tests/migrationtest.php b/apps/files_sharing/tests/migrationtest.php
index 14df5af381d..e1c047e0342 100644
--- a/apps/files_sharing/tests/migrationtest.php
+++ b/apps/files_sharing/tests/migrationtest.php
@@ -32,51 +32,277 @@ use OCA\Files_Sharing\Migration;
*/
class MigrationTest extends TestCase {
- /**
- * @var \OCP\IDBConnection
- */
+ /** @var \OCP\IDBConnection */
private $connection;
- function __construct() {
- parent::__construct();
+ /** @var Migration */
+ private $migration;
+
+ private $table = 'share';
+
+ public function setUp() {
+ parent::setUp();
$this->connection = \OC::$server->getDatabaseConnection();
+ $this->migration = new Migration($this->connection);
+
+ $this->cleanDB();
}
- function testAddAccept() {
+ public function tearDown() {
+ parent::tearDown();
+ $this->cleanDB();
+ }
- $query = $this->connection->prepare('
- INSERT INTO `*PREFIX*share_external`
- (`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `remote_id`, `accepted`)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- ');
+ private function cleanDB() {
+ $query = $this->connection->getQueryBuilder();
+ $query->delete($this->table)->execute();
+ }
- for ($i = 0; $i < 10; $i++) {
- $query->execute(array('remote', 'token', 'password', 'name', 'owner', 'user', 'mount point', $i, $i, 0));
- }
+ public function addDummyValues() {
+ $query = $this->connection->getQueryBuilder();
+ $query->insert($this->table)
+ ->values(
+ array(
+ 'share_type' => $query->createParameter('share_type'),
+ 'share_with' => $query->createParameter('share_with'),
+ 'uid_owner' => $query->createParameter('uid_owner'),
+ 'uid_initiator' => $query->createParameter('uid_initiator'),
+ 'parent' => $query->createParameter('parent'),
+ 'item_type' => $query->createParameter('item_type'),
+ 'item_source' => $query->createParameter('item_source'),
+ 'item_target' => $query->createParameter('item_target'),
+ 'file_source' => $query->createParameter('file_source'),
+ 'file_target' => $query->createParameter('file_target'),
+ 'permissions' => $query->createParameter('permissions'),
+ 'stime' => $query->createParameter('stime'),
+ )
+ );
+ // shared contact, shouldn't be modified
+ $query->setParameter('share_type', \OCP\Share::SHARE_TYPE_CONTACT)
+ ->setParameter('share_with', 'user1')
+ ->setParameter('uid_owner', 'owner1')
+ ->setParameter('uid_initiator', '')
+ ->setParameter('parent', null)
+ ->setParameter('item_type', 'contact')
+ ->setParameter('item_source', '2')
+ ->setParameter('item_target', '/2')
+ ->setParameter('file_source', null)
+ ->setParameter('file_target', null)
+ ->setParameter('permissions', 31)
+ ->setParameter('stime', time());
+ $this->assertSame(1,
+ $query->execute()
+ );
+ // shared calendar, shouldn't be modified
+ $query->setParameter('share_type', \OCP\Share::SHARE_TYPE_USER)
+ ->setParameter('share_with', 'user1')
+ ->setParameter('uid_owner', 'owner1')
+ ->setParameter('uid_initiator', '')
+ ->setParameter('parent', null)
+ ->setParameter('item_type', 'calendar')
+ ->setParameter('item_source', '2')
+ ->setParameter('item_target', '/2')
+ ->setParameter('file_source', null)
+ ->setParameter('file_target', null)
+ ->setParameter('permissions', 31)
+ ->setParameter('stime', time());
+ $this->assertSame(1,
+ $query->execute()
+ );
+ // single user share, shouldn't be modified
+ $query->setParameter('share_type', \OCP\Share::SHARE_TYPE_USER)
+ ->setParameter('share_with', 'user1')
+ ->setParameter('uid_owner', 'owner1')
+ ->setParameter('uid_initiator', '')
+ ->setParameter('parent', null)
+ ->setParameter('item_type', 'file')
+ ->setParameter('item_source', '2')
+ ->setParameter('item_target', '/2')
+ ->setParameter('file_source', 2)
+ ->setParameter('file_target', '/foo')
+ ->setParameter('permissions', 31)
+ ->setParameter('stime', time());
+ $this->assertSame(1,
+ $query->execute()
+ );
+ // single group share, shouldn't be modified
+ $query->setParameter('share_type', \OCP\Share::SHARE_TYPE_GROUP)
+ ->setParameter('share_with', 'group1')
+ ->setParameter('uid_owner', 'owner1')
+ ->setParameter('uid_initiator', '')
+ ->setParameter('parent', null)
+ ->setParameter('item_type', 'file')
+ ->setParameter('item_source', '2')
+ ->setParameter('item_target', '/2')
+ ->setParameter('file_source', 2)
+ ->setParameter('file_target', '/foo')
+ ->setParameter('permissions', 31)
+ ->setParameter('stime', time());
+ $this->assertSame(1,
+ $query->execute()
+ );
+ $parent = $query->getLastInsertId();
+ // unique target for group share, shouldn't be modified
+ $query->setParameter('share_type', 2)
+ ->setParameter('share_with', 'group1')
+ ->setParameter('uid_owner', 'owner1')
+ ->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', '/foo renamed')
+ ->setParameter('permissions', 31)
+ ->setParameter('stime', time());
+ $this->assertSame(1,
+ $query->execute()
+ );
+ // first user share, shouldn't be modified
+ $query->setParameter('share_type', \OCP\Share::SHARE_TYPE_USER)
+ ->setParameter('share_with', 'user1')
+ ->setParameter('uid_owner', 'owner2')
+ ->setParameter('uid_initiator', '')
+ ->setParameter('parent', null)
+ ->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()
+ );
+ $parent = $query->getLastInsertId();
+ // first re-share, should be attached to the first user share after migration
+ $query->setParameter('share_type', \OCP\Share::SHARE_TYPE_USER)
+ ->setParameter('share_with', 'user2')
+ ->setParameter('uid_owner', 'user1')
+ ->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()
+ );
+ $parent = $query->getLastInsertId();
+ // second re-share, should be attached to the first user share after migration
+ $query->setParameter('share_type', \OCP\Share::SHARE_TYPE_USER)
+ ->setParameter('share_with', 'user3')
+ ->setParameter('uid_owner', 'user2')
+ ->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()
+ );
+ }
- $query = $this->connection->prepare('SELECT `id` FROM `*PREFIX*share_external`');
- $query->execute();
- $dummyEntries = $query->fetchAll();
+ public function testRemoveReShares() {
+ $this->addDummyValues();
+ $this->migration->removeReShares();
+ $this->verifyResult();
+ }
- $this->assertSame(10, count($dummyEntries));
+ public function verifyResult() {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('*')->from($this->table)->orderBy('id');
+ $result = $query->execute()->fetchAll();
+ $this->assertSame(8, count($result));
- $m = new Migration();
- $m->addAcceptRow();
+ // shares which shouldn't be modified
+ for ($i = 0; $i < 4; $i++) {
+ $this->assertSame('owner1', $result[$i]['uid_owner']);
+ $this->assertEmpty($result[$i]['uid_initiator']);
+ $this->assertNull($result[$i]['parent']);
+ }
+ // group share with unique target
+ $this->assertSame('owner1', $result[4]['uid_owner']);
+ $this->assertEmpty($result[4]['uid_initiator']);
+ $this->assertNotEmpty($result[4]['parent']);
+ // initial user share which was re-shared
+ $this->assertSame('owner2', $result[5]['uid_owner']);
+ $this->assertEmpty($result[5]['uid_initiator']);
+ $this->assertNull($result[5]['parent']);
+ // flatted re-shares
+ for($i = 6; $i < 8; $i++) {
+ $this->assertSame('owner2', $result[$i]['uid_owner']);
+ $user = 'user' . ($i - 5);
+ $this->assertSame($user, $result[$i]['uid_initiator']);
+ $this->assertNull($result[$i]['parent']);
+ }
+ }
- // verify result
- $query = $this->connection->prepare('SELECT `accepted` FROM `*PREFIX*share_external`');
- $query->execute();
- $results = $query->fetchAll();
- $this->assertSame(10, count($results));
+ public function test1001DeepReshares() {
+ $parent = null;
+ for ($i = 0; $i < 1001; $i++) {
+ $query = $this->connection->getQueryBuilder();
+ $query->insert($this->table)
+ ->values(
+ [
+ 'share_type' => $query->createParameter('share_type'),
+ 'share_with' => $query->createParameter('share_with'),
+ 'uid_owner' => $query->createParameter('uid_owner'),
+ 'uid_initiator' => $query->createParameter('uid_initiator'),
+ 'parent' => $query->createParameter('parent'),
+ 'item_type' => $query->createParameter('item_type'),
+ 'item_source' => $query->createParameter('item_source'),
+ 'item_target' => $query->createParameter('item_target'),
+ 'file_source' => $query->createParameter('file_source'),
+ 'file_target' => $query->createParameter('file_target'),
+ 'permissions' => $query->createParameter('permissions'),
+ 'stime' => $query->createParameter('stime'),
+ ]
+ )
+ ->setParameter('share_type', \OCP\Share::SHARE_TYPE_USER)
+ ->setParameter('share_with', 'user'.($i+1))
+ ->setParameter('uid_owner', 'user'.($i))
+ ->setParameter('uid_initiator', null)
+ ->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());
- foreach ($results as $r) {
- $this->assertSame(1, (int) $r['accepted']);
+ $this->assertSame(1, $query->execute());
+ $parent = $query->getLastInsertId();
}
- // cleanup
- $cleanup = $this->connection->prepare('DELETE FROM `*PREFIX*share_external`');
- $cleanup->execute();
- }
+ $this->migration->removeReShares();
+ $this->migration->updateInitiatorInfo();
+ $qb = $this->connection->getQueryBuilder();
+
+ $stmt = $qb->select('id', 'share_with', 'uid_owner', 'uid_initiator', 'parent')
+ ->from('share')
+ ->orderBy('id', 'asc')
+ ->execute();
+
+ $i = 0;
+ while($share = $stmt->fetch()) {
+ $this->assertEquals('user'.($i+1), $share['share_with']);
+ $this->assertEquals('user' . ($i), $share['uid_initiator']);
+ $this->assertEquals('user0', $share['uid_owner']);
+ $this->assertEquals(null, $share['parent']);
+ $i++;
+ }
+ $stmt->closeCursor();
+ $this->assertEquals(1001, $i);
+ }
}
diff --git a/apps/user_ldap/l10n/he.js b/apps/user_ldap/l10n/he.js
index 8b10da2ea1e..9d2fae8295a 100644
--- a/apps/user_ldap/l10n/he.js
+++ b/apps/user_ldap/l10n/he.js
@@ -17,6 +17,7 @@ OC.L10N.register(
"Configuration incomplete" : "הגדרה לא מלאה",
"Configuration OK" : "הגדרה בסדר",
"Select groups" : "בחירת קבוצות",
+ "Select object classes" : "בחירת מחלקות עצמים",
"Please check the credentials, they seem to be wrong." : "יש לבדוק את פרטי הכניסה, נראה שהם שגויים",
"Please specify the port, it could not be auto-detected." : "יש לספק את שער הכניסה - פורט, לא ניתן היה לאתרו בצורה אוטומטית",
"Base DN could not be auto-detected, please revise credentials, host and port." : "לא ניתן היה לאתר באופן אוטומטי את בסיס DN, יש להחליף את פרטי הכניסה, פרטי שרת ושער גישה - פורט.",
@@ -57,6 +58,7 @@ OC.L10N.register(
"Test Configuration" : "בדיקת הגדרות",
"Help" : "עזרה",
"Groups meeting these criteria are available in %s:" : "קבוצות העומדות בקריטריון זה זמינות ב- %s:",
+ "Only these object classes:" : "מחלקות עצמים אלו בלבד:",
"Only from these groups:" : "רק מקבוצות אלו:",
"Search groups" : "חיפוש בקבוצות",
"Available groups" : "קבוצות זמינות",
@@ -66,23 +68,43 @@ OC.L10N.register(
"The filter specifies which LDAP groups shall have access to the %s instance." : "המסנן הקובע לאיזו קבוצת LDAP תהיה יכולת כניסה למקרה %s.",
"Verify settings and count groups" : "מאמת הגדרות וסופר קבוצות",
"When logging in, %s will find the user based on the following attributes:" : "כאשר מתחברים, %s יחפש את המשתמש על פי המאפיינים הבאים:",
+ "LDAP / AD Username:" : "שם משתמש LDAP / AD:",
+ "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "מאפשר התחברות אל מול שם משתמש LDAP / AD, שהוא רק uid או samaccountname ויזוהה.",
+ "LDAP / AD Email Address:" : "כתובת דואר אלקטרוני LDAP / AD:",
+ "Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed." : "מאפשר התחברות אל מול מאפיין דואר אלקטרוני. Mail וכן mailPrimaryAddress יהיו מותרים לשימוש.",
+ "Other Attributes:" : "מאפיינים נוספים:",
+ "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "מגדיר את הסינון הפעיל, כשיש ניסיון התחברות. %%uid מחליף את שם המשתמש בפעולת ההתחברות. לדוגמא: \"uid=%%uid\"",
+ "Test Loginname" : "בדיקת שם התחברות",
+ "Verify settings" : "מאמת הגדרות",
"1. Server" : "1. שרת",
"%s. Server:" : "%s. שרת:",
"Add a new and blank configuration" : "הוספת תצורה חדשה וריקה",
+ "Copy current configuration into new directory binding" : "מעתיק תצורה נוכחית אל תוך תיקייה חדשה",
"Delete the current configuration" : "מחיקת תצורה נוכחית",
"Host" : "מארח",
+ "You can omit the protocol, except you require SSL. Then start with ldaps://" : "ניתן להשמיט את הפרוטוקול, אך SSL מחייב. לפיכך יש להתחיל עם ldaps://",
"Port" : "פורט",
"Detect Port" : "מחיקת שער - פורט",
"User DN" : "DN משתמש",
+ "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "ה- DN של משתמש הלקוח שבו החיבור יעשה, למשל uid=agent,dc=example,dc=com. לחיבור אנונימי, יש להשאיר את ה- DN והסיסמא ריקים.",
"Password" : "סיסמא",
"For anonymous access, leave DN and Password empty." : "לגישה אנונימית, השאר את הDM והסיסמא ריקים.",
+ "One Base DN per line" : "DN בסיסי אחד לשורה",
+ "Detect Base DN" : "גילוי DN בסיסי",
+ "Test Base DN" : "בדיקת DN בסיסי",
+ "Verify settings and count users" : "מאמת הגדרות וסופר משתמשים",
"Saving" : "שמירה",
"Back" : "אחורה",
"Continue" : "המשך",
"LDAP" : "LDAP",
"Expert" : "מומחה",
"Advanced" : "מתקדם",
+ "Connection Settings" : "הגדרות התחברות",
+ "Configuration Active" : "תצורה פעילה",
+ "When unchecked, this configuration will be skipped." : "כאשר לא מסומן, נדלג על תצורה זו.",
+ "Disable Main Server" : "ניטרול שרת עיקרי",
"in seconds. A change empties the cache." : "בשניות. שינוי מרוקן את המטמון.",
+ "Directory Settings" : "הגדרות תיקייה",
"in bytes" : "בבתים"
},
"nplurals=2; plural=(n != 1);");
diff --git a/apps/user_ldap/l10n/he.json b/apps/user_ldap/l10n/he.json
index 889a55a7172..c325d8f4405 100644
--- a/apps/user_ldap/l10n/he.json
+++ b/apps/user_ldap/l10n/he.json
@@ -15,6 +15,7 @@
"Configuration incomplete" : "הגדרה לא מלאה",
"Configuration OK" : "הגדרה בסדר",
"Select groups" : "בחירת קבוצות",
+ "Select object classes" : "בחירת מחלקות עצמים",
"Please check the credentials, they seem to be wrong." : "יש לבדוק את פרטי הכניסה, נראה שהם שגויים",
"Please specify the port, it could not be auto-detected." : "יש לספק את שער הכניסה - פורט, לא ניתן היה לאתרו בצורה אוטומטית",
"Base DN could not be auto-detected, please revise credentials, host and port." : "לא ניתן היה לאתר באופן אוטומטי את בסיס DN, יש להחליף את פרטי הכניסה, פרטי שרת ושער גישה - פורט.",
@@ -55,6 +56,7 @@
"Test Configuration" : "בדיקת הגדרות",
"Help" : "עזרה",
"Groups meeting these criteria are available in %s:" : "קבוצות העומדות בקריטריון זה זמינות ב- %s:",
+ "Only these object classes:" : "מחלקות עצמים אלו בלבד:",
"Only from these groups:" : "רק מקבוצות אלו:",
"Search groups" : "חיפוש בקבוצות",
"Available groups" : "קבוצות זמינות",
@@ -64,23 +66,43 @@
"The filter specifies which LDAP groups shall have access to the %s instance." : "המסנן הקובע לאיזו קבוצת LDAP תהיה יכולת כניסה למקרה %s.",
"Verify settings and count groups" : "מאמת הגדרות וסופר קבוצות",
"When logging in, %s will find the user based on the following attributes:" : "כאשר מתחברים, %s יחפש את המשתמש על פי המאפיינים הבאים:",
+ "LDAP / AD Username:" : "שם משתמש LDAP / AD:",
+ "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "מאפשר התחברות אל מול שם משתמש LDAP / AD, שהוא רק uid או samaccountname ויזוהה.",
+ "LDAP / AD Email Address:" : "כתובת דואר אלקטרוני LDAP / AD:",
+ "Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed." : "מאפשר התחברות אל מול מאפיין דואר אלקטרוני. Mail וכן mailPrimaryAddress יהיו מותרים לשימוש.",
+ "Other Attributes:" : "מאפיינים נוספים:",
+ "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "מגדיר את הסינון הפעיל, כשיש ניסיון התחברות. %%uid מחליף את שם המשתמש בפעולת ההתחברות. לדוגמא: \"uid=%%uid\"",
+ "Test Loginname" : "בדיקת שם התחברות",
+ "Verify settings" : "מאמת הגדרות",
"1. Server" : "1. שרת",
"%s. Server:" : "%s. שרת:",
"Add a new and blank configuration" : "הוספת תצורה חדשה וריקה",
+ "Copy current configuration into new directory binding" : "מעתיק תצורה נוכחית אל תוך תיקייה חדשה",
"Delete the current configuration" : "מחיקת תצורה נוכחית",
"Host" : "מארח",
+ "You can omit the protocol, except you require SSL. Then start with ldaps://" : "ניתן להשמיט את הפרוטוקול, אך SSL מחייב. לפיכך יש להתחיל עם ldaps://",
"Port" : "פורט",
"Detect Port" : "מחיקת שער - פורט",
"User DN" : "DN משתמש",
+ "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "ה- DN של משתמש הלקוח שבו החיבור יעשה, למשל uid=agent,dc=example,dc=com. לחיבור אנונימי, יש להשאיר את ה- DN והסיסמא ריקים.",
"Password" : "סיסמא",
"For anonymous access, leave DN and Password empty." : "לגישה אנונימית, השאר את הDM והסיסמא ריקים.",
+ "One Base DN per line" : "DN בסיסי אחד לשורה",
+ "Detect Base DN" : "גילוי DN בסיסי",
+ "Test Base DN" : "בדיקת DN בסיסי",
+ "Verify settings and count users" : "מאמת הגדרות וסופר משתמשים",
"Saving" : "שמירה",
"Back" : "אחורה",
"Continue" : "המשך",
"LDAP" : "LDAP",
"Expert" : "מומחה",
"Advanced" : "מתקדם",
+ "Connection Settings" : "הגדרות התחברות",
+ "Configuration Active" : "תצורה פעילה",
+ "When unchecked, this configuration will be skipped." : "כאשר לא מסומן, נדלג על תצורה זו.",
+ "Disable Main Server" : "ניטרול שרת עיקרי",
"in seconds. A change empties the cache." : "בשניות. שינוי מרוקן את המטמון.",
+ "Directory Settings" : "הגדרות תיקייה",
"in bytes" : "בבתים"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php
index 97bb13c5f79..addd7d0b51d 100644
--- a/apps/user_ldap/lib/connection.php
+++ b/apps/user_ldap/lib/connection.php
@@ -206,7 +206,7 @@ class Connection extends LDAPUtility {
}
$key = $this->getCacheKey($key);
- return json_decode(base64_decode($this->cache->get($key)));
+ return json_decode(base64_decode($this->cache->get($key)), true);
}
/**
diff --git a/build/integration/features/sharing-v1.feature b/build/integration/features/sharing-v1.feature
index dedf2c388fc..bdc1a4224d8 100644
--- a/build/integration/features/sharing-v1.feature
+++ b/build/integration/features/sharing-v1.feature
@@ -313,6 +313,28 @@ Feature: sharing
And the HTTP status code should be "200"
And last share_id is included in the answer
+ Scenario: Sharee can see the filtered share
+ Given user "user0" exists
+ And user "user1" exists
+ And file "textfile0.txt" of user "user0" is shared with user "user1"
+ And file "textfile1.txt" of user "user0" is shared with user "user1"
+ And As an "user1"
+ When sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=true&path=textfile1 (2).txt"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And last share_id is included in the answer
+
+ Scenario: Sharee can't see the share that is filtered out
+ Given user "user0" exists
+ And user "user1" exists
+ And file "textfile0.txt" of user "user0" is shared with user "user1"
+ And file "textfile1.txt" of user "user0" is shared with user "user1"
+ And As an "user1"
+ When sending "GET" to "/apps/files_sharing/api/v1/shares?shared_with_me=true&path=textfile0 (2).txt"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And last share_id is not included in the answer
+
Scenario: Sharee can see the group share
Given As an "admin"
And user "user0" exists
diff --git a/core/js/config.php b/core/js/config.php
index 708da777ee4..9ea8f3864d1 100644
--- a/core/js/config.php
+++ b/core/js/config.php
@@ -140,7 +140,7 @@ $array = array(
'session_keepalive' => \OCP\Config::getSystemValue('session_keepalive', true),
'version' => implode('.', \OCP\Util::getVersion()),
'versionstring' => OC_Util::getVersionString(),
- 'enable_avatars' => \OC::$server->getConfig()->getSystemValue('enable_avatars', true),
+ 'enable_avatars' => \OC::$server->getConfig()->getSystemValue('enable_avatars', true) === true,
'lost_password_link'=> \OC::$server->getConfig()->getSystemValue('lost_password_link', null),
'modRewriteWorking' => (getenv('front_controller_active') === 'true'),
)
diff --git a/core/js/js.js b/core/js/js.js
index 74121fa3d80..43ea269c203 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -1,8 +1,8 @@
/**
* Disable console output unless DEBUG mode is enabled.
* Add
- * define('DEBUG', true);
- * To the end of config/config.php to enable debug mode.
+ * 'debug' => true,
+ * To the definition of $CONFIG in config/config.php to enable debug mode.
* The undefined checks fix the broken ie8 console
*/
diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js
index c9fc85c91b2..a7764dd6266 100644
--- a/core/js/shareitemmodel.js
+++ b/core/js/shareitemmodel.js
@@ -123,7 +123,7 @@
shareId = this.get('linkShare').id;
// note: update can only update a single value at a time
- call = this.updateShare(shareId, attributes);
+ call = this.updateShare(shareId, attributes, options);
} else {
attributes = _.defaults(attributes, {
password: '',
@@ -133,7 +133,7 @@
shareType: OC.Share.SHARE_TYPE_LINK
});
- call = this.addShare(attributes);
+ call = this.addShare(attributes, options);
}
return call;
@@ -189,8 +189,9 @@
}
}
});
- }).fail(function(result) {
+ }).fail(function(xhr) {
var msg = t('core', 'Error');
+ var result = xhr.responseJSON;
if (result.ocs && result.ocs.meta) {
msg = result.ocs.meta.message;
}
@@ -203,15 +204,34 @@
});
},
- updateShare: function(shareId, attrs) {
+ updateShare: function(shareId, attrs, options) {
var self = this;
+ options = options || {};
return $.ajax({
type: 'PUT',
url: this._getUrl('shares/' + encodeURIComponent(shareId)),
data: attrs,
dataType: 'json'
}).done(function() {
- self.fetch();
+ self.fetch({
+ success: function() {
+ if (_.isFunction(options.success)) {
+ options.success(self);
+ }
+ }
+ });
+ }).fail(function(xhr) {
+ var msg = t('core', 'Error');
+ var result = xhr.responseJSON;
+ if (result.ocs && result.ocs.meta) {
+ msg = result.ocs.meta.message;
+ }
+
+ if (_.isFunction(options.error)) {
+ options.error(self, msg);
+ } else {
+ OC.dialogs.alert(msg, t('core', 'Error while sharing'));
+ }
});
},
@@ -221,13 +241,32 @@
* @param {int} shareId share id
* @return {jQuery}
*/
- removeShare: function(shareId) {
+ removeShare: function(shareId, options) {
var self = this;
+ options = options || {};
return $.ajax({
type: 'DELETE',
url: this._getUrl('shares/' + encodeURIComponent(shareId)),
}).done(function() {
- self.fetch();
+ self.fetch({
+ success: function() {
+ if (_.isFunction(options.success)) {
+ options.success(self);
+ }
+ }
+ });
+ }).fail(function(xhr) {
+ var msg = t('core', 'Error');
+ var result = xhr.responseJSON;
+ if (result.ocs && result.ocs.meta) {
+ msg = result.ocs.meta.message;
+ }
+
+ if (_.isFunction(options.error)) {
+ options.error(self, msg);
+ } else {
+ OC.dialogs.alert(msg, t('core', 'Error removing share'));
+ }
});
},
diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js
index e1a14bbbe7f..b2480a8beaa 100644
--- a/core/js/tests/specs/shareitemmodelSpec.js
+++ b/core/js/tests/specs/shareitemmodelSpec.js
@@ -659,6 +659,47 @@ describe('OC.Share.ShareItemModel', function() {
password: 'test'
});
});
+ it('forwards error message on add', function() {
+ var errorStub = sinon.stub();
+ model.set({
+ linkShare: {
+ isLinkShare: false
+ }
+ }, {
+ });
+
+ model.saveLinkShare({
+ password: 'test'
+ }, {
+ error: errorStub
+ });
+
+ addShareStub.yieldTo('error', 'Some error message');
+
+ expect(errorStub.calledOnce).toEqual(true);
+ expect(errorStub.lastCall.args[0]).toEqual('Some error message');
+ });
+ it('forwards error message on update', function() {
+ var errorStub = sinon.stub();
+ model.set({
+ linkShare: {
+ isLinkShare: true,
+ id: '123'
+ }
+ }, {
+ });
+
+ model.saveLinkShare({
+ password: 'test'
+ }, {
+ error: errorStub
+ });
+
+ updateShareStub.yieldTo('error', 'Some error message');
+
+ expect(errorStub.calledOnce).toEqual(true);
+ expect(errorStub.lastCall.args[0]).toEqual('Some error message');
+ });
});
describe('creating shares', function() {
it('sends POST method to endpoint with passed values', function() {
@@ -680,6 +721,31 @@ describe('OC.Share.ShareItemModel', function() {
shareWith: 'group1'
});
});
+ it('calls error handler with error message', function() {
+ var errorStub = sinon.stub();
+ model.addShare({
+ shareType: OC.Share.SHARE_TYPE_GROUP,
+ shareWith: 'group1'
+ }, {
+ error: errorStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 400,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({
+ ocs: {
+ meta: {
+ message: 'Some error message'
+ }
+ }
+ })
+ );
+
+ expect(errorStub.calledOnce).toEqual(true);
+ expect(errorStub.lastCall.args[1]).toEqual('Some error message');
+ });
});
describe('updating shares', function() {
it('sends PUT method to endpoint with passed values', function() {
@@ -697,6 +763,30 @@ describe('OC.Share.ShareItemModel', function() {
permissions: '' + (OC.PERMISSION_READ | OC.PERMISSION_SHARE)
});
});
+ it('calls error handler with error message', function() {
+ var errorStub = sinon.stub();
+ model.updateShare(123, {
+ permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE
+ }, {
+ error: errorStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 400,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({
+ ocs: {
+ meta: {
+ message: 'Some error message'
+ }
+ }
+ })
+ );
+
+ expect(errorStub.calledOnce).toEqual(true);
+ expect(errorStub.lastCall.args[1]).toEqual('Some error message');
+ });
});
describe('removing shares', function() {
it('sends DELETE method to endpoint with share id', function() {
@@ -709,6 +799,28 @@ describe('OC.Share.ShareItemModel', function() {
'shares/123?format=json'
);
});
+ it('calls error handler with error message', function() {
+ var errorStub = sinon.stub();
+ model.removeShare(123, {
+ error: errorStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 400,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({
+ ocs: {
+ meta: {
+ message: 'Some error message'
+ }
+ }
+ })
+ );
+
+ expect(errorStub.calledOnce).toEqual(true);
+ expect(errorStub.lastCall.args[1]).toEqual('Some error message');
+ });
});
});
diff --git a/core/l10n/cs_CZ.js b/core/l10n/cs_CZ.js
index 33c18b3a293..8fcfef6f2b4 100644
--- a/core/l10n/cs_CZ.js
+++ b/core/l10n/cs_CZ.js
@@ -164,17 +164,20 @@ OC.L10N.register(
"change" : "změnit",
"delete" : "smazat",
"access control" : "řízení přístupu",
+ "Could not unshare" : "Nelze zrušit sdílení",
"Share details could not be loaded for this item." : "Detaily sdílení pro tuto položku nelze načíst.",
"An error occured. Please try again" : "Nastala chyba. Prosím zkuste to znovu",
"Share" : "Sdílet",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Sdílejte s lidmi na ownClouds použitím syntaxe username@example.com/owncloud",
"Share with users or groups …" : "Sdílet s uživateli nebo skupinami",
"Share with users, groups or remote users …" : "Sdílet s uživateli, skupinami nebo vzdálenými uživateli",
+ "Error removing share" : "Chyba při odstraňování sdílení",
"Warning" : "Varování",
"Error while sending notification" : "Chyba při odesílání upozornění",
"Non-existing tag #{tag}" : "Neexistující tag #{tag}",
"not assignable" : "nepřiřaditelný",
"invisible" : "neviditelný",
+ "({scope})" : "({scope})",
"Delete" : "Smazat",
"Rename" : "Přejmenovat",
"Global tags" : "Globální tagy",
diff --git a/core/l10n/cs_CZ.json b/core/l10n/cs_CZ.json
index f72bdd32fdc..19c22abd616 100644
--- a/core/l10n/cs_CZ.json
+++ b/core/l10n/cs_CZ.json
@@ -162,17 +162,20 @@
"change" : "změnit",
"delete" : "smazat",
"access control" : "řízení přístupu",
+ "Could not unshare" : "Nelze zrušit sdílení",
"Share details could not be loaded for this item." : "Detaily sdílení pro tuto položku nelze načíst.",
"An error occured. Please try again" : "Nastala chyba. Prosím zkuste to znovu",
"Share" : "Sdílet",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Sdílejte s lidmi na ownClouds použitím syntaxe username@example.com/owncloud",
"Share with users or groups …" : "Sdílet s uživateli nebo skupinami",
"Share with users, groups or remote users …" : "Sdílet s uživateli, skupinami nebo vzdálenými uživateli",
+ "Error removing share" : "Chyba při odstraňování sdílení",
"Warning" : "Varování",
"Error while sending notification" : "Chyba při odesílání upozornění",
"Non-existing tag #{tag}" : "Neexistující tag #{tag}",
"not assignable" : "nepřiřaditelný",
"invisible" : "neviditelný",
+ "({scope})" : "({scope})",
"Delete" : "Smazat",
"Rename" : "Přejmenovat",
"Global tags" : "Globální tagy",
diff --git a/core/l10n/de_DE.js b/core/l10n/de_DE.js
index 0da881a3b11..eb3cff6bef5 100644
--- a/core/l10n/de_DE.js
+++ b/core/l10n/de_DE.js
@@ -160,6 +160,7 @@ OC.L10N.register(
"Share with users, groups or remote users …" : "Mit Benutzern, Gruppen oder entfernten Benutzern teilen…",
"Warning" : "Warnung",
"Error while sending notification" : "Fehler beim Senden der Benachrichtigung",
+ "invisible" : "unsichtbar",
"Delete" : "Löschen",
"Rename" : "Umbenennen",
"The object type is not specified." : "Der Objekttyp ist nicht angegeben.",
@@ -175,6 +176,7 @@ OC.L10N.register(
"Hello {name}" : "Hallo {name}",
"_download %n file_::_download %n files_" : ["Lade %n Datei herunter","Lade %n Dateien herunter"],
"{version} is available. Get more information on how to update." : "{version} ist verfügbar. Holen Sie weitere Informationen zu Aktualisierungen ein.",
+ "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Das Upgrade läuft noch , diese Seite zu verlassen könnte das Verfahren in einigen Umgebungen unterbrechen.",
"Updating {productName} to version {version}, this may take a while." : "{productName} wird auf Version {version} aktualisiert. Das könnte eine Weile dauern.",
"An error occurred." : "Ein Fehler ist aufgetreten.",
"Please reload the page." : "Bitte laden Sie die Seite neu.",
@@ -250,6 +252,7 @@ OC.L10N.register(
"Please try again or contact your administrator." : "Bitte versuchen Sie es noch einmal oder kontaktieren Sie Ihren Administrator.",
"Log in" : "Einloggen",
"Wrong password. Reset it?" : "Falsches Passwort. Soll es zurückgesetzt werden?",
+ "Wrong password." : "Falsches Passwort.",
"Stay logged in" : "Angemeldet bleiben",
"Alternative Logins" : "Alternative Logins",
"Use the following link to reset your password: {link}" : "Benutzen Sie den folgenden Link, um Ihr Passwort zurückzusetzen: {link}",
diff --git a/core/l10n/de_DE.json b/core/l10n/de_DE.json
index 47bcc27c72b..4eec4c90246 100644
--- a/core/l10n/de_DE.json
+++ b/core/l10n/de_DE.json
@@ -158,6 +158,7 @@
"Share with users, groups or remote users …" : "Mit Benutzern, Gruppen oder entfernten Benutzern teilen…",
"Warning" : "Warnung",
"Error while sending notification" : "Fehler beim Senden der Benachrichtigung",
+ "invisible" : "unsichtbar",
"Delete" : "Löschen",
"Rename" : "Umbenennen",
"The object type is not specified." : "Der Objekttyp ist nicht angegeben.",
@@ -173,6 +174,7 @@
"Hello {name}" : "Hallo {name}",
"_download %n file_::_download %n files_" : ["Lade %n Datei herunter","Lade %n Dateien herunter"],
"{version} is available. Get more information on how to update." : "{version} ist verfügbar. Holen Sie weitere Informationen zu Aktualisierungen ein.",
+ "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Das Upgrade läuft noch , diese Seite zu verlassen könnte das Verfahren in einigen Umgebungen unterbrechen.",
"Updating {productName} to version {version}, this may take a while." : "{productName} wird auf Version {version} aktualisiert. Das könnte eine Weile dauern.",
"An error occurred." : "Ein Fehler ist aufgetreten.",
"Please reload the page." : "Bitte laden Sie die Seite neu.",
@@ -248,6 +250,7 @@
"Please try again or contact your administrator." : "Bitte versuchen Sie es noch einmal oder kontaktieren Sie Ihren Administrator.",
"Log in" : "Einloggen",
"Wrong password. Reset it?" : "Falsches Passwort. Soll es zurückgesetzt werden?",
+ "Wrong password." : "Falsches Passwort.",
"Stay logged in" : "Angemeldet bleiben",
"Alternative Logins" : "Alternative Logins",
"Use the following link to reset your password: {link}" : "Benutzen Sie den folgenden Link, um Ihr Passwort zurückzusetzen: {link}",
diff --git a/core/l10n/fi_FI.js b/core/l10n/fi_FI.js
index 43422498388..12c131f8410 100644
--- a/core/l10n/fi_FI.js
+++ b/core/l10n/fi_FI.js
@@ -164,17 +164,20 @@ OC.L10N.register(
"change" : "muuta",
"delete" : "poista",
"access control" : "Pääsyn hallinta",
+ "Could not unshare" : "Jakamisen lopettaminen epäonnistui",
"Share details could not be loaded for this item." : "Tämän kohteen jakamistietoja ei voitu ladata.",
"An error occured. Please try again" : "Tapahtui virhe. Yritä myöhemmin uudestaan",
"Share" : "Jaa",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Jaa toisia ownCloud-järjestelmiä käyttävien kesken käyttäen syntaksia käyttäjätunnus@esimerkki.fi/owncloud",
"Share with users or groups …" : "Jaa käyttäjien tai ryhmien kanssa…",
"Share with users, groups or remote users …" : "Jaa käyttäjien, ryhmien tai etäkäyttäjien kanssa…",
+ "Error removing share" : "Virhe jakoa poistaessa",
"Warning" : "Varoitus",
"Error while sending notification" : "Virhe ilmoitusta lähettäessä",
"Non-existing tag #{tag}" : "Ei olemassa oleva tunniste #{tag}",
"not assignable" : "ei määritettävissä",
"invisible" : "näkymätön",
+ "({scope})" : "({scope})",
"Delete" : "Poista",
"Rename" : "Nimeä uudelleen",
"Global tags" : "Yleiset tunnisteet",
diff --git a/core/l10n/fi_FI.json b/core/l10n/fi_FI.json
index 40afb85d496..89944f4e153 100644
--- a/core/l10n/fi_FI.json
+++ b/core/l10n/fi_FI.json
@@ -162,17 +162,20 @@
"change" : "muuta",
"delete" : "poista",
"access control" : "Pääsyn hallinta",
+ "Could not unshare" : "Jakamisen lopettaminen epäonnistui",
"Share details could not be loaded for this item." : "Tämän kohteen jakamistietoja ei voitu ladata.",
"An error occured. Please try again" : "Tapahtui virhe. Yritä myöhemmin uudestaan",
"Share" : "Jaa",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Jaa toisia ownCloud-järjestelmiä käyttävien kesken käyttäen syntaksia käyttäjätunnus@esimerkki.fi/owncloud",
"Share with users or groups …" : "Jaa käyttäjien tai ryhmien kanssa…",
"Share with users, groups or remote users …" : "Jaa käyttäjien, ryhmien tai etäkäyttäjien kanssa…",
+ "Error removing share" : "Virhe jakoa poistaessa",
"Warning" : "Varoitus",
"Error while sending notification" : "Virhe ilmoitusta lähettäessä",
"Non-existing tag #{tag}" : "Ei olemassa oleva tunniste #{tag}",
"not assignable" : "ei määritettävissä",
"invisible" : "näkymätön",
+ "({scope})" : "({scope})",
"Delete" : "Poista",
"Rename" : "Nimeä uudelleen",
"Global tags" : "Yleiset tunnisteet",
diff --git a/core/l10n/he.js b/core/l10n/he.js
index 7d061d236b5..9a7bfeede44 100644
--- a/core/l10n/he.js
+++ b/core/l10n/he.js
@@ -119,7 +119,18 @@ OC.L10N.register(
"Strong password" : "סיסמא חזקה",
"Your web server is not yet set up properly to allow file synchronization because the WebDAV interface seems to be broken." : "שרת האינטרנט שלך אינו מוגדר כהלכה לאפשר סנכרון כיוון שממשק ה־WebDAV כנראה שבור.",
"Your web server is not set up properly to resolve \"{url}\". Further information can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>." : "שרת האינטרנט שלך לא מוגדר כהלכה לפתור \"{url}\". מידע נוסף קיים <a target=\"_blank\" href=\"{docLink}\">במסמכים</a> שלנו.",
+ "This server has no working Internet connection. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. We suggest to enable Internet connection for this server if you want to have all features." : "לשרת זה אין חיבור אינטרנט פעיל. לפיכך חלק מהתכונות כגון עגינת אחסון חיצוני, הודעות על עדכונים או התקנת יישומי צד שלישי לא יעבדו. ייתכן ולא יעבדו גם כניסה לקבצים מבחוץ ושליחת הודעות דואר אלקטרוני. אנו ממליצים לאפשר חיבור אינטרנט לשרת זה אם ברצונך שיהיו לך את כל התכונות.",
+ "Your data directory and your files are probably accessible from the Internet. The .htaccess file is not working. We strongly suggest that you configure your web server in a way that the data directory is no longer accessible or you move the data directory outside the web server document root." : "כנראה וניתן לגשת אל תיקיית data והקבצים שלך מהאינטרנט. קובץ .htaccess אינו עובד. אנו ממליצים בכל תוקף שתגדיר את השרת בצורה כזאת שלא ניתן יהיה לגשת לתיקיית ה- data או להעביר את תיקיית ה- dta מחוץ לנתיב המסמכים של שרת האינטרנט.",
+ "No memory cache has been configured. To enhance your performance please configure a memcache if available. Further information can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>." : "לא הוגדר memory cache. על מנת לשפר את הביצועים יש להגדיר memcache אם קיים. מידע נוסף ניתן לצפות ב- <a target=\"_blank\" href=\"{docLink}\">מסמכי התיעוד</a>.",
+ "/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>." : "/dev/urandom אינו ניתן לקריאה על ידי PHP אשר אינו מומלץ בשל סיבות אבטחה. מידע נוסף ניתן לראות ב- <a target=\"_blank\" href=\"{docLink}\">מסמכי התיעוד</a>.",
+ "Your PHP version ({version}) is no longer <a target=\"_blank\" href=\"{phpLink}\">supported by PHP</a>. We encourage you to upgrade your PHP version to take advantage of performance and security updates provided by PHP." : "גרסת ה- PHP שלך ({version}) אינה <a target=\"_blank\" href=\"{phpLink}\">נתמכת כבר על ידי PHP</a>. אנו ממליצים לשדרג את גרסת ה- PHP ולהנות מהיתרון של עדכוני האבטחה והביצועים שמספק ה- PHP.",
+ "The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>." : "תצורת כותרות פרוקסי ההפוכה אינה נכונה, או שהגישה ל- ownCloud מתבצעת מ- proxy אמין. אם הגישה ל- ownCloud אינה מ- proxy אמין, מדובר בבעיית אבטחה שמאפשרת לתוקף לזיין את כתובת ה- IP כגלויה ל- ownCloud. מידע נוסף ניתן למצוא ב- <a target=\"_blank\" href=\"{docLink}\">מסמכי התיעוד</a> שלנו.",
+ "Memcached is configured as distributed cache, but the wrong PHP module \"memcache\" is installed. \\OC\\Memcache\\Memcached only supports \"memcached\" and not \"memcache\". See the <a target=\"_blank\" href=\"{wikiLink}\">memcached wiki about both modules</a>." : "Memcached מוגדר כמטמון מופץ, אבל מותקן מודול PHP לא נכון \"memcache\". רק \\OC\\Memcache\\Memcached תומך ב- \"memcached\" אבל לא ב- \"memcache\". ניתן לצפות ב- <a target=\"_blank\" href=\"{wikiLink}\">memcached wiki בנושא שני המודולים</a>.",
+ "Some files have not passed the integrity check. Further information on how to resolve this issue can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>. (<a href=\"{codeIntegrityDownloadEndpoint}\">List of invalid files…</a> / <a href=\"{rescanEndpoint}\">Rescan…</a>)" : "חלק מהקבצים לא עברו את בדיקת התקינות. מידע נוסף איך לפתור את הבעיה ניתן למצוא ב- to resolve this issue can be found in our <a target=\"_blank\" href=\"{docLink}\">מסמכי התיעוד</a> שלנו. (<a href=\"{codeIntegrityDownloadEndpoint}\">רשימה של קבצים לא תקינים…</a> / <a href=\"{rescanEndpoint}\">סריקה מחדש…</a>)",
"Error occurred while checking server setup" : "שגיאה אירעה בזמן בדיקת התקנת השרת",
+ "The \"{header}\" HTTP header is not configured to equal to \"{expected}\". This is a potential security or privacy risk and we recommend adjusting this setting." : "כותרת ה- HTTP \"{header}\" אינה מוגדרת להיות שווה ל- \"{expected}\". הדבר מהווה פוטנציאל סיכון אבטחה או פגיעה בפרטיות ואנו ממליצים לתקן את הגדרה זו.",
+ "The \"Strict-Transport-Security\" HTTP header is not configured to least \"{seconds}\" seconds. For enhanced security we recommend enabling HSTS as described in our <a href=\"{docUrl}\">security tips</a>." : "כותרת HTTP \"Strict-Transport-Security\" אינה מוגדרת לפחות \"{seconds}\" שניות. להגברת האבטחה אנו ממליצים לאפשר HSTS כפי שמוסבר ב- <a href=\"{docUrl}\">טיפים לאבטחה</a> שלנו.",
+ "You are accessing this site via HTTP. We strongly suggest you configure your server to require using HTTPS instead as described in our <a href=\"{docUrl}\">security tips</a>." : "הנך נכנס לאתר באמצעות פרוטוקול HTTP. אנו ממליצים מאוד להגדיר את השרת לעבוד עם פרוטוקול HTTPS במקום כפי שמוסבר ב- <a href=\"{docUrl}\">טיפים לאבטחה</a> שלנו.",
"Shared" : "שותף",
"Shared with {recipients}" : "שיתוף עם {recipients}",
"Error" : "שגיאה",
@@ -153,16 +164,20 @@ OC.L10N.register(
"change" : "שינוי",
"delete" : "מחיקה",
"access control" : "בקרת גישה",
+ "Could not unshare" : "לא ניתן לבטל שיתוף",
"Share details could not be loaded for this item." : "לא ניתן היה לטעון מידע שיתוף לפריט זה",
"An error occured. Please try again" : "אירעה שגיאה. יש לנסות בבקשה שוב",
"Share" : "שתף",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "ניתן לשתף עם אנשים אחרים המשתמשים ב- ownClouds בעזרת הפורמט הבא username@example.com/owncloud",
"Share with users or groups …" : "שיתוף עם משתמשים או קבוצות...",
"Share with users, groups or remote users …" : "שיתוף עם משתמשים, קבוצות או משתמשים חצוניים...",
+ "Error removing share" : "שגיאה בזמן הסרת שיתוף",
"Warning" : "אזהרה",
"Error while sending notification" : "שגיאה בזמן שליחת הודעה",
"Non-existing tag #{tag}" : "תגית לא קיימת #{tag}",
+ "not assignable" : "לא ניתן להקצאה",
"invisible" : "בלתי גלוי",
+ "({scope})" : "({scope})",
"Delete" : "מחיקה",
"Rename" : "שינוי שם",
"Global tags" : "תגיות גלובליות",
@@ -179,6 +194,8 @@ OC.L10N.register(
"Hello {name}" : "שלום {name}",
"_download %n file_::_download %n files_" : ["הורד %n קובץ","הורדו %n קבצים"],
"{version} is available. Get more information on how to update." : "{version} זמינה. ניתן לקבל מידע נוסף על איך לעדכן.",
+ "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "מתבצע עכשיו שדרוג, מעבר מדף זה עלול לפגוע בתהליך בסביבות הפעלה מסויימות.",
+ "Updating {productName} to version {version}, this may take a while." : "מעדכן {productName} לגרסה {version}, זה יקח זמן מה.",
"An error occurred." : "אירעה שגיאה.",
"Please reload the page." : "יש להעלות מחדש דף זה.",
"The update was unsuccessful. " : "העדכון בוצע בהצלחה.",
@@ -186,6 +203,7 @@ OC.L10N.register(
"The update was successful. Redirecting you to ownCloud now." : "תהליך העדכון הסתיים בהצלחה. עכשיו מנתב אותך אל ownCloud.",
"Searching other places" : "מחפש במקומות אחרים",
"No search results in other folders" : "אין תוצאות חיפוש בתיקיות אחרות",
+ "_{count} search result in another folder_::_{count} search results in other folders_" : ["{count} תוצאת חיפוש בתיקייה אחרות","{count} תוצאות חיפוש בתיקיות אחרות"],
"Personal" : "אישי",
"Users" : "משתמשים",
"Apps" : "יישומים",
@@ -206,6 +224,9 @@ OC.L10N.register(
"The share will expire on %s." : "השיתוף יפוג תוקף ב- %s.",
"Cheers!" : "לחיים!",
"Internal Server Error" : "שגיאה פנימית בשרת",
+ "The server encountered an internal error and was unable to complete your request." : "השרת נתקל בשגיאה פנימית ולא הצליח לסיים את הבקשה שלך.",
+ "Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report." : "יש ליצור קשר עם מנהל השרת אם שגיאה זו חוזרת מספר פעמים, יש לצרף לדיווח את הפרטים הטכניים למטה.",
+ "More details can be found in the server log." : "פרטים נוספים ניתן למצוא בלוג של הרשת.",
"Technical details" : "פרטים טכנים",
"Remote Address: %s" : "כתובת מרוחקת: %s",
"Request ID: %s" : "מספר זיהוי מבוקש: %s",
@@ -217,24 +238,38 @@ OC.L10N.register(
"Trace" : "עקבות",
"Security warning" : "אזהרת אבטחה",
"Your data directory and files are probably accessible from the internet because the .htaccess file does not work." : "תיקיית וקבצי המידע שלך כנראה נגישים מהאינטרנט מכיוון שקובץ ה.htaccess לא עובד.",
+ "For information how to properly configure your server, please see the <a href=\"%s\" target=\"_blank\">documentation</a>." : "למידע איך להגדיר את השרת שלך, ניתן לראות ב- <a href=\"%s\" target=\"_blank\">מסמכי התיעוד</a>.",
"Create an <strong>admin account</strong>" : "יצירת <strong>חשבון מנהל</strong>",
"Username" : "שם משתמש",
"Storage & database" : "אחסון ומסד נתונים",
"Data folder" : "תיקיית נתונים",
"Configure the database" : "הגדרת מסד הנתונים",
"Only %s is available." : "רק %s זמין.",
+ "Install and activate additional PHP modules to choose other database types." : "לבחירת סוגים אחרים של מסדי נתונים יש להתקין ולהפעיל מודולי PHP נוספים.",
+ "For more details check out the documentation." : "למידע נוסף יש לבדוק במסמכי התיעוד.",
"Database user" : "שם משתמש במסד הנתונים",
"Database password" : "ססמת מסד הנתונים",
"Database name" : "שם מסד הנתונים",
"Database tablespace" : "מרחב הכתובות של מסד הנתונים",
"Database host" : "שרת בסיס נתונים",
+ "Performance warning" : "אזהרת ביצועים",
+ "SQLite will be used as database." : "יעשה שימוש ב- SQLite כמסד נתונים.",
+ "For larger installations we recommend to choose a different database backend." : "להתקנות נרחבות אנו ממליצים לבחור מסד נתונים אחר לצד השרת.",
"Especially when using the desktop client for file syncing the use of SQLite is discouraged." : "במיוחד כאשר משתמשים במחשב שולחני לסנכרון קבצים השימוש ב SQLite אינו מומלץ.",
"Finish setup" : "סיום התקנה",
"Finishing …" : "מסיים...",
"Need help?" : "עזרה נזקקת?",
+ "See the documentation" : "יש לצפות במסמכי התיעוד",
+ "Hey there,<br><br>just letting you know that %s shared <strong>%s</strong> with you.<br><a href=\"%s\">View it!</a><br><br>" : "הי לך,<br><br>כדאי לך לדעת ש- %s משתף/פת <strong>%s</strong> אתך.<br><a href=\"%s\">לצפייה!</a><br><br>",
+ "This application requires JavaScript for correct operation. Please {linkstart}enable JavaScript{linkend} and reload the page." : "יישום זה דורש JavaScript לפעולה נכונה. יש {linkstart}לאפשר JavaScript{linkend} ולטעון את העמוד מחדש.",
"Log out" : "התנתקות",
"Search" : "חיפוש",
+ "Server side authentication failed!" : "אימות לצד שרת נכשל!",
+ "Please contact your administrator." : "יש ליצור קשר עם המנהל.",
+ "An internal error occured." : "שגיאה פנימית אירעה.",
+ "Please try again or contact your administrator." : "יש לנסות שוב ליצור קשר עם המנהל שלך.",
"Log in" : "כניסה",
+ "Wrong password. Reset it?" : "סיסמא שגוייה. האם לאפס אותה?",
"Wrong password." : "סיסמא שגוייה.",
"Stay logged in" : "השאר מחובר",
"Alternative Logins" : "כניסות אלטרנטיביות",
@@ -242,7 +277,23 @@ OC.L10N.register(
"New password" : "ססמה חדשה",
"New Password" : "סיסמא חדשה",
"Reset password" : "איפוס ססמה",
+ "This ownCloud instance is currently in single user mode." : "הפעלת ownCloud זו עובדת כרגע במצב של משתמש יחיד.",
+ "This means only administrators can use the instance." : "לפיכך רק מנהלים יכולים להשתמש בהפעלה זו.",
+ "Contact your system administrator if this message persists or appeared unexpectedly." : "יש ליצור קשר עם מנהל המערכת אם הודעה שו נמשכת או מופיעה באופן בלתי צפוי. ",
"Thank you for your patience." : "תודה על הסבלנות.",
- "Start update" : "התחלת עדכון"
+ "You are accessing the server from an untrusted domain." : "נכנסת לשרת משם מתחם / דומיין שאינו מהימן.",
+ "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "יש ליצור קשר עם המנהל שלך. אם הנך המנהל של הפעלה זו, יש להגדיר את הגדרות ה- \"trusted_domains\" של config/config.php. דוגמת תצורה ניתן לראות ב- config/config.sample.php.",
+ "Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "בהתאם לתצורה שלך, כמנהל יתכן ותוכל להשתמש בכפתור מטה להפיכת שם המתחם / דומיין למהימן.",
+ "Add \"%s\" as trusted domain" : "הוספת \"%s\" כשם מתחם / דומיין מהימן",
+ "App update required" : "נדרש עדכון יישום",
+ "%s will be updated to version %s" : "%s יעודכן לגרסה %s",
+ "These apps will be updated:" : "יישומים אלו יעודכנו:",
+ "These incompatible apps will be disabled:" : "יישומים לא תואמים ינוטרלו:",
+ "The theme %s has been disabled." : "ערכת הנושא %s נוטרלה.",
+ "Please make sure that the database, the config folder and the data folder have been backed up before proceeding." : "יש לוודא שמסד הנתונים, תיקיית config ותיקיית data גובו לפני ההמשך.",
+ "Start update" : "התחלת עדכון",
+ "To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "למניעת פסקי זמן בהתקנות גדולות, ניתן במקום להריץ את הפקודה הבאה בתיקיית ההתקנה שלך:",
+ "This %s instance is currently in maintenance mode, which may take a while." : "הפעלה %s זו כרגע במצב אחזקה, שתמשך זמן מה.",
+ "This page will refresh itself when the %s instance is available again." : "עמוד זה ירענן את עצמו כשהפעלת %s תהיה זמינה שוב."
},
"nplurals=2; plural=(n != 1);");
diff --git a/core/l10n/he.json b/core/l10n/he.json
index 64492bcbcac..9e25f9895d1 100644
--- a/core/l10n/he.json
+++ b/core/l10n/he.json
@@ -117,7 +117,18 @@
"Strong password" : "סיסמא חזקה",
"Your web server is not yet set up properly to allow file synchronization because the WebDAV interface seems to be broken." : "שרת האינטרנט שלך אינו מוגדר כהלכה לאפשר סנכרון כיוון שממשק ה־WebDAV כנראה שבור.",
"Your web server is not set up properly to resolve \"{url}\". Further information can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>." : "שרת האינטרנט שלך לא מוגדר כהלכה לפתור \"{url}\". מידע נוסף קיים <a target=\"_blank\" href=\"{docLink}\">במסמכים</a> שלנו.",
+ "This server has no working Internet connection. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. We suggest to enable Internet connection for this server if you want to have all features." : "לשרת זה אין חיבור אינטרנט פעיל. לפיכך חלק מהתכונות כגון עגינת אחסון חיצוני, הודעות על עדכונים או התקנת יישומי צד שלישי לא יעבדו. ייתכן ולא יעבדו גם כניסה לקבצים מבחוץ ושליחת הודעות דואר אלקטרוני. אנו ממליצים לאפשר חיבור אינטרנט לשרת זה אם ברצונך שיהיו לך את כל התכונות.",
+ "Your data directory and your files are probably accessible from the Internet. The .htaccess file is not working. We strongly suggest that you configure your web server in a way that the data directory is no longer accessible or you move the data directory outside the web server document root." : "כנראה וניתן לגשת אל תיקיית data והקבצים שלך מהאינטרנט. קובץ .htaccess אינו עובד. אנו ממליצים בכל תוקף שתגדיר את השרת בצורה כזאת שלא ניתן יהיה לגשת לתיקיית ה- data או להעביר את תיקיית ה- dta מחוץ לנתיב המסמכים של שרת האינטרנט.",
+ "No memory cache has been configured. To enhance your performance please configure a memcache if available. Further information can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>." : "לא הוגדר memory cache. על מנת לשפר את הביצועים יש להגדיר memcache אם קיים. מידע נוסף ניתן לצפות ב- <a target=\"_blank\" href=\"{docLink}\">מסמכי התיעוד</a>.",
+ "/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>." : "/dev/urandom אינו ניתן לקריאה על ידי PHP אשר אינו מומלץ בשל סיבות אבטחה. מידע נוסף ניתן לראות ב- <a target=\"_blank\" href=\"{docLink}\">מסמכי התיעוד</a>.",
+ "Your PHP version ({version}) is no longer <a target=\"_blank\" href=\"{phpLink}\">supported by PHP</a>. We encourage you to upgrade your PHP version to take advantage of performance and security updates provided by PHP." : "גרסת ה- PHP שלך ({version}) אינה <a target=\"_blank\" href=\"{phpLink}\">נתמכת כבר על ידי PHP</a>. אנו ממליצים לשדרג את גרסת ה- PHP ולהנות מהיתרון של עדכוני האבטחה והביצועים שמספק ה- PHP.",
+ "The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>." : "תצורת כותרות פרוקסי ההפוכה אינה נכונה, או שהגישה ל- ownCloud מתבצעת מ- proxy אמין. אם הגישה ל- ownCloud אינה מ- proxy אמין, מדובר בבעיית אבטחה שמאפשרת לתוקף לזיין את כתובת ה- IP כגלויה ל- ownCloud. מידע נוסף ניתן למצוא ב- <a target=\"_blank\" href=\"{docLink}\">מסמכי התיעוד</a> שלנו.",
+ "Memcached is configured as distributed cache, but the wrong PHP module \"memcache\" is installed. \\OC\\Memcache\\Memcached only supports \"memcached\" and not \"memcache\". See the <a target=\"_blank\" href=\"{wikiLink}\">memcached wiki about both modules</a>." : "Memcached מוגדר כמטמון מופץ, אבל מותקן מודול PHP לא נכון \"memcache\". רק \\OC\\Memcache\\Memcached תומך ב- \"memcached\" אבל לא ב- \"memcache\". ניתן לצפות ב- <a target=\"_blank\" href=\"{wikiLink}\">memcached wiki בנושא שני המודולים</a>.",
+ "Some files have not passed the integrity check. Further information on how to resolve this issue can be found in our <a target=\"_blank\" href=\"{docLink}\">documentation</a>. (<a href=\"{codeIntegrityDownloadEndpoint}\">List of invalid files…</a> / <a href=\"{rescanEndpoint}\">Rescan…</a>)" : "חלק מהקבצים לא עברו את בדיקת התקינות. מידע נוסף איך לפתור את הבעיה ניתן למצוא ב- to resolve this issue can be found in our <a target=\"_blank\" href=\"{docLink}\">מסמכי התיעוד</a> שלנו. (<a href=\"{codeIntegrityDownloadEndpoint}\">רשימה של קבצים לא תקינים…</a> / <a href=\"{rescanEndpoint}\">סריקה מחדש…</a>)",
"Error occurred while checking server setup" : "שגיאה אירעה בזמן בדיקת התקנת השרת",
+ "The \"{header}\" HTTP header is not configured to equal to \"{expected}\". This is a potential security or privacy risk and we recommend adjusting this setting." : "כותרת ה- HTTP \"{header}\" אינה מוגדרת להיות שווה ל- \"{expected}\". הדבר מהווה פוטנציאל סיכון אבטחה או פגיעה בפרטיות ואנו ממליצים לתקן את הגדרה זו.",
+ "The \"Strict-Transport-Security\" HTTP header is not configured to least \"{seconds}\" seconds. For enhanced security we recommend enabling HSTS as described in our <a href=\"{docUrl}\">security tips</a>." : "כותרת HTTP \"Strict-Transport-Security\" אינה מוגדרת לפחות \"{seconds}\" שניות. להגברת האבטחה אנו ממליצים לאפשר HSTS כפי שמוסבר ב- <a href=\"{docUrl}\">טיפים לאבטחה</a> שלנו.",
+ "You are accessing this site via HTTP. We strongly suggest you configure your server to require using HTTPS instead as described in our <a href=\"{docUrl}\">security tips</a>." : "הנך נכנס לאתר באמצעות פרוטוקול HTTP. אנו ממליצים מאוד להגדיר את השרת לעבוד עם פרוטוקול HTTPS במקום כפי שמוסבר ב- <a href=\"{docUrl}\">טיפים לאבטחה</a> שלנו.",
"Shared" : "שותף",
"Shared with {recipients}" : "שיתוף עם {recipients}",
"Error" : "שגיאה",
@@ -151,16 +162,20 @@
"change" : "שינוי",
"delete" : "מחיקה",
"access control" : "בקרת גישה",
+ "Could not unshare" : "לא ניתן לבטל שיתוף",
"Share details could not be loaded for this item." : "לא ניתן היה לטעון מידע שיתוף לפריט זה",
"An error occured. Please try again" : "אירעה שגיאה. יש לנסות בבקשה שוב",
"Share" : "שתף",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "ניתן לשתף עם אנשים אחרים המשתמשים ב- ownClouds בעזרת הפורמט הבא username@example.com/owncloud",
"Share with users or groups …" : "שיתוף עם משתמשים או קבוצות...",
"Share with users, groups or remote users …" : "שיתוף עם משתמשים, קבוצות או משתמשים חצוניים...",
+ "Error removing share" : "שגיאה בזמן הסרת שיתוף",
"Warning" : "אזהרה",
"Error while sending notification" : "שגיאה בזמן שליחת הודעה",
"Non-existing tag #{tag}" : "תגית לא קיימת #{tag}",
+ "not assignable" : "לא ניתן להקצאה",
"invisible" : "בלתי גלוי",
+ "({scope})" : "({scope})",
"Delete" : "מחיקה",
"Rename" : "שינוי שם",
"Global tags" : "תגיות גלובליות",
@@ -177,6 +192,8 @@
"Hello {name}" : "שלום {name}",
"_download %n file_::_download %n files_" : ["הורד %n קובץ","הורדו %n קבצים"],
"{version} is available. Get more information on how to update." : "{version} זמינה. ניתן לקבל מידע נוסף על איך לעדכן.",
+ "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "מתבצע עכשיו שדרוג, מעבר מדף זה עלול לפגוע בתהליך בסביבות הפעלה מסויימות.",
+ "Updating {productName} to version {version}, this may take a while." : "מעדכן {productName} לגרסה {version}, זה יקח זמן מה.",
"An error occurred." : "אירעה שגיאה.",
"Please reload the page." : "יש להעלות מחדש דף זה.",
"The update was unsuccessful. " : "העדכון בוצע בהצלחה.",
@@ -184,6 +201,7 @@
"The update was successful. Redirecting you to ownCloud now." : "תהליך העדכון הסתיים בהצלחה. עכשיו מנתב אותך אל ownCloud.",
"Searching other places" : "מחפש במקומות אחרים",
"No search results in other folders" : "אין תוצאות חיפוש בתיקיות אחרות",
+ "_{count} search result in another folder_::_{count} search results in other folders_" : ["{count} תוצאת חיפוש בתיקייה אחרות","{count} תוצאות חיפוש בתיקיות אחרות"],
"Personal" : "אישי",
"Users" : "משתמשים",
"Apps" : "יישומים",
@@ -204,6 +222,9 @@
"The share will expire on %s." : "השיתוף יפוג תוקף ב- %s.",
"Cheers!" : "לחיים!",
"Internal Server Error" : "שגיאה פנימית בשרת",
+ "The server encountered an internal error and was unable to complete your request." : "השרת נתקל בשגיאה פנימית ולא הצליח לסיים את הבקשה שלך.",
+ "Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report." : "יש ליצור קשר עם מנהל השרת אם שגיאה זו חוזרת מספר פעמים, יש לצרף לדיווח את הפרטים הטכניים למטה.",
+ "More details can be found in the server log." : "פרטים נוספים ניתן למצוא בלוג של הרשת.",
"Technical details" : "פרטים טכנים",
"Remote Address: %s" : "כתובת מרוחקת: %s",
"Request ID: %s" : "מספר זיהוי מבוקש: %s",
@@ -215,24 +236,38 @@
"Trace" : "עקבות",
"Security warning" : "אזהרת אבטחה",
"Your data directory and files are probably accessible from the internet because the .htaccess file does not work." : "תיקיית וקבצי המידע שלך כנראה נגישים מהאינטרנט מכיוון שקובץ ה.htaccess לא עובד.",
+ "For information how to properly configure your server, please see the <a href=\"%s\" target=\"_blank\">documentation</a>." : "למידע איך להגדיר את השרת שלך, ניתן לראות ב- <a href=\"%s\" target=\"_blank\">מסמכי התיעוד</a>.",
"Create an <strong>admin account</strong>" : "יצירת <strong>חשבון מנהל</strong>",
"Username" : "שם משתמש",
"Storage & database" : "אחסון ומסד נתונים",
"Data folder" : "תיקיית נתונים",
"Configure the database" : "הגדרת מסד הנתונים",
"Only %s is available." : "רק %s זמין.",
+ "Install and activate additional PHP modules to choose other database types." : "לבחירת סוגים אחרים של מסדי נתונים יש להתקין ולהפעיל מודולי PHP נוספים.",
+ "For more details check out the documentation." : "למידע נוסף יש לבדוק במסמכי התיעוד.",
"Database user" : "שם משתמש במסד הנתונים",
"Database password" : "ססמת מסד הנתונים",
"Database name" : "שם מסד הנתונים",
"Database tablespace" : "מרחב הכתובות של מסד הנתונים",
"Database host" : "שרת בסיס נתונים",
+ "Performance warning" : "אזהרת ביצועים",
+ "SQLite will be used as database." : "יעשה שימוש ב- SQLite כמסד נתונים.",
+ "For larger installations we recommend to choose a different database backend." : "להתקנות נרחבות אנו ממליצים לבחור מסד נתונים אחר לצד השרת.",
"Especially when using the desktop client for file syncing the use of SQLite is discouraged." : "במיוחד כאשר משתמשים במחשב שולחני לסנכרון קבצים השימוש ב SQLite אינו מומלץ.",
"Finish setup" : "סיום התקנה",
"Finishing …" : "מסיים...",
"Need help?" : "עזרה נזקקת?",
+ "See the documentation" : "יש לצפות במסמכי התיעוד",
+ "Hey there,<br><br>just letting you know that %s shared <strong>%s</strong> with you.<br><a href=\"%s\">View it!</a><br><br>" : "הי לך,<br><br>כדאי לך לדעת ש- %s משתף/פת <strong>%s</strong> אתך.<br><a href=\"%s\">לצפייה!</a><br><br>",
+ "This application requires JavaScript for correct operation. Please {linkstart}enable JavaScript{linkend} and reload the page." : "יישום זה דורש JavaScript לפעולה נכונה. יש {linkstart}לאפשר JavaScript{linkend} ולטעון את העמוד מחדש.",
"Log out" : "התנתקות",
"Search" : "חיפוש",
+ "Server side authentication failed!" : "אימות לצד שרת נכשל!",
+ "Please contact your administrator." : "יש ליצור קשר עם המנהל.",
+ "An internal error occured." : "שגיאה פנימית אירעה.",
+ "Please try again or contact your administrator." : "יש לנסות שוב ליצור קשר עם המנהל שלך.",
"Log in" : "כניסה",
+ "Wrong password. Reset it?" : "סיסמא שגוייה. האם לאפס אותה?",
"Wrong password." : "סיסמא שגוייה.",
"Stay logged in" : "השאר מחובר",
"Alternative Logins" : "כניסות אלטרנטיביות",
@@ -240,7 +275,23 @@
"New password" : "ססמה חדשה",
"New Password" : "סיסמא חדשה",
"Reset password" : "איפוס ססמה",
+ "This ownCloud instance is currently in single user mode." : "הפעלת ownCloud זו עובדת כרגע במצב של משתמש יחיד.",
+ "This means only administrators can use the instance." : "לפיכך רק מנהלים יכולים להשתמש בהפעלה זו.",
+ "Contact your system administrator if this message persists or appeared unexpectedly." : "יש ליצור קשר עם מנהל המערכת אם הודעה שו נמשכת או מופיעה באופן בלתי צפוי. ",
"Thank you for your patience." : "תודה על הסבלנות.",
- "Start update" : "התחלת עדכון"
+ "You are accessing the server from an untrusted domain." : "נכנסת לשרת משם מתחם / דומיין שאינו מהימן.",
+ "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "יש ליצור קשר עם המנהל שלך. אם הנך המנהל של הפעלה זו, יש להגדיר את הגדרות ה- \"trusted_domains\" של config/config.php. דוגמת תצורה ניתן לראות ב- config/config.sample.php.",
+ "Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "בהתאם לתצורה שלך, כמנהל יתכן ותוכל להשתמש בכפתור מטה להפיכת שם המתחם / דומיין למהימן.",
+ "Add \"%s\" as trusted domain" : "הוספת \"%s\" כשם מתחם / דומיין מהימן",
+ "App update required" : "נדרש עדכון יישום",
+ "%s will be updated to version %s" : "%s יעודכן לגרסה %s",
+ "These apps will be updated:" : "יישומים אלו יעודכנו:",
+ "These incompatible apps will be disabled:" : "יישומים לא תואמים ינוטרלו:",
+ "The theme %s has been disabled." : "ערכת הנושא %s נוטרלה.",
+ "Please make sure that the database, the config folder and the data folder have been backed up before proceeding." : "יש לוודא שמסד הנתונים, תיקיית config ותיקיית data גובו לפני ההמשך.",
+ "Start update" : "התחלת עדכון",
+ "To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "למניעת פסקי זמן בהתקנות גדולות, ניתן במקום להריץ את הפקודה הבאה בתיקיית ההתקנה שלך:",
+ "This %s instance is currently in maintenance mode, which may take a while." : "הפעלה %s זו כרגע במצב אחזקה, שתמשך זמן מה.",
+ "This page will refresh itself when the %s instance is available again." : "עמוד זה ירענן את עצמו כשהפעלת %s תהיה זמינה שוב."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/core/l10n/it.js b/core/l10n/it.js
index 05bb1dd31c1..d23609d57ec 100644
--- a/core/l10n/it.js
+++ b/core/l10n/it.js
@@ -164,17 +164,20 @@ OC.L10N.register(
"change" : "cambia",
"delete" : "elimina",
"access control" : "controllo d'accesso",
+ "Could not unshare" : "Impossibile rimuovere la condivisione",
"Share details could not be loaded for this item." : "I dettagli della condivisione non possono essere caricati per questo elemento.",
"An error occured. Please try again" : "Si è verificato un errore. Prova ancora",
"Share" : "Condividi",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Condividi con persone su altri ownCloud utilizzando la sintassi nomeutente@esempio.com/owncloud",
"Share with users or groups …" : "Condividi con utenti o gruppi...",
"Share with users, groups or remote users …" : "Condividi con utenti, gruppi o utenti remoti...",
+ "Error removing share" : "Errore durante la rimozione della condivisione",
"Warning" : "Avviso",
"Error while sending notification" : "Errore durante l'invio della notifica",
"Non-existing tag #{tag}" : "Tag #{tag} inesistente",
"not assignable" : "non assegnabile",
"invisible" : "invisibile",
+ "({scope})" : "({scope})",
"Delete" : "Elimina",
"Rename" : "Rinomina",
"Global tags" : "Tag globali",
diff --git a/core/l10n/it.json b/core/l10n/it.json
index 3b301350cb1..966d0f58125 100644
--- a/core/l10n/it.json
+++ b/core/l10n/it.json
@@ -162,17 +162,20 @@
"change" : "cambia",
"delete" : "elimina",
"access control" : "controllo d'accesso",
+ "Could not unshare" : "Impossibile rimuovere la condivisione",
"Share details could not be loaded for this item." : "I dettagli della condivisione non possono essere caricati per questo elemento.",
"An error occured. Please try again" : "Si è verificato un errore. Prova ancora",
"Share" : "Condividi",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Condividi con persone su altri ownCloud utilizzando la sintassi nomeutente@esempio.com/owncloud",
"Share with users or groups …" : "Condividi con utenti o gruppi...",
"Share with users, groups or remote users …" : "Condividi con utenti, gruppi o utenti remoti...",
+ "Error removing share" : "Errore durante la rimozione della condivisione",
"Warning" : "Avviso",
"Error while sending notification" : "Errore durante l'invio della notifica",
"Non-existing tag #{tag}" : "Tag #{tag} inesistente",
"not assignable" : "non assegnabile",
"invisible" : "invisibile",
+ "({scope})" : "({scope})",
"Delete" : "Elimina",
"Rename" : "Rinomina",
"Global tags" : "Tag globali",
diff --git a/core/l10n/ja.js b/core/l10n/ja.js
index 044ca97e888..1b368469f43 100644
--- a/core/l10n/ja.js
+++ b/core/l10n/ja.js
@@ -163,17 +163,21 @@ OC.L10N.register(
"change" : "更新",
"delete" : "削除",
"access control" : "アクセス権限",
+ "Could not unshare" : "共有の解除ができませんでした",
"Share details could not be loaded for this item." : "共有の詳細はこのアイテムによりロードできませんでした。",
"An error occured. Please try again" : "エラーが発生しました。もう一度トライしてください。",
"Share" : "共有",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "次の形式で指定して他のownCloudのユーザーと、共有",
"Share with users or groups …" : "ユーザーもしくはグループと共有 ...",
"Share with users, groups or remote users …" : "ユーザー、グループもしくはリモートユーザーと共有 ...",
+ "Error removing share" : "共有の削除エラー",
"Warning" : "警告",
"Error while sending notification" : "通知送信中にエラーが発生",
"Non-existing tag #{tag}" : "存在しないタグ#{tag}",
+ "not assignable" : "割り当てできない",
"Delete" : "削除",
"Rename" : "名前の変更",
+ "Global tags" : "グローバルタグ",
"The object type is not specified." : "オブジェクトタイプが指定されていません。",
"Enter new" : "新規に入力",
"Add" : "追加",
diff --git a/core/l10n/ja.json b/core/l10n/ja.json
index 337aa886031..4dbcaac6a08 100644
--- a/core/l10n/ja.json
+++ b/core/l10n/ja.json
@@ -161,17 +161,21 @@
"change" : "更新",
"delete" : "削除",
"access control" : "アクセス権限",
+ "Could not unshare" : "共有の解除ができませんでした",
"Share details could not be loaded for this item." : "共有の詳細はこのアイテムによりロードできませんでした。",
"An error occured. Please try again" : "エラーが発生しました。もう一度トライしてください。",
"Share" : "共有",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "次の形式で指定して他のownCloudのユーザーと、共有",
"Share with users or groups …" : "ユーザーもしくはグループと共有 ...",
"Share with users, groups or remote users …" : "ユーザー、グループもしくはリモートユーザーと共有 ...",
+ "Error removing share" : "共有の削除エラー",
"Warning" : "警告",
"Error while sending notification" : "通知送信中にエラーが発生",
"Non-existing tag #{tag}" : "存在しないタグ#{tag}",
+ "not assignable" : "割り当てできない",
"Delete" : "削除",
"Rename" : "名前の変更",
+ "Global tags" : "グローバルタグ",
"The object type is not specified." : "オブジェクトタイプが指定されていません。",
"Enter new" : "新規に入力",
"Add" : "追加",
diff --git a/core/l10n/nl.js b/core/l10n/nl.js
index ed533d6d31c..560cdbcebbc 100644
--- a/core/l10n/nl.js
+++ b/core/l10n/nl.js
@@ -164,17 +164,20 @@ OC.L10N.register(
"change" : "wijzig",
"delete" : "verwijderen",
"access control" : "toegangscontrole",
+ "Could not unshare" : "Kon delen niet ongedaan maken",
"Share details could not be loaded for this item." : "Details van shares voor dit object konden niet worden geladen.",
"An error occured. Please try again" : "Er trad een fout op. Probeer het opnieuw",
"Share" : "Delen",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Delen met mensen op andere ownClouds via de syntax gebruikersnaam@voorbeeld.org/owncloud",
"Share with users or groups …" : "Delen met gebruikers of groepen ...",
"Share with users, groups or remote users …" : "Delen met gebruikers, groepen of externe gebruikers ...",
+ "Error removing share" : "Fout bij verwijderen share",
"Warning" : "Waarschuwing",
"Error while sending notification" : "Fout bij versturen melding",
"Non-existing tag #{tag}" : "Niet bestaande tag #{tag}",
"not assignable" : "niet toewijsbaar",
"invisible" : "onzichtbaar",
+ "({scope})" : "({scope})",
"Delete" : "Verwijder",
"Rename" : "Naam wijzigen",
"Global tags" : "Globale tags",
diff --git a/core/l10n/nl.json b/core/l10n/nl.json
index 906e1d23278..f5eb1c59b41 100644
--- a/core/l10n/nl.json
+++ b/core/l10n/nl.json
@@ -162,17 +162,20 @@
"change" : "wijzig",
"delete" : "verwijderen",
"access control" : "toegangscontrole",
+ "Could not unshare" : "Kon delen niet ongedaan maken",
"Share details could not be loaded for this item." : "Details van shares voor dit object konden niet worden geladen.",
"An error occured. Please try again" : "Er trad een fout op. Probeer het opnieuw",
"Share" : "Delen",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Delen met mensen op andere ownClouds via de syntax gebruikersnaam@voorbeeld.org/owncloud",
"Share with users or groups …" : "Delen met gebruikers of groepen ...",
"Share with users, groups or remote users …" : "Delen met gebruikers, groepen of externe gebruikers ...",
+ "Error removing share" : "Fout bij verwijderen share",
"Warning" : "Waarschuwing",
"Error while sending notification" : "Fout bij versturen melding",
"Non-existing tag #{tag}" : "Niet bestaande tag #{tag}",
"not assignable" : "niet toewijsbaar",
"invisible" : "onzichtbaar",
+ "({scope})" : "({scope})",
"Delete" : "Verwijder",
"Rename" : "Naam wijzigen",
"Global tags" : "Globale tags",
diff --git a/core/l10n/pt_BR.js b/core/l10n/pt_BR.js
index ec0605eaace..3d4e236bac2 100644
--- a/core/l10n/pt_BR.js
+++ b/core/l10n/pt_BR.js
@@ -164,17 +164,20 @@ OC.L10N.register(
"change" : "mudança",
"delete" : "remover",
"access control" : "controle de acesso",
+ "Could not unshare" : "Não foi possível descompartilhar",
"Share details could not be loaded for this item." : "Detalhes de compartilhamento não puderam ser carregados para este item.",
"An error occured. Please try again" : "Ocorreu um erro. Por favor tente novamente",
"Share" : "Compartilhar",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Compartilhar com usuários em outros ownClouds usando a sintaxe username@example.com/owncloud",
"Share with users or groups …" : "Compartilhar com usuários ou grupos ...",
"Share with users, groups or remote users …" : "Compartilhar com usuários, grupos ou usuários remoto ...",
+ "Error removing share" : "Erro na remoção do compartilhamento",
"Warning" : "Aviso",
"Error while sending notification" : "Erro ao enviar notificação",
"Non-existing tag #{tag}" : "Etiqueta não existente #{tag}",
"not assignable" : "não atribuível",
"invisible" : "invisível",
+ "({scope})" : "({scope})",
"Delete" : "Eliminar",
"Rename" : "Renomear",
"Global tags" : "Etiquetas Globais",
diff --git a/core/l10n/pt_BR.json b/core/l10n/pt_BR.json
index eb0e5c223cb..f2289975685 100644
--- a/core/l10n/pt_BR.json
+++ b/core/l10n/pt_BR.json
@@ -162,17 +162,20 @@
"change" : "mudança",
"delete" : "remover",
"access control" : "controle de acesso",
+ "Could not unshare" : "Não foi possível descompartilhar",
"Share details could not be loaded for this item." : "Detalhes de compartilhamento não puderam ser carregados para este item.",
"An error occured. Please try again" : "Ocorreu um erro. Por favor tente novamente",
"Share" : "Compartilhar",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Compartilhar com usuários em outros ownClouds usando a sintaxe username@example.com/owncloud",
"Share with users or groups …" : "Compartilhar com usuários ou grupos ...",
"Share with users, groups or remote users …" : "Compartilhar com usuários, grupos ou usuários remoto ...",
+ "Error removing share" : "Erro na remoção do compartilhamento",
"Warning" : "Aviso",
"Error while sending notification" : "Erro ao enviar notificação",
"Non-existing tag #{tag}" : "Etiqueta não existente #{tag}",
"not assignable" : "não atribuível",
"invisible" : "invisível",
+ "({scope})" : "({scope})",
"Delete" : "Eliminar",
"Rename" : "Renomear",
"Global tags" : "Etiquetas Globais",
diff --git a/core/l10n/pt_PT.js b/core/l10n/pt_PT.js
index dbed83d6c5f..653ec6ec8b5 100644
--- a/core/l10n/pt_PT.js
+++ b/core/l10n/pt_PT.js
@@ -164,17 +164,20 @@ OC.L10N.register(
"change" : "alterar",
"delete" : "apagar",
"access control" : "controlo de acesso",
+ "Could not unshare" : "Não foi possível cancelar a partilha",
"Share details could not be loaded for this item." : "Não foi possível carregar os detalhes de partilha para este item.",
"An error occured. Please try again" : "Ocorreu um erro. Por favor, tente de novo",
"Share" : "Compartilhar",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Compartilhe com as pessoas nas outras ownClouds utilizando a sintaxe username@example.com/owncloud",
"Share with users or groups …" : "Partilhar com utilizadores ou grupos...",
"Share with users, groups or remote users …" : "Partilhar com utilizadores, grupos ou utilizadores remotos...",
+ "Error removing share" : "Erro ao remover partilha",
"Warning" : "Aviso",
"Error while sending notification" : "Erro enquanto estava a enviar a notificação",
"Non-existing tag #{tag}" : "Etiqueta não existente #{tag}",
"not assignable" : "não atribuível",
"invisible" : "invisível",
+ "({scope})" : "({scope})",
"Delete" : "Apagar",
"Rename" : "Renomear",
"Global tags" : "Etiquetas gerais",
diff --git a/core/l10n/pt_PT.json b/core/l10n/pt_PT.json
index 298e821985b..af02e2bf081 100644
--- a/core/l10n/pt_PT.json
+++ b/core/l10n/pt_PT.json
@@ -162,17 +162,20 @@
"change" : "alterar",
"delete" : "apagar",
"access control" : "controlo de acesso",
+ "Could not unshare" : "Não foi possível cancelar a partilha",
"Share details could not be loaded for this item." : "Não foi possível carregar os detalhes de partilha para este item.",
"An error occured. Please try again" : "Ocorreu um erro. Por favor, tente de novo",
"Share" : "Compartilhar",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Compartilhe com as pessoas nas outras ownClouds utilizando a sintaxe username@example.com/owncloud",
"Share with users or groups …" : "Partilhar com utilizadores ou grupos...",
"Share with users, groups or remote users …" : "Partilhar com utilizadores, grupos ou utilizadores remotos...",
+ "Error removing share" : "Erro ao remover partilha",
"Warning" : "Aviso",
"Error while sending notification" : "Erro enquanto estava a enviar a notificação",
"Non-existing tag #{tag}" : "Etiqueta não existente #{tag}",
"not assignable" : "não atribuível",
"invisible" : "invisível",
+ "({scope})" : "({scope})",
"Delete" : "Apagar",
"Rename" : "Renomear",
"Global tags" : "Etiquetas gerais",
diff --git a/core/l10n/sq.js b/core/l10n/sq.js
index bdef0ba9196..bcdec3b9c73 100644
--- a/core/l10n/sq.js
+++ b/core/l10n/sq.js
@@ -164,17 +164,20 @@ OC.L10N.register(
"change" : "ndërroje",
"delete" : "fshije",
"access control" : "kontroll hyrjesh",
+ "Could not unshare" : "S’e shndau dot",
"Share details could not be loaded for this item." : "Për këtë objekt s’u ngarkuan dot hollësi ndarjeje.",
"An error occured. Please try again" : "Ndodhi një gabim. Ju lutemi, riprovoni",
"Share" : "Ndaje",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Ndajeni me persona në ownCloud-e të tjera duke përdorur sintaksën username@example.com/owncloud",
"Share with users or groups …" : "Ndajeni me përdorues ose grupe …",
"Share with users, groups or remote users …" : "Ndajeni me përdorues, grupe ose përdorues të largët …",
+ "Error removing share" : "Gabim në heqjen e ndarjes",
"Warning" : "Kujdes",
"Error while sending notification" : "Gabim gjatë dërgimit të njoftimit",
"Non-existing tag #{tag}" : "Etiketë #{tag} që s’ekziston",
"not assignable" : "e pacaktueshme",
"invisible" : "e padukshme",
+ "({scope})" : "({scope})",
"Delete" : "Fshije",
"Rename" : "Riemërtoje",
"Global tags" : "Etiketa globale",
diff --git a/core/l10n/sq.json b/core/l10n/sq.json
index 90a455c99aa..3fdd04bd429 100644
--- a/core/l10n/sq.json
+++ b/core/l10n/sq.json
@@ -162,17 +162,20 @@
"change" : "ndërroje",
"delete" : "fshije",
"access control" : "kontroll hyrjesh",
+ "Could not unshare" : "S’e shndau dot",
"Share details could not be loaded for this item." : "Për këtë objekt s’u ngarkuan dot hollësi ndarjeje.",
"An error occured. Please try again" : "Ndodhi një gabim. Ju lutemi, riprovoni",
"Share" : "Ndaje",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Ndajeni me persona në ownCloud-e të tjera duke përdorur sintaksën username@example.com/owncloud",
"Share with users or groups …" : "Ndajeni me përdorues ose grupe …",
"Share with users, groups or remote users …" : "Ndajeni me përdorues, grupe ose përdorues të largët …",
+ "Error removing share" : "Gabim në heqjen e ndarjes",
"Warning" : "Kujdes",
"Error while sending notification" : "Gabim gjatë dërgimit të njoftimit",
"Non-existing tag #{tag}" : "Etiketë #{tag} që s’ekziston",
"not assignable" : "e pacaktueshme",
"invisible" : "e padukshme",
+ "({scope})" : "({scope})",
"Delete" : "Fshije",
"Rename" : "Riemërtoje",
"Global tags" : "Etiketa globale",
diff --git a/lib/l10n/de_DE.js b/lib/l10n/de_DE.js
index 886d107bc7a..b434ac56aad 100644
--- a/lib/l10n/de_DE.js
+++ b/lib/l10n/de_DE.js
@@ -107,8 +107,10 @@ OC.L10N.register(
"Sharing %s failed, because resharing is not allowed" : "Freigabe von %s fehlgeschlagen, da das nochmalige Freigeben einer Freigabe nicht erlaubt ist",
"Sharing %s failed, because the sharing backend for %s could not find its source" : "Freigabe von %s fehlgeschlagen, da das Freigabe-Backend für %s nicht in dieser Quelle gefunden werden konnte",
"Sharing %s failed, because the file could not be found in the file cache" : "Freigabe von %s fehlgeschlagen, da die Datei im Datei-Cache nicht gefunden werden konnte",
+ "Expiration date is in the past" : "Das Ablaufdatum liegt in der Vergangenheit.",
"Could not find category \"%s\"" : "Die Kategorie „%s“ konnte nicht gefunden werden",
"Apps" : "Apps",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Nur die follgenden Zeichen sind im Benutzernamen erlaubt: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"A valid username must be provided" : "Es muss ein gültiger Benutzername angegeben werden",
"A valid password must be provided" : "Es muss ein gültiges Passwort angegeben werden",
"The username is already being used" : "Der Benutzername existiert bereits",
diff --git a/lib/l10n/de_DE.json b/lib/l10n/de_DE.json
index 33a177eedea..09ce050c452 100644
--- a/lib/l10n/de_DE.json
+++ b/lib/l10n/de_DE.json
@@ -105,8 +105,10 @@
"Sharing %s failed, because resharing is not allowed" : "Freigabe von %s fehlgeschlagen, da das nochmalige Freigeben einer Freigabe nicht erlaubt ist",
"Sharing %s failed, because the sharing backend for %s could not find its source" : "Freigabe von %s fehlgeschlagen, da das Freigabe-Backend für %s nicht in dieser Quelle gefunden werden konnte",
"Sharing %s failed, because the file could not be found in the file cache" : "Freigabe von %s fehlgeschlagen, da die Datei im Datei-Cache nicht gefunden werden konnte",
+ "Expiration date is in the past" : "Das Ablaufdatum liegt in der Vergangenheit.",
"Could not find category \"%s\"" : "Die Kategorie „%s“ konnte nicht gefunden werden",
"Apps" : "Apps",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Nur die follgenden Zeichen sind im Benutzernamen erlaubt: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"A valid username must be provided" : "Es muss ein gültiger Benutzername angegeben werden",
"A valid password must be provided" : "Es muss ein gültiges Passwort angegeben werden",
"The username is already being used" : "Der Benutzername existiert bereits",
diff --git a/lib/l10n/ja.js b/lib/l10n/ja.js
index 2de52bad390..e3433a18d7a 100644
--- a/lib/l10n/ja.js
+++ b/lib/l10n/ja.js
@@ -113,8 +113,11 @@ OC.L10N.register(
"Sharing %s failed, because resharing is not allowed" : "%s を共有できませんでした。再共有は許可されていません。",
"Sharing %s failed, because the sharing backend for %s could not find its source" : "%s の共有に失敗しました。%s のバックエンド共有に必要なソースが見つかりませんでした。",
"Sharing %s failed, because the file could not be found in the file cache" : "%s の共有に失敗しました。ファイルキャッシュにファイルがありませんでした。",
+ "Expiration date is in the past" : "有効期限が切れています",
+ "Cannot set expiration date more than %s days in the future" : "有効期限を%s日以降に設定できません。",
"Could not find category \"%s\"" : "カテゴリ \"%s\" が見つかりませんでした",
"Apps" : "アプリ",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ユーザー名で利用できる文字列は、次のものです: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-\"",
"A valid username must be provided" : "有効なユーザー名を指定する必要があります",
"A valid password must be provided" : "有効なパスワードを指定する必要があります",
"The username is already being used" : "ユーザー名はすでに使われています",
diff --git a/lib/l10n/ja.json b/lib/l10n/ja.json
index 42b9b1912c7..a4b18386c7a 100644
--- a/lib/l10n/ja.json
+++ b/lib/l10n/ja.json
@@ -111,8 +111,11 @@
"Sharing %s failed, because resharing is not allowed" : "%s を共有できませんでした。再共有は許可されていません。",
"Sharing %s failed, because the sharing backend for %s could not find its source" : "%s の共有に失敗しました。%s のバックエンド共有に必要なソースが見つかりませんでした。",
"Sharing %s failed, because the file could not be found in the file cache" : "%s の共有に失敗しました。ファイルキャッシュにファイルがありませんでした。",
+ "Expiration date is in the past" : "有効期限が切れています",
+ "Cannot set expiration date more than %s days in the future" : "有効期限を%s日以降に設定できません。",
"Could not find category \"%s\"" : "カテゴリ \"%s\" が見つかりませんでした",
"Apps" : "アプリ",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ユーザー名で利用できる文字列は、次のものです: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-\"",
"A valid username must be provided" : "有効なユーザー名を指定する必要があります",
"A valid password must be provided" : "有効なパスワードを指定する必要があります",
"The username is already being used" : "ユーザー名はすでに使われています",
diff --git a/lib/l10n/nl.js b/lib/l10n/nl.js
index b2427d3f1ee..e49ddc6d27c 100644
--- a/lib/l10n/nl.js
+++ b/lib/l10n/nl.js
@@ -118,6 +118,7 @@ OC.L10N.register(
"Cannot set expiration date more than %s days in the future" : "Kan vervaldatum niet verder dan %s dagen in de toekomst instellen",
"Could not find category \"%s\"" : "Kon categorie \"%s\" niet vinden",
"Apps" : "Apps",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Alleen de volgende tekens zijn toegestaan in een gebruikersnaam: \"a-z\", \"A-Z\", \"0-9\", en \"_.@-\"",
"A valid username must be provided" : "Er moet een geldige gebruikersnaam worden opgegeven",
"A valid password must be provided" : "Er moet een geldig wachtwoord worden opgegeven",
"The username is already being used" : "De gebruikersnaam bestaat al",
diff --git a/lib/l10n/nl.json b/lib/l10n/nl.json
index 094866dc08b..78b54fbbc6d 100644
--- a/lib/l10n/nl.json
+++ b/lib/l10n/nl.json
@@ -116,6 +116,7 @@
"Cannot set expiration date more than %s days in the future" : "Kan vervaldatum niet verder dan %s dagen in de toekomst instellen",
"Could not find category \"%s\"" : "Kon categorie \"%s\" niet vinden",
"Apps" : "Apps",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Alleen de volgende tekens zijn toegestaan in een gebruikersnaam: \"a-z\", \"A-Z\", \"0-9\", en \"_.@-\"",
"A valid username must be provided" : "Er moet een geldige gebruikersnaam worden opgegeven",
"A valid password must be provided" : "Er moet een geldig wachtwoord worden opgegeven",
"The username is already being used" : "De gebruikersnaam bestaat al",
diff --git a/lib/l10n/pt_BR.js b/lib/l10n/pt_BR.js
index a9a282882c6..1ffbdc17a7b 100644
--- a/lib/l10n/pt_BR.js
+++ b/lib/l10n/pt_BR.js
@@ -118,6 +118,7 @@ OC.L10N.register(
"Cannot set expiration date more than %s days in the future" : "Não é possível definir a data de validade mais de %s dias no futuro",
"Could not find category \"%s\"" : "Impossível localizar categoria \"%s\"",
"Apps" : "Aplicações",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Somente os seguintes caracteres são permitidos em um nome de usuário: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"",
"A valid username must be provided" : "Forneça um nome de usuário válido",
"A valid password must be provided" : "Forneça uma senha válida",
"The username is already being used" : "Este nome de usuário já está sendo usado",
diff --git a/lib/l10n/pt_BR.json b/lib/l10n/pt_BR.json
index 0a2ed3628dd..cb0e48af0db 100644
--- a/lib/l10n/pt_BR.json
+++ b/lib/l10n/pt_BR.json
@@ -116,6 +116,7 @@
"Cannot set expiration date more than %s days in the future" : "Não é possível definir a data de validade mais de %s dias no futuro",
"Could not find category \"%s\"" : "Impossível localizar categoria \"%s\"",
"Apps" : "Aplicações",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Somente os seguintes caracteres são permitidos em um nome de usuário: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"",
"A valid username must be provided" : "Forneça um nome de usuário válido",
"A valid password must be provided" : "Forneça uma senha válida",
"The username is already being used" : "Este nome de usuário já está sendo usado",
diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php
index 61a04482431..ff9da88cd81 100644
--- a/lib/private/appframework/dependencyinjection/dicontainer.php
+++ b/lib/private/appframework/dependencyinjection/dicontainer.php
@@ -32,7 +32,6 @@ namespace OC\AppFramework\DependencyInjection;
use OC;
use OC\AppFramework\Http;
-use OC\AppFramework\Http\Request;
use OC\AppFramework\Http\Dispatcher;
use OC\AppFramework\Http\Output;
use OC\AppFramework\Core\API;
@@ -43,8 +42,6 @@ use OC\AppFramework\Middleware\SessionMiddleware;
use OC\AppFramework\Utility\SimpleContainer;
use OCP\AppFramework\IApi;
use OCP\AppFramework\IAppContainer;
-use OCP\AppFramework\Middleware;
-use OCP\IServerContainer;
class DIContainer extends SimpleContainer implements IAppContainer {
@@ -255,6 +252,10 @@ class DIContainer extends SimpleContainer implements IAppContainer {
return $this->getServer()->getSession();
});
+ $this->registerService('OCP\\Security\\IContentSecurityPolicyManager', function($c) {
+ return $this->getServer()->getContentSecurityPolicyManager();
+ });
+
$this->registerService('ServerContainer', function ($c) {
return $this->getServer();
});
@@ -319,7 +320,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$app->getServer()->getLogger(),
$c['AppName'],
$app->isLoggedIn(),
- $app->isAdminUser()
+ $app->isAdminUser(),
+ $app->getServer()->getContentSecurityPolicyManager()
);
});
diff --git a/lib/private/appframework/middleware/security/securitymiddleware.php b/lib/private/appframework/middleware/security/securitymiddleware.php
index 4ef043ad50f..f1ba98254f0 100644
--- a/lib/private/appframework/middleware/security/securitymiddleware.php
+++ b/lib/private/appframework/middleware/security/securitymiddleware.php
@@ -32,6 +32,8 @@ use OC\Appframework\Middleware\Security\Exceptions\CrossSiteRequestForgeryExcept
use OC\Appframework\Middleware\Security\Exceptions\NotAdminException;
use OC\Appframework\Middleware\Security\Exceptions\NotLoggedInException;
use OC\AppFramework\Utility\ControllerMethodReflector;
+use OC\Security\CSP\ContentSecurityPolicyManager;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
@@ -52,15 +54,24 @@ use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
* check fails
*/
class SecurityMiddleware extends Middleware {
-
+ /** @var INavigationManager */
private $navigationManager;
+ /** @var IRequest */
private $request;
+ /** @var ControllerMethodReflector */
private $reflector;
+ /** @var string */
private $appName;
+ /** @var IURLGenerator */
private $urlGenerator;
+ /** @var ILogger */
private $logger;
+ /** @var bool */
private $isLoggedIn;
+ /** @var bool */
private $isAdminUser;
+ /** @var ContentSecurityPolicyManager */
+ private $contentSecurityPolicyManager;
/**
* @param IRequest $request
@@ -71,6 +82,7 @@ class SecurityMiddleware extends Middleware {
* @param string $appName
* @param bool $isLoggedIn
* @param bool $isAdminUser
+ * @param ContentSecurityPolicyManager $contentSecurityPolicyManager
*/
public function __construct(IRequest $request,
ControllerMethodReflector $reflector,
@@ -79,7 +91,8 @@ class SecurityMiddleware extends Middleware {
ILogger $logger,
$appName,
$isLoggedIn,
- $isAdminUser) {
+ $isAdminUser,
+ ContentSecurityPolicyManager $contentSecurityPolicyManager) {
$this->navigationManager = $navigationManager;
$this->request = $request;
$this->reflector = $reflector;
@@ -88,6 +101,7 @@ class SecurityMiddleware extends Middleware {
$this->logger = $logger;
$this->isLoggedIn = $isLoggedIn;
$this->isAdminUser = $isAdminUser;
+ $this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
}
@@ -139,6 +153,25 @@ class SecurityMiddleware extends Middleware {
}
+ /**
+ * Performs the default CSP modifications that may be injected by other
+ * applications
+ *
+ * @param Controller $controller
+ * @param string $methodName
+ * @param Response $response
+ * @return Response
+ */
+ public function afterController($controller, $methodName, Response $response) {
+ $policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy();
+
+ $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
+ $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
+
+ $response->setContentSecurityPolicy($defaultPolicy);
+
+ return $response;
+ }
/**
* If an SecurityException is being caught, ajax requests return a JSON error
diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php
index 527c8b76e52..30e00b6080c 100644
--- a/lib/private/files/cache/cache.php
+++ b/lib/private/files/cache/cache.php
@@ -49,6 +49,10 @@ use OCP\IDBConnection;
* - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
*/
class Cache implements ICache {
+ use MoveFromCacheTrait {
+ MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
+ }
+
/**
* @var array partial data for the cache
*/
@@ -466,39 +470,42 @@ class Cache implements ICache {
* @throws \OC\DatabaseException
*/
public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
- // normalize source and target
- $sourcePath = $this->normalize($sourcePath);
- $targetPath = $this->normalize($targetPath);
-
- $sourceData = $sourceCache->get($sourcePath);
- $sourceId = $sourceData['fileid'];
- $newParentId = $this->getParentId($targetPath);
-
- list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
- list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
-
- // sql for final update
- $moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
-
- if ($sourceData['mimetype'] === 'httpd/unix-directory') {
- //find all child entries
- $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
- $result = $this->connection->executeQuery($sql, [$sourceStorageId, $this->connection->escapeLikeParameter($sourcePath) . '/%']);
- $childEntries = $result->fetchAll();
- $sourceLength = strlen($sourcePath);
- $this->connection->beginTransaction();
- $query = $this->connection->prepare('UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?');
-
- foreach ($childEntries as $child) {
- $newTargetPath = $targetPath . substr($child['path'], $sourceLength);
- $query->execute([$targetStorageId, $newTargetPath, md5($newTargetPath), $child['fileid']]);
+ if ($sourceCache instanceof Cache) {
+ // normalize source and target
+ $sourcePath = $this->normalize($sourcePath);
+ $targetPath = $this->normalize($targetPath);
+
+ $sourceData = $sourceCache->get($sourcePath);
+ $sourceId = $sourceData['fileid'];
+ $newParentId = $this->getParentId($targetPath);
+
+ list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
+ list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
+
+ // sql for final update
+ $moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
+
+ if ($sourceData['mimetype'] === 'httpd/unix-directory') {
+ //find all child entries
+ $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
+ $result = $this->connection->executeQuery($sql, [$sourceStorageId, $this->connection->escapeLikeParameter($sourcePath) . '/%']);
+ $childEntries = $result->fetchAll();
+ $sourceLength = strlen($sourcePath);
+ $this->connection->beginTransaction();
+ $query = $this->connection->prepare('UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?');
+
+ foreach ($childEntries as $child) {
+ $newTargetPath = $targetPath . substr($child['path'], $sourceLength);
+ $query->execute([$targetStorageId, $newTargetPath, md5($newTargetPath), $child['fileid']]);
+ }
+ $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
+ $this->connection->commit();
+ } else {
+ $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
}
- $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
- $this->connection->commit();
} else {
- $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
+ $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
}
-
}
/**
diff --git a/lib/private/files/cache/movefromcachetrait.php b/lib/private/files/cache/movefromcachetrait.php
new file mode 100644
index 00000000000..7d8ed7b5d21
--- /dev/null
+++ b/lib/private/files/cache/movefromcachetrait.php
@@ -0,0 +1,87 @@
+<?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 OC\Files\Cache;
+
+use OCP\Files\Cache\ICache;
+use OCP\Files\Cache\ICacheEntry;
+
+/**
+ * Fallback implementation for moveFromCache
+ */
+trait MoveFromCacheTrait {
+ /**
+ * store meta data for a file or folder
+ *
+ * @param string $file
+ * @param array $data
+ *
+ * @return int file id
+ * @throws \RuntimeException
+ */
+ abstract public function put($file, array $data);
+
+ /**
+ * Move a file or folder in the cache
+ *
+ * @param \OCP\Files\Cache\ICache $sourceCache
+ * @param string $sourcePath
+ * @param string $targetPath
+ */
+ public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
+ $sourceEntry = $sourceCache->get($sourcePath);
+
+ $this->copyFromCache($sourceCache, $sourceEntry, $targetPath);
+
+ $sourceCache->remove($sourcePath);
+ }
+
+ /**
+ * Copy a file or folder in the cache
+ *
+ * @param \OCP\Files\Cache\ICache $sourceCache
+ * @param ICacheEntry $sourceEntry
+ * @param string $targetPath
+ */
+ public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, $targetPath) {
+ $this->put($targetPath, $this->cacheEntryToArray($sourceEntry));
+ if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
+ $folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId());
+ foreach ($folderContent as $subEntry) {
+ $subTargetPath = $targetPath . '/' . $subEntry->getName();
+ $this->copyFromCache($sourceCache, $subEntry, $subTargetPath);
+ }
+ }
+ }
+
+ private function cacheEntryToArray(ICacheEntry $entry) {
+ return [
+ 'size' => $entry->getSize(),
+ 'mtime' => $entry->getMTime(),
+ 'storage_mtime' => $entry->getStorageMTime(),
+ 'mimetype' => $entry->getMimeType(),
+ 'mimepart' => $entry->getMimePart(),
+ 'etag' => $entry->getEtag(),
+ 'permissions' => $entry->getPermissions(),
+ 'encrypted' => $entry->isEncrypted()
+ ];
+ }
+}
diff --git a/lib/private/security/csp/contentsecuritypolicy.php b/lib/private/security/csp/contentsecuritypolicy.php
new file mode 100644
index 00000000000..25eacfab1d6
--- /dev/null
+++ b/lib/private/security/csp/contentsecuritypolicy.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@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 OC\Security\CSP;
+
+/**
+ * Class ContentSecurityPolicy extends the public class and adds getter and setters.
+ * This is necessary since we don't want to expose the setters and getters to the
+ * public API.
+ *
+ * @package OC\Security\CSP
+ */
+class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy {
+ /**
+ * @return boolean
+ */
+ public function isInlineScriptAllowed() {
+ return $this->inlineScriptAllowed;
+ }
+
+ /**
+ * @param boolean $inlineScriptAllowed
+ */
+ public function setInlineScriptAllowed($inlineScriptAllowed) {
+ $this->inlineScriptAllowed = $inlineScriptAllowed;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isEvalScriptAllowed() {
+ return $this->evalScriptAllowed;
+ }
+
+ /**
+ * @param boolean $evalScriptAllowed
+ */
+ public function setEvalScriptAllowed($evalScriptAllowed) {
+ $this->evalScriptAllowed = $evalScriptAllowed;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedScriptDomains() {
+ return $this->allowedScriptDomains;
+ }
+
+ /**
+ * @param array $allowedScriptDomains
+ */
+ public function setAllowedScriptDomains($allowedScriptDomains) {
+ $this->allowedScriptDomains = $allowedScriptDomains;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isInlineStyleAllowed() {
+ return $this->inlineStyleAllowed;
+ }
+
+ /**
+ * @param boolean $inlineStyleAllowed
+ */
+ public function setInlineStyleAllowed($inlineStyleAllowed) {
+ $this->inlineStyleAllowed = $inlineStyleAllowed;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedStyleDomains() {
+ return $this->allowedStyleDomains;
+ }
+
+ /**
+ * @param array $allowedStyleDomains
+ */
+ public function setAllowedStyleDomains($allowedStyleDomains) {
+ $this->allowedStyleDomains = $allowedStyleDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedImageDomains() {
+ return $this->allowedImageDomains;
+ }
+
+ /**
+ * @param array $allowedImageDomains
+ */
+ public function setAllowedImageDomains($allowedImageDomains) {
+ $this->allowedImageDomains = $allowedImageDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedConnectDomains() {
+ return $this->allowedConnectDomains;
+ }
+
+ /**
+ * @param array $allowedConnectDomains
+ */
+ public function setAllowedConnectDomains($allowedConnectDomains) {
+ $this->allowedConnectDomains = $allowedConnectDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedMediaDomains() {
+ return $this->allowedMediaDomains;
+ }
+
+ /**
+ * @param array $allowedMediaDomains
+ */
+ public function setAllowedMediaDomains($allowedMediaDomains) {
+ $this->allowedMediaDomains = $allowedMediaDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedObjectDomains() {
+ return $this->allowedObjectDomains;
+ }
+
+ /**
+ * @param array $allowedObjectDomains
+ */
+ public function setAllowedObjectDomains($allowedObjectDomains) {
+ $this->allowedObjectDomains = $allowedObjectDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedFrameDomains() {
+ return $this->allowedFrameDomains;
+ }
+
+ /**
+ * @param array $allowedFrameDomains
+ */
+ public function setAllowedFrameDomains($allowedFrameDomains) {
+ $this->allowedFrameDomains = $allowedFrameDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedFontDomains() {
+ return $this->allowedFontDomains;
+ }
+
+ /**
+ * @param array $allowedFontDomains
+ */
+ public function setAllowedFontDomains($allowedFontDomains) {
+ $this->allowedFontDomains = $allowedFontDomains;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedChildSrcDomains() {
+ return $this->allowedChildSrcDomains;
+ }
+
+ /**
+ * @param array $allowedChildSrcDomains
+ */
+ public function setAllowedChildSrcDomains($allowedChildSrcDomains) {
+ $this->allowedChildSrcDomains = $allowedChildSrcDomains;
+ }
+
+}
diff --git a/lib/private/security/csp/contentsecuritypolicymanager.php b/lib/private/security/csp/contentsecuritypolicymanager.php
new file mode 100644
index 00000000000..760cd36e56b
--- /dev/null
+++ b/lib/private/security/csp/contentsecuritypolicymanager.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@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 OC\Security\CSP;
+
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
+use OCP\Security\IContentSecurityPolicyManager;
+
+class ContentSecurityPolicyManager implements IContentSecurityPolicyManager {
+ /** @var ContentSecurityPolicy[] */
+ private $policies = [];
+
+ /** {@inheritdoc} */
+ public function addDefaultPolicy(EmptyContentSecurityPolicy $policy) {
+ $this->policies[] = $policy;
+ }
+
+ /**
+ * Get the configured default policy. This is not in the public namespace
+ * as it is only supposed to be used by core itself.
+ *
+ * @return ContentSecurityPolicy
+ */
+ public function getDefaultPolicy() {
+ $defaultPolicy = new \OC\Security\CSP\ContentSecurityPolicy();
+ foreach($this->policies as $policy) {
+ $defaultPolicy = $this->mergePolicies($defaultPolicy, $policy);
+ }
+ return $defaultPolicy;
+ }
+
+ /**
+ * Merges the first given policy with the second one
+ *
+ * @param ContentSecurityPolicy $defaultPolicy
+ * @param EmptyContentSecurityPolicy $originalPolicy
+ * @return ContentSecurityPolicy
+ */
+ public function mergePolicies(ContentSecurityPolicy $defaultPolicy,
+ EmptyContentSecurityPolicy $originalPolicy) {
+ foreach((object)(array)$originalPolicy as $name => $value) {
+ $setter = 'set'.ucfirst($name);
+ if(is_array($value)) {
+ $getter = 'get'.ucfirst($name);
+ $currentValues = is_array($defaultPolicy->$getter()) ? $defaultPolicy->$getter() : [];
+ $defaultPolicy->$setter(array_values(array_unique(array_merge($currentValues, $value))));
+ } elseif (is_bool($value)) {
+ $defaultPolicy->$setter($value);
+ }
+ }
+
+ return $defaultPolicy;
+ }
+}
diff --git a/lib/private/server.php b/lib/private/server.php
index d453a42d3a0..d3dbcba86ba 100644
--- a/lib/private/server.php
+++ b/lib/private/server.php
@@ -63,6 +63,7 @@ use OC\Lock\NoopLockingProvider;
use OC\Mail\Mailer;
use OC\Notification\Manager;
use OC\Security\CertificateManager;
+use OC\Security\CSP\ContentSecurityPolicyManager;
use OC\Security\Crypto;
use OC\Security\CSRF\CsrfTokenGenerator;
use OC\Security\CSRF\CsrfTokenManager;
@@ -74,6 +75,7 @@ use OC\Security\TrustedDomainHelper;
use OC\Session\CryptoWrapper;
use OC\Tagging\TagMapper;
use OCP\IServerContainer;
+use OCP\Security\IContentSecurityPolicyManager;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -598,6 +600,9 @@ class Server extends ServerContainer implements IServerContainer {
$sessionStorage
);
});
+ $this->registerService('ContentSecurityPolicyManager', function (Server $c) {
+ return new ContentSecurityPolicyManager();
+ });
$this->registerService('ShareManager', function(Server $c) {
$config = $c->getConfig();
$factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory');
@@ -1221,6 +1226,13 @@ class Server extends ServerContainer implements IServerContainer {
}
/**
+ * @return IContentSecurityPolicyManager
+ */
+ public function getContentSecurityPolicyManager() {
+ return $this->query('ContentSecurityPolicyManager');
+ }
+
+ /**
* Not a public API as of 8.2, wait for 9.0
*
* @return \OCA\Files_External\Service\BackendService
diff --git a/lib/private/share20/defaultshareprovider.php b/lib/private/share20/defaultshareprovider.php
index 35b3f71f3de..0fa1552a1e7 100644
--- a/lib/private/share20/defaultshareprovider.php
+++ b/lib/private/share20/defaultshareprovider.php
@@ -518,16 +518,9 @@ class DefaultShareProvider implements IShareProvider {
}
/**
- * Get shared with the given user
- *
- * @param IUser $user get shares where this user is the recipient
- * @param int $shareType \OCP\Share::SHARE_TYPE_USER or \OCP\Share::SHARE_TYPE_GROUP are supported
- * @param int $limit The maximum number of shares, -1 for all
- * @param int $offset
- * @return IShare[]
- * @throws BackendError
+ * @inheritdoc
*/
- public function getSharedWith(IUser $user, $shareType, $limit, $offset) {
+ public function getSharedWith(IUser $user, $shareType, $node, $limit, $offset) {
/** @var Share[] $shares */
$shares = [];
@@ -549,6 +542,11 @@ class DefaultShareProvider implements IShareProvider {
$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)));
$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($user->getUID())));
+ // 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()) {
@@ -581,9 +579,14 @@ class DefaultShareProvider implements IShareProvider {
$qb->setMaxResults($limit - count($shares));
}
+ // Filter by node if provided
+ if ($node !== null) {
+ $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
+ }
+
$groups = array_map(function(IGroup $group) { return $group->getGID(); }, $groups);
- $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)));
+ $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)));
$qb->andWhere($qb->expr()->in('share_with', $qb->createNamedParameter(
$groups,
IQueryBuilder::PARAM_STR_ARRAY
diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php
index d6245f4beac..e45fa4b40f9 100644
--- a/lib/private/share20/manager.php
+++ b/lib/private/share20/manager.php
@@ -21,6 +21,7 @@
namespace OC\Share20;
+use OCP\Files\Node;
use OCP\Share\IManager;
use OCP\Share\IProviderFactory;
use OC\Share20\Exception\BackendError;
@@ -216,7 +217,9 @@ class Manager implements IManager {
* @return \DateTime|null The expiration date or null if $expireDate was null and it is not required
* @throws \OC\HintException
*/
- protected function validateExpirationDate($expirationDate) {
+ protected function validateExpirationDate(\OCP\Share\IShare $share) {
+
+ $expirationDate = $share->getExpirationDate();
if ($expirationDate !== null) {
//Make sure the expiration date is a date
@@ -243,18 +246,30 @@ class Manager implements IManager {
$message = $this->l->t('Cannot set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
throw new \OC\HintException($message, $message, 404);
}
-
- return $expirationDate;
}
// If expiredate is empty set a default one if there is a default
if ($expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
- $date = new \DateTime();
- $date->setTime(0,0,0);
- $date->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
- return $date;
+ $expirationDate = new \DateTime();
+ $expirationDate->setTime(0,0,0);
+ $expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
}
+ $accepted = true;
+ $message = '';
+ \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
+ 'expirationDate' => &$expirationDate,
+ 'accepted' => &$accepted,
+ 'message' => &$message,
+ 'passwordSet' => $share->getPassword() === null,
+ ]);
+
+ if (!$accepted) {
+ throw new \Exception($message);
+ }
+
+ $share->setExpirationDate($expirationDate);
+
return $expirationDate;
}
@@ -435,7 +450,7 @@ class Manager implements IManager {
);
//Verify the expiration date
- $share->setExpirationDate($this->validateExpirationDate($share->getExpirationDate()));
+ $this->validateExpirationDate($share);
//Verify the password
$this->verifyPassword($share->getPassword());
@@ -572,7 +587,7 @@ class Manager implements IManager {
if ($share->getExpirationDate() !== $originalShare->getExpirationDate()) {
//Verify the expiration date
- $share->setExpirationDate($this->validateExpirationDate($share->getExpirationDate()));
+ $this->validateExpirationDate($share);
$expirationDateUpdated = true;
}
}
@@ -722,18 +737,12 @@ class Manager implements IManager {
}
/**
- * Get shares shared with $user.
- *
- * @param IUser $user
- * @param int $shareType
- * @param int $limit The maximum number of shares returned, -1 for all
- * @param int $offset
- * @return \OCP\Share\IShare[]
+ * @inheritdoc
*/
- public function getSharedWith(IUser $user, $shareType, $limit = 50, $offset = 0) {
+ public function getSharedWith(IUser $user, $shareType, $node = null, $limit = 50, $offset = 0) {
$provider = $this->factory->getProviderForType($shareType);
- return $provider->getSharedWith($user, $shareType, $limit, $offset);
+ return $provider->getSharedWith($user, $shareType, $node, $limit, $offset);
}
/**
diff --git a/lib/private/templatelayout.php b/lib/private/templatelayout.php
index 7f17ab15335..bc66c0dfb1e 100644
--- a/lib/private/templatelayout.php
+++ b/lib/private/templatelayout.php
@@ -130,7 +130,7 @@ class TemplateLayout extends \OC_Template {
$this->assign('user_displayname', $userDisplayName);
$this->assign('user_uid', \OC_User::getUser());
$this->assign('appsmanagement_active', $appsMgmtActive);
- $this->assign('enableAvatars', $this->config->getSystemValue('enable_avatars', true));
+ $this->assign('enableAvatars', $this->config->getSystemValue('enable_avatars', true) === true);
if (\OC_User::getUser() === false) {
$this->assign('userAvatarSet', false);
diff --git a/lib/public/appframework/controller.php b/lib/public/appframework/controller.php
index 973c9044684..c3744683300 100644
--- a/lib/public/appframework/controller.php
+++ b/lib/public/appframework/controller.php
@@ -72,7 +72,7 @@ abstract class Controller {
* @since 6.0.0 - parameter $appName was added in 7.0.0 - parameter $app was removed in 7.0.0
*/
public function __construct($appName,
- IRequest $request){
+ IRequest $request) {
$this->appName = $appName;
$this->request = $request;
diff --git a/lib/public/appframework/http/contentsecuritypolicy.php b/lib/public/appframework/http/contentsecuritypolicy.php
index 35da4f05e80..7762ca809a2 100644
--- a/lib/public/appframework/http/contentsecuritypolicy.php
+++ b/lib/public/appframework/http/contentsecuritypolicy.php
@@ -38,17 +38,17 @@ use OCP\AppFramework\Http;
* @package OCP\AppFramework\Http
* @since 8.1.0
*/
-class ContentSecurityPolicy {
+class ContentSecurityPolicy extends EmptyContentSecurityPolicy {
/** @var bool Whether inline JS snippets are allowed */
- private $inlineScriptAllowed = false;
+ protected $inlineScriptAllowed = false;
/**
* @var bool Whether eval in JS scripts is allowed
* TODO: Disallow per default
* @link https://github.com/owncloud/core/issues/11925
*/
- private $evalScriptAllowed = true;
+ protected $evalScriptAllowed = true;
/** @var array Domains from which scripts can get loaded */
- private $allowedScriptDomains = [
+ protected $allowedScriptDomains = [
'\'self\'',
];
/**
@@ -56,342 +56,33 @@ class ContentSecurityPolicy {
* TODO: Disallow per default
* @link https://github.com/owncloud/core/issues/13458
*/
- private $inlineStyleAllowed = true;
+ protected $inlineStyleAllowed = true;
/** @var array Domains from which CSS can get loaded */
- private $allowedStyleDomains = [
+ protected $allowedStyleDomains = [
'\'self\'',
];
/** @var array Domains from which images can get loaded */
- private $allowedImageDomains = [
+ protected $allowedImageDomains = [
'\'self\'',
'data:',
'blob:',
];
/** @var array Domains to which connections can be done */
- private $allowedConnectDomains = [
+ protected $allowedConnectDomains = [
'\'self\'',
];
/** @var array Domains from which media elements can be loaded */
- private $allowedMediaDomains = [
+ protected $allowedMediaDomains = [
'\'self\'',
];
/** @var array Domains from which object elements can be loaded */
- private $allowedObjectDomains = [];
+ protected $allowedObjectDomains = [];
/** @var array Domains from which iframes can be loaded */
- private $allowedFrameDomains = [];
+ protected $allowedFrameDomains = [];
/** @var array Domains from which fonts can be loaded */
- private $allowedFontDomains = [
+ protected $allowedFontDomains = [
'\'self\'',
];
/** @var array Domains from which web-workers and nested browsing content can load elements */
- private $allowedChildSrcDomains = [];
-
- /**
- * Whether inline JavaScript snippets are allowed or forbidden
- * @param bool $state
- * @return $this
- * @since 8.1.0
- */
- public function allowInlineScript($state = false) {
- $this->inlineScriptAllowed = $state;
- return $this;
- }
-
- /**
- * Whether eval in JavaScript is allowed or forbidden
- * @param bool $state
- * @return $this
- * @since 8.1.0
- */
- public function allowEvalScript($state = true) {
- $this->evalScriptAllowed = $state;
- return $this;
- }
-
- /**
- * Allows to execute JavaScript files from a specific domain. Use * to
- * allow JavaScript from all domains.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedScriptDomain($domain) {
- $this->allowedScriptDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed script domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowScriptDomain($domain) {
- $this->allowedScriptDomains = array_diff($this->allowedScriptDomains, [$domain]);
- return $this;
- }
-
- /**
- * Whether inline CSS snippets are allowed or forbidden
- * @param bool $state
- * @return $this
- * @since 8.1.0
- */
- public function allowInlineStyle($state = true) {
- $this->inlineStyleAllowed = $state;
- return $this;
- }
-
- /**
- * Allows to execute CSS files from a specific domain. Use * to allow
- * CSS from all domains.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedStyleDomain($domain) {
- $this->allowedStyleDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed style domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowStyleDomain($domain) {
- $this->allowedStyleDomains = array_diff($this->allowedStyleDomains, [$domain]);
- return $this;
- }
-
- /**
- * Allows using fonts from a specific domain. Use * to allow
- * fonts from all domains.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedFontDomain($domain) {
- $this->allowedFontDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed font domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowFontDomain($domain) {
- $this->allowedFontDomains = array_diff($this->allowedFontDomains, [$domain]);
- return $this;
- }
-
- /**
- * Allows embedding images from a specific domain. Use * to allow
- * images from all domains.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedImageDomain($domain) {
- $this->allowedImageDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed image domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowImageDomain($domain) {
- $this->allowedImageDomains = array_diff($this->allowedImageDomains, [$domain]);
- return $this;
- }
-
- /**
- * To which remote domains the JS connect to.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedConnectDomain($domain) {
- $this->allowedConnectDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed connect domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowConnectDomain($domain) {
- $this->allowedConnectDomains = array_diff($this->allowedConnectDomains, [$domain]);
- return $this;
- }
-
- /**
- * From which domains media elements can be embedded.
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedMediaDomain($domain) {
- $this->allowedMediaDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed media domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowMediaDomain($domain) {
- $this->allowedMediaDomains = array_diff($this->allowedMediaDomains, [$domain]);
- return $this;
- }
-
- /**
- * From which domains objects such as <object>, <embed> or <applet> are executed
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedObjectDomain($domain) {
- $this->allowedObjectDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed object domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowObjectDomain($domain) {
- $this->allowedObjectDomains = array_diff($this->allowedObjectDomains, [$domain]);
- return $this;
- }
-
- /**
- * Which domains can be embedded in an iframe
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedFrameDomain($domain) {
- $this->allowedFrameDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed frame domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowFrameDomain($domain) {
- $this->allowedFrameDomains = array_diff($this->allowedFrameDomains, [$domain]);
- return $this;
- }
-
- /**
- * Domains from which web-workers and nested browsing content can load elements
- * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
- * @return $this
- * @since 8.1.0
- */
- public function addAllowedChildSrcDomain($domain) {
- $this->allowedChildSrcDomains[] = $domain;
- return $this;
- }
-
- /**
- * Remove the specified allowed child src domain from the allowed domains.
- *
- * @param string $domain
- * @return $this
- * @since 8.1.0
- */
- public function disallowChildSrcDomain($domain) {
- $this->allowedChildSrcDomains = array_diff($this->allowedChildSrcDomains, [$domain]);
- return $this;
- }
-
- /**
- * Get the generated Content-Security-Policy as a string
- * @return string
- * @since 8.1.0
- */
- public function buildPolicy() {
- $policy = "default-src 'none';";
-
- if(!empty($this->allowedScriptDomains)) {
- $policy .= 'script-src ' . implode(' ', $this->allowedScriptDomains);
- if($this->inlineScriptAllowed) {
- $policy .= ' \'unsafe-inline\'';
- }
- if($this->evalScriptAllowed) {
- $policy .= ' \'unsafe-eval\'';
- }
- $policy .= ';';
- }
-
- if(!empty($this->allowedStyleDomains)) {
- $policy .= 'style-src ' . implode(' ', $this->allowedStyleDomains);
- if($this->inlineStyleAllowed) {
- $policy .= ' \'unsafe-inline\'';
- }
- $policy .= ';';
- }
-
- if(!empty($this->allowedImageDomains)) {
- $policy .= 'img-src ' . implode(' ', $this->allowedImageDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedFontDomains)) {
- $policy .= 'font-src ' . implode(' ', $this->allowedFontDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedConnectDomains)) {
- $policy .= 'connect-src ' . implode(' ', $this->allowedConnectDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedMediaDomains)) {
- $policy .= 'media-src ' . implode(' ', $this->allowedMediaDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedObjectDomains)) {
- $policy .= 'object-src ' . implode(' ', $this->allowedObjectDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedFrameDomains)) {
- $policy .= 'frame-src ' . implode(' ', $this->allowedFrameDomains);
- $policy .= ';';
- }
-
- if(!empty($this->allowedChildSrcDomains)) {
- $policy .= 'child-src ' . implode(' ', $this->allowedChildSrcDomains);
- $policy .= ';';
- }
-
- return rtrim($policy, ';');
- }
+ protected $allowedChildSrcDomains = [];
}
diff --git a/lib/public/appframework/http/emptycontentsecuritypolicy.php b/lib/public/appframework/http/emptycontentsecuritypolicy.php
new file mode 100644
index 00000000000..33860dcdb0f
--- /dev/null
+++ b/lib/public/appframework/http/emptycontentsecuritypolicy.php
@@ -0,0 +1,387 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author sualko <klaus@jsxc.org>
+ *
+ * @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 OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class EmptyContentSecurityPolicy is a simple helper which allows applications
+ * to modify the Content-Security-Policy sent by ownCloud. Per default the policy
+ * is forbidding everything.
+ *
+ * As alternative with sane exemptions look at ContentSecurityPolicy
+ *
+ * @see \OCP\AppFramework\Http\ContentSecurityPolicy
+ * @package OCP\AppFramework\Http
+ * @since 9.0.0
+ */
+class EmptyContentSecurityPolicy {
+ /** @var bool Whether inline JS snippets are allowed */
+ protected $inlineScriptAllowed = null;
+ /**
+ * @var bool Whether eval in JS scripts is allowed
+ * TODO: Disallow per default
+ * @link https://github.com/owncloud/core/issues/11925
+ */
+ protected $evalScriptAllowed = null;
+ /** @var array Domains from which scripts can get loaded */
+ protected $allowedScriptDomains = null;
+ /**
+ * @var bool Whether inline CSS is allowed
+ * TODO: Disallow per default
+ * @link https://github.com/owncloud/core/issues/13458
+ */
+ protected $inlineStyleAllowed = null;
+ /** @var array Domains from which CSS can get loaded */
+ protected $allowedStyleDomains = null;
+ /** @var array Domains from which images can get loaded */
+ protected $allowedImageDomains = null;
+ /** @var array Domains to which connections can be done */
+ protected $allowedConnectDomains = null;
+ /** @var array Domains from which media elements can be loaded */
+ protected $allowedMediaDomains = null;
+ /** @var array Domains from which object elements can be loaded */
+ protected $allowedObjectDomains = null;
+ /** @var array Domains from which iframes can be loaded */
+ protected $allowedFrameDomains = null;
+ /** @var array Domains from which fonts can be loaded */
+ protected $allowedFontDomains = null;
+ /** @var array Domains from which web-workers and nested browsing content can load elements */
+ protected $allowedChildSrcDomains = null;
+
+ /**
+ * Whether inline JavaScript snippets are allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 8.1.0
+ */
+ public function allowInlineScript($state = false) {
+ $this->inlineScriptAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Whether eval in JavaScript is allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 8.1.0
+ */
+ public function allowEvalScript($state = true) {
+ $this->evalScriptAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Allows to execute JavaScript files from a specific domain. Use * to
+ * allow JavaScript from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedScriptDomain($domain) {
+ $this->allowedScriptDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed script domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowScriptDomain($domain) {
+ $this->allowedScriptDomains = array_diff($this->allowedScriptDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Whether inline CSS snippets are allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 8.1.0
+ */
+ public function allowInlineStyle($state = true) {
+ $this->inlineStyleAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Allows to execute CSS files from a specific domain. Use * to allow
+ * CSS from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedStyleDomain($domain) {
+ $this->allowedStyleDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed style domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowStyleDomain($domain) {
+ $this->allowedStyleDomains = array_diff($this->allowedStyleDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Allows using fonts from a specific domain. Use * to allow
+ * fonts from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedFontDomain($domain) {
+ $this->allowedFontDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed font domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowFontDomain($domain) {
+ $this->allowedFontDomains = array_diff($this->allowedFontDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Allows embedding images from a specific domain. Use * to allow
+ * images from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedImageDomain($domain) {
+ $this->allowedImageDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed image domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowImageDomain($domain) {
+ $this->allowedImageDomains = array_diff($this->allowedImageDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * To which remote domains the JS connect to.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedConnectDomain($domain) {
+ $this->allowedConnectDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed connect domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowConnectDomain($domain) {
+ $this->allowedConnectDomains = array_diff($this->allowedConnectDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * From which domains media elements can be embedded.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedMediaDomain($domain) {
+ $this->allowedMediaDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed media domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowMediaDomain($domain) {
+ $this->allowedMediaDomains = array_diff($this->allowedMediaDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * From which domains objects such as <object>, <embed> or <applet> are executed
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedObjectDomain($domain) {
+ $this->allowedObjectDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed object domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowObjectDomain($domain) {
+ $this->allowedObjectDomains = array_diff($this->allowedObjectDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Which domains can be embedded in an iframe
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedFrameDomain($domain) {
+ $this->allowedFrameDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed frame domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowFrameDomain($domain) {
+ $this->allowedFrameDomains = array_diff($this->allowedFrameDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Domains from which web-workers and nested browsing content can load elements
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedChildSrcDomain($domain) {
+ $this->allowedChildSrcDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed child src domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowChildSrcDomain($domain) {
+ $this->allowedChildSrcDomains = array_diff($this->allowedChildSrcDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Get the generated Content-Security-Policy as a string
+ * @return string
+ * @since 8.1.0
+ */
+ public function buildPolicy() {
+ $policy = "default-src 'none';";
+
+ if(!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
+ $policy .= 'script-src ';
+ if(is_array($this->allowedScriptDomains)) {
+ $policy .= implode(' ', $this->allowedScriptDomains);
+ }
+ if($this->inlineScriptAllowed) {
+ $policy .= ' \'unsafe-inline\'';
+ }
+ if($this->evalScriptAllowed) {
+ $policy .= ' \'unsafe-eval\'';
+ }
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedStyleDomains) || $this->inlineStyleAllowed) {
+ $policy .= 'style-src ';
+ if(is_array($this->allowedStyleDomains)) {
+ $policy .= implode(' ', $this->allowedStyleDomains);
+ }
+ if($this->inlineStyleAllowed) {
+ $policy .= ' \'unsafe-inline\'';
+ }
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedImageDomains)) {
+ $policy .= 'img-src ' . implode(' ', $this->allowedImageDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedFontDomains)) {
+ $policy .= 'font-src ' . implode(' ', $this->allowedFontDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedConnectDomains)) {
+ $policy .= 'connect-src ' . implode(' ', $this->allowedConnectDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedMediaDomains)) {
+ $policy .= 'media-src ' . implode(' ', $this->allowedMediaDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedObjectDomains)) {
+ $policy .= 'object-src ' . implode(' ', $this->allowedObjectDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedFrameDomains)) {
+ $policy .= 'frame-src ' . implode(' ', $this->allowedFrameDomains);
+ $policy .= ';';
+ }
+
+ if(!empty($this->allowedChildSrcDomains)) {
+ $policy .= 'child-src ' . implode(' ', $this->allowedChildSrcDomains);
+ $policy .= ';';
+ }
+
+ return rtrim($policy, ';');
+ }
+}
diff --git a/lib/public/files/cache/icache.php b/lib/public/files/cache/icache.php
index 3fbf310148d..e80c6fa2cb0 100644
--- a/lib/public/files/cache/icache.php
+++ b/lib/public/files/cache/icache.php
@@ -51,7 +51,7 @@ interface ICache {
* get the stored metadata of a file or folder
*
* @param string | int $file either the path of a file or folder or the file id for a file or folder
- * @return ICacheEntry[]|false the cache entry or false if the file is not found in the cache
+ * @return ICacheEntry|false the cache entry or false if the file is not found in the cache
* @since 9.0.0
*/
public function get($file);
@@ -148,6 +148,8 @@ interface ICache {
/**
* Move a file or folder in the cache
*
+ * Note that this should make sure the entries are removed from the source cache
+ *
* @param \OCP\Files\Cache\ICache $sourceCache
* @param string $sourcePath
* @param string $targetPath
diff --git a/lib/public/files/cache/icacheentry.php b/lib/public/files/cache/icacheentry.php
index 8d14bd2c555..63a232c9618 100644
--- a/lib/public/files/cache/icacheentry.php
+++ b/lib/public/files/cache/icacheentry.php
@@ -27,6 +27,8 @@ namespace OCP\Files\Cache;
* @since 9.0.0
*/
interface ICacheEntry {
+ const DIRECTORY_MIMETYPE = 'httpd/unix-directory';
+
/**
* Get the numeric id of a file
*
diff --git a/lib/public/files/storagenotavailableexception.php b/lib/public/files/storagenotavailableexception.php
index 4572a69f047..f9ac79d66ec 100644
--- a/lib/public/files/storagenotavailableexception.php
+++ b/lib/public/files/storagenotavailableexception.php
@@ -54,7 +54,7 @@ class StorageNotAvailableException extends HintException {
* @param \Exception $previous
* @since 6.0.0
*/
- public function __construct($message = '', $code = 0, \Exception $previous = null) {
+ public function __construct($message = '', $code = self::STATUS_ERROR, \Exception $previous = null) {
$l = \OC::$server->getL10N('core');
parent::__construct($message, $l->t('Storage not available'), $code, $previous);
}
diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php
index ce1364cc4ea..de48daeef88 100644
--- a/lib/public/iservercontainer.php
+++ b/lib/public/iservercontainer.php
@@ -42,6 +42,7 @@
// use OCP namespace for all classes that are considered public.
// This means that they should be used by apps instead of the internal ownCloud classes
namespace OCP;
+use OCP\Security\IContentSecurityPolicyManager;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -512,4 +513,10 @@ interface IServerContainer {
* @since 9.0.0
*/
public function getShareManager();
+
+ /**
+ * @return IContentSecurityPolicyManager
+ * @since 9.0.0
+ */
+ public function getContentSecurityPolicyManager();
}
diff --git a/lib/public/security/icontentsecuritypolicymanager.php b/lib/public/security/icontentsecuritypolicymanager.php
new file mode 100644
index 00000000000..10f1efe995f
--- /dev/null
+++ b/lib/public/security/icontentsecuritypolicymanager.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@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 OCP\Security;
+use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
+
+/**
+ * Used for Content Security Policy manipulations
+ *
+ * @package OCP\Security
+ * @since 9.0.0
+ */
+interface IContentSecurityPolicyManager {
+ /**
+ * Allows to inject something into the default content policy. This is for
+ * example useful when you're injecting Javascript code into a view belonging
+ * to another controller and cannot modify its Content-Security-Policy itself.
+ * Note that the adjustment is only applied to applications that use AppFramework
+ * controllers.
+ *
+ * To use this from your `app.php` use `\OC::$server->getContentSecurityPolicyManager()->addDefaultPolicy($policy)`,
+ * $policy has to be of type `\OCP\AppFramework\Http\ContentSecurityPolicy`.
+ *
+ * WARNING: Using this API incorrectly may make the instance more insecure.
+ * Do think twice before adding whitelisting resources. Please do also note
+ * that it is not possible to use the `disallowXYZ` functions.
+ *
+ * @param EmptyContentSecurityPolicy $policy
+ * @since 9.0.0
+ */
+ public function addDefaultPolicy(EmptyContentSecurityPolicy $policy);
+}
diff --git a/lib/public/share/imanager.php b/lib/public/share/imanager.php
index 6531c14a857..b2d9953e9ef 100644
--- a/lib/public/share/imanager.php
+++ b/lib/public/share/imanager.php
@@ -88,15 +88,17 @@ interface IManager {
/**
* Get shares shared with $user.
+ * Filter by $node if provided
*
* @param IUser $user
* @param int $shareType
+ * @param File|Folder|null $node
* @param int $limit The maximum number of shares returned, -1 for all
* @param int $offset
* @return IShare[]
* @since 9.0.0
*/
- public function getSharedWith(IUser $user, $shareType, $limit = 50, $offset = 0);
+ public function getSharedWith(IUser $user, $shareType, $node = null, $limit = 50, $offset = 0);
/**
* Retrieve a share by the share id
diff --git a/lib/public/share/ishareprovider.php b/lib/public/share/ishareprovider.php
index 50964c88dd6..8507462cbed 100644
--- a/lib/public/share/ishareprovider.php
+++ b/lib/public/share/ishareprovider.php
@@ -23,6 +23,7 @@ namespace OCP\Share;
use OC\Share20\Exception\ShareNotFound;
use OC\Share20\Exception\BackendError;
+use OCP\Files\Node;
use OCP\IUser;
/**
@@ -116,12 +117,13 @@ interface IShareProvider {
*
* @param IUser $user get shares where this user is the recipient
* @param int $shareType
+ * @param Node|null $node
* @param int $limit The max number of entries returned, -1 for all
* @param int $offset
* @return \OCP\Share\IShare[]
* @since 9.0.0
*/
- public function getSharedWith(IUser $user, $shareType, $limit, $offset);
+ public function getSharedWith(IUser $user, $shareType, $node, $limit, $offset);
/**
* Get a share by token
diff --git a/settings/l10n/de_DE.js b/settings/l10n/de_DE.js
index 70090b9e2b6..3f314c54816 100644
--- a/settings/l10n/de_DE.js
+++ b/settings/l10n/de_DE.js
@@ -180,6 +180,7 @@ OC.L10N.register(
"More" : "Mehr",
"Less" : "Weniger",
"The logfile is bigger than 100 MB. Downloading it may take some time!" : "Die Logdatei ist größer als 100 MB. Es kann etwas Zeit beanspruchen, sie herunterzuladen!",
+ "What to log" : "Was geloggt wird",
"SQLite is used as database. For larger installations we recommend to switch to a different database backend." : "SQLite wird als Datenbank verwendet. Bei größeren Installationen wird empfohlen, auf ein anderes Datenbank-Backend zu wechseln.",
"Especially when using the desktop client for file syncing the use of SQLite is discouraged." : "Insbesondere bei der Nutzung des Desktop Clients zur Dateisynchronisierung wird vom Einsatz von SQLite abgeraten.",
"To migrate to another database use the command line tool: 'occ db:convert-type', or see the <a target=\"_blank\" href=\"%s\">documentation ↗</a>." : "Um auf eine andere Datenbank zu migrieren, benutzen Sie bitte die Kommandozeile: „occ db:convert-type“ oder konsultieren Sie die <a target=\"_blank\" href=\"%s\">Dokumentation ↗</a>.",
@@ -193,6 +194,8 @@ OC.L10N.register(
"Developer documentation" : "Dokumentation für Entwickler",
"Experimental applications ahead" : "Experimentelle Apps nachfolgend",
"Experimental apps are not checked for security issues, new or known to be unstable and under heavy development. Installing them can cause data loss or security breaches." : "Experimentelle Apps sind nicht auf Sicherheitsprobleme hin überprüft, sind neu oder bekanntermaßen instabil und befinden sich in intensiver Entwicklung. Ihre Installation kann Datenverlust oder Sicherheitslücken hervorrufen.",
+ "by %s" : "durch %s",
+ "%s-licensed" : "%s-Lizensiert",
"Documentation:" : "Dokumentation:",
"User documentation" : "Dokumentation für Benutzer",
"Admin documentation" : "Dokumentation für Administratoren",
@@ -202,6 +205,7 @@ OC.L10N.register(
"Enable only for specific groups" : "Nur für bestimmte Gruppen aktivieren",
"Uninstall App" : "App deinstallieren",
"Enable experimental apps" : "Experimentelle Apps aktivieren",
+ "SSL Root Certificates" : "SSL Root Zertifikate",
"Common Name" : "Common Name",
"Valid until" : "Gültig bis",
"Issued By" : "Ausgestellt von:",
@@ -217,8 +221,11 @@ OC.L10N.register(
"Commercial support" : "Kommerzieller Support",
"Profile picture" : "Profilbild",
"Upload new" : "Neues hochladen",
+ "Select from Files" : "Aus Dateien wählen",
"Remove image" : "Bild entfernen",
+ "png or jpg, max. 20 MB" : "png oder jpg, max. 20 MB",
"Cancel" : "Abbrechen",
+ "Choose as profile picture" : "Als Profilbild auswählen",
"Full name" : "Vollständiger Name",
"No display name set" : "Kein Anzeigename angegeben",
"Email" : "E-Mail",
diff --git a/settings/l10n/de_DE.json b/settings/l10n/de_DE.json
index 8f6931db5c2..0df74f1d602 100644
--- a/settings/l10n/de_DE.json
+++ b/settings/l10n/de_DE.json
@@ -178,6 +178,7 @@
"More" : "Mehr",
"Less" : "Weniger",
"The logfile is bigger than 100 MB. Downloading it may take some time!" : "Die Logdatei ist größer als 100 MB. Es kann etwas Zeit beanspruchen, sie herunterzuladen!",
+ "What to log" : "Was geloggt wird",
"SQLite is used as database. For larger installations we recommend to switch to a different database backend." : "SQLite wird als Datenbank verwendet. Bei größeren Installationen wird empfohlen, auf ein anderes Datenbank-Backend zu wechseln.",
"Especially when using the desktop client for file syncing the use of SQLite is discouraged." : "Insbesondere bei der Nutzung des Desktop Clients zur Dateisynchronisierung wird vom Einsatz von SQLite abgeraten.",
"To migrate to another database use the command line tool: 'occ db:convert-type', or see the <a target=\"_blank\" href=\"%s\">documentation ↗</a>." : "Um auf eine andere Datenbank zu migrieren, benutzen Sie bitte die Kommandozeile: „occ db:convert-type“ oder konsultieren Sie die <a target=\"_blank\" href=\"%s\">Dokumentation ↗</a>.",
@@ -191,6 +192,8 @@
"Developer documentation" : "Dokumentation für Entwickler",
"Experimental applications ahead" : "Experimentelle Apps nachfolgend",
"Experimental apps are not checked for security issues, new or known to be unstable and under heavy development. Installing them can cause data loss or security breaches." : "Experimentelle Apps sind nicht auf Sicherheitsprobleme hin überprüft, sind neu oder bekanntermaßen instabil und befinden sich in intensiver Entwicklung. Ihre Installation kann Datenverlust oder Sicherheitslücken hervorrufen.",
+ "by %s" : "durch %s",
+ "%s-licensed" : "%s-Lizensiert",
"Documentation:" : "Dokumentation:",
"User documentation" : "Dokumentation für Benutzer",
"Admin documentation" : "Dokumentation für Administratoren",
@@ -200,6 +203,7 @@
"Enable only for specific groups" : "Nur für bestimmte Gruppen aktivieren",
"Uninstall App" : "App deinstallieren",
"Enable experimental apps" : "Experimentelle Apps aktivieren",
+ "SSL Root Certificates" : "SSL Root Zertifikate",
"Common Name" : "Common Name",
"Valid until" : "Gültig bis",
"Issued By" : "Ausgestellt von:",
@@ -215,8 +219,11 @@
"Commercial support" : "Kommerzieller Support",
"Profile picture" : "Profilbild",
"Upload new" : "Neues hochladen",
+ "Select from Files" : "Aus Dateien wählen",
"Remove image" : "Bild entfernen",
+ "png or jpg, max. 20 MB" : "png oder jpg, max. 20 MB",
"Cancel" : "Abbrechen",
+ "Choose as profile picture" : "Als Profilbild auswählen",
"Full name" : "Vollständiger Name",
"No display name set" : "Kein Anzeigename angegeben",
"Email" : "E-Mail",
diff --git a/settings/l10n/ja.js b/settings/l10n/ja.js
index 65881d30d34..2d092c302d1 100644
--- a/settings/l10n/ja.js
+++ b/settings/l10n/ja.js
@@ -209,6 +209,8 @@ OC.L10N.register(
"Admin documentation" : "管理者ドキュメント",
"Show description …" : "説明を表示 ...",
"Hide description …" : "説明を隠す ...",
+ "This app has no minimum ownCloud version assigned. This will be an error in ownCloud 11 and later." : "このアプリはownCloudの最小バージョンが指定されていません.ownCloud 11 以降でエラーが発生する可能性があります.",
+ "This app has no maximum ownCloud version assigned. This will be an error in ownCloud 11 and later." : "このアプリはownCloudの最大バージョンが指定されていません.ownCloud 11 以降でエラーが発生する可能性があります.",
"This app cannot be installed because the following dependencies are not fulfilled:" : "次の依存関係が満たされないためこのアプリをインストールできません:",
"Enable only for specific groups" : "特定のグループのみ有効に",
"Uninstall App" : "アプリをアンインストール",
diff --git a/settings/l10n/ja.json b/settings/l10n/ja.json
index 05d43d0cd62..996a53df9c5 100644
--- a/settings/l10n/ja.json
+++ b/settings/l10n/ja.json
@@ -207,6 +207,8 @@
"Admin documentation" : "管理者ドキュメント",
"Show description …" : "説明を表示 ...",
"Hide description …" : "説明を隠す ...",
+ "This app has no minimum ownCloud version assigned. This will be an error in ownCloud 11 and later." : "このアプリはownCloudの最小バージョンが指定されていません.ownCloud 11 以降でエラーが発生する可能性があります.",
+ "This app has no maximum ownCloud version assigned. This will be an error in ownCloud 11 and later." : "このアプリはownCloudの最大バージョンが指定されていません.ownCloud 11 以降でエラーが発生する可能性があります.",
"This app cannot be installed because the following dependencies are not fulfilled:" : "次の依存関係が満たされないためこのアプリをインストールできません:",
"Enable only for specific groups" : "特定のグループのみ有効に",
"Uninstall App" : "アプリをアンインストール",
diff --git a/settings/l10n/nl.js b/settings/l10n/nl.js
index 2aead6ae80f..60962c89c61 100644
--- a/settings/l10n/nl.js
+++ b/settings/l10n/nl.js
@@ -204,6 +204,8 @@ OC.L10N.register(
"Developer documentation" : "Ontwikkelaarsdocumentatie",
"Experimental applications ahead" : "Experimentele applicaties vooraan",
"Experimental apps are not checked for security issues, new or known to be unstable and under heavy development. Installing them can cause data loss or security breaches." : "Experimentele apps zijn niet gecontroleerd op beveiligingsproblemen, zijn nieuw of staan bekend als instabiel en worden volop ontwikkeld. Installatie kan leiden tot gegevensverlies of beveiligingsincidenten.",
+ "by %s" : "op %s",
+ "%s-licensed" : "%s-licensed",
"Documentation:" : "Documentatie:",
"User documentation" : "Gebruikersdocumentatie",
"Admin documentation" : "Beheerdocumentatie",
diff --git a/settings/l10n/nl.json b/settings/l10n/nl.json
index b72ac4f860c..2c201e83e30 100644
--- a/settings/l10n/nl.json
+++ b/settings/l10n/nl.json
@@ -202,6 +202,8 @@
"Developer documentation" : "Ontwikkelaarsdocumentatie",
"Experimental applications ahead" : "Experimentele applicaties vooraan",
"Experimental apps are not checked for security issues, new or known to be unstable and under heavy development. Installing them can cause data loss or security breaches." : "Experimentele apps zijn niet gecontroleerd op beveiligingsproblemen, zijn nieuw of staan bekend als instabiel en worden volop ontwikkeld. Installatie kan leiden tot gegevensverlies of beveiligingsincidenten.",
+ "by %s" : "op %s",
+ "%s-licensed" : "%s-licensed",
"Documentation:" : "Documentatie:",
"User documentation" : "Gebruikersdocumentatie",
"Admin documentation" : "Beheerdocumentatie",
diff --git a/settings/l10n/pt_BR.js b/settings/l10n/pt_BR.js
index 7d04bc3c04b..144c4a812f2 100644
--- a/settings/l10n/pt_BR.js
+++ b/settings/l10n/pt_BR.js
@@ -204,6 +204,8 @@ OC.L10N.register(
"Developer documentation" : "Documentação do desenvolvedor",
"Experimental applications ahead" : "Aplicações experimentais à frente",
"Experimental apps are not checked for security issues, new or known to be unstable and under heavy development. Installing them can cause data loss or security breaches." : "Aplicativos experimentais não são marcados por questões de segurança, por serem novos ou conhecidos como instáveis e sob forte desenvolvimento. Instalá-los pode causar perda de dados ou falhas de segurança.",
+ "by %s" : "por %s",
+ "%s-licensed" : "%s-licenciado",
"Documentation:" : "Documentação:",
"User documentation" : "Documentação do usuário",
"Admin documentation" : "Documentação para Administração",
diff --git a/settings/l10n/pt_BR.json b/settings/l10n/pt_BR.json
index 100b7e60f22..fb06b056b44 100644
--- a/settings/l10n/pt_BR.json
+++ b/settings/l10n/pt_BR.json
@@ -202,6 +202,8 @@
"Developer documentation" : "Documentação do desenvolvedor",
"Experimental applications ahead" : "Aplicações experimentais à frente",
"Experimental apps are not checked for security issues, new or known to be unstable and under heavy development. Installing them can cause data loss or security breaches." : "Aplicativos experimentais não são marcados por questões de segurança, por serem novos ou conhecidos como instáveis e sob forte desenvolvimento. Instalá-los pode causar perda de dados ou falhas de segurança.",
+ "by %s" : "por %s",
+ "%s-licensed" : "%s-licenciado",
"Documentation:" : "Documentação:",
"User documentation" : "Documentação do usuário",
"Admin documentation" : "Documentação para Administração",
diff --git a/settings/l10n/zh_TW.js b/settings/l10n/zh_TW.js
index 755befc2d5f..14e94ae0f30 100644
--- a/settings/l10n/zh_TW.js
+++ b/settings/l10n/zh_TW.js
@@ -277,7 +277,7 @@ OC.L10N.register(
"Unlimited" : "無限制",
"Other" : "其他",
"Full Name" : "全名",
- "Group Admin for" : "是以下群組的管理員",
+ "Group Admin for" : "指定群組管理者",
"Quota" : "容量限制",
"Storage Location" : "儲存位置",
"User Backend" : "用戶後端",
diff --git a/settings/l10n/zh_TW.json b/settings/l10n/zh_TW.json
index e89a00e338f..0fc46a9ee8f 100644
--- a/settings/l10n/zh_TW.json
+++ b/settings/l10n/zh_TW.json
@@ -275,7 +275,7 @@
"Unlimited" : "無限制",
"Other" : "其他",
"Full Name" : "全名",
- "Group Admin for" : "是以下群組的管理員",
+ "Group Admin for" : "指定群組管理者",
"Quota" : "容量限制",
"Storage Location" : "儲存位置",
"User Backend" : "用戶後端",
diff --git a/settings/personal.php b/settings/personal.php
index 261a459a921..d2d4fc90f5e 100644
--- a/settings/personal.php
+++ b/settings/personal.php
@@ -149,7 +149,7 @@ $tmpl->assign('activelanguage', $userLang);
$tmpl->assign('passwordChangeSupported', OC_User::canUserChangePassword(OC_User::getUser()));
$tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC_User::getUser()));
$tmpl->assign('displayName', OC_User::getDisplayName());
-$tmpl->assign('enableAvatars', $config->getSystemValue('enable_avatars', true));
+$tmpl->assign('enableAvatars', $config->getSystemValue('enable_avatars', true) === true);
$tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser()));
$tmpl->assign('certs', $certificateManager->listCertificates());
$tmpl->assign('showCertificates', $enableCertImport);
diff --git a/settings/users.php b/settings/users.php
index 9d89ff65b4a..0dff85c1ade 100644
--- a/settings/users.php
+++ b/settings/users.php
@@ -116,7 +116,7 @@ $tmpl->assign('quota_preset', $quotaPreset);
$tmpl->assign('default_quota', $defaultQuota);
$tmpl->assign('defaultQuotaIsUserDefined', $defaultQuotaIsUserDefined);
$tmpl->assign('recoveryAdminEnabled', $recoveryAdminEnabled);
-$tmpl->assign('enableAvatars', \OC::$server->getConfig()->getSystemValue('enable_avatars', true));
+$tmpl->assign('enableAvatars', \OC::$server->getConfig()->getSystemValue('enable_avatars', true) === true);
$tmpl->assign('show_storage_location', $config->getAppValue('core', 'umgmt_show_storage_location', 'false'));
$tmpl->assign('show_last_login', $config->getAppValue('core', 'umgmt_show_last_login', 'false'));
diff --git a/tests/lib/appframework/http/ContentSecurityPolicyTest.php b/tests/lib/appframework/http/ContentSecurityPolicyTest.php
index 6d9c6d7b8d9..adf03185e9f 100644
--- a/tests/lib/appframework/http/ContentSecurityPolicyTest.php
+++ b/tests/lib/appframework/http/ContentSecurityPolicyTest.php
@@ -426,21 +426,4 @@ class ContentSecurityPolicyTest extends \Test\TestCase {
$this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org')->disallowChildSrcDomain('www.owncloud.com');
$this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
}
-
- public function testConfigureStacked() {
- $expectedPolicy = "default-src 'none';script-src 'self' script.owncloud.org;style-src 'self' style.owncloud.org;img-src 'self' data: blob: img.owncloud.org;font-src 'self' font.owncloud.org;connect-src 'self' connect.owncloud.org;media-src 'self' media.owncloud.org;object-src objects.owncloud.org;frame-src frame.owncloud.org;child-src child.owncloud.org";
-
- $this->contentSecurityPolicy->allowInlineStyle(false)
- ->allowEvalScript(false)
- ->addAllowedScriptDomain('script.owncloud.org')
- ->addAllowedStyleDomain('style.owncloud.org')
- ->addAllowedFontDomain('font.owncloud.org')
- ->addAllowedImageDomain('img.owncloud.org')
- ->addAllowedConnectDomain('connect.owncloud.org')
- ->addAllowedMediaDomain('media.owncloud.org')
- ->addAllowedObjectDomain('objects.owncloud.org')
- ->addAllowedChildSrcDomain('child.owncloud.org')
- ->addAllowedFrameDomain('frame.owncloud.org');
- $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
- }
}
diff --git a/tests/lib/appframework/http/EmptyContentSecurityPolicyTest.php b/tests/lib/appframework/http/EmptyContentSecurityPolicyTest.php
new file mode 100644
index 00000000000..0d0f92de819
--- /dev/null
+++ b/tests/lib/appframework/http/EmptyContentSecurityPolicyTest.php
@@ -0,0 +1,430 @@
+<?php
+/**
+ * Copyright (c) 2015 Lukas Reschke lukas@owncloud.com
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+namespace OC\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
+
+/**
+ * Class ContentSecurityPolicyTest
+ *
+ * @package OC\AppFramework\Http
+ */
+class EmptyContentSecurityPolicyTest extends \Test\TestCase {
+
+ /** @var EmptyContentSecurityPolicy */
+ private $contentSecurityPolicy;
+
+ public function setUp() {
+ parent::setUp();
+ $this->contentSecurityPolicy = new EmptyContentSecurityPolicy();
+ }
+
+ public function testGetPolicyDefault() {
+ $defaultPolicy = "default-src 'none'";
+ $this->assertSame($defaultPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyScriptDomainValid() {
+ $expectedPolicy = "default-src 'none';script-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyScriptDomainValidMultiple() {
+ $expectedPolicy = "default-src 'none';script-src www.owncloud.com www.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowScriptDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowScriptDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowScriptDomainMultiple() {
+ $expectedPolicy = "default-src 'none';script-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowScriptDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowScriptDomainMultipleStacked() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowScriptDomain('www.owncloud.org')->disallowScriptDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyScriptAllowInline() {
+ $expectedPolicy = "default-src 'none';script-src 'unsafe-inline'";
+
+ $this->contentSecurityPolicy->allowInlineScript(true);
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyScriptAllowInlineWithDomain() {
+ $expectedPolicy = "default-src 'none';script-src www.owncloud.com 'unsafe-inline'";
+
+ $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->allowInlineScript(true);
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyScriptAllowInlineAndEval() {
+ $expectedPolicy = "default-src 'none';script-src 'unsafe-inline' 'unsafe-eval'";
+
+ $this->contentSecurityPolicy->allowInlineScript(true);
+ $this->contentSecurityPolicy->allowEvalScript(true);
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyStyleDomainValid() {
+ $expectedPolicy = "default-src 'none';style-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyStyleDomainValidMultiple() {
+ $expectedPolicy = "default-src 'none';style-src www.owncloud.com www.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowStyleDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowStyleDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowStyleDomainMultiple() {
+ $expectedPolicy = "default-src 'none';style-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowStyleDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowStyleDomainMultipleStacked() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowStyleDomain('www.owncloud.org')->disallowStyleDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyStyleAllowInline() {
+ $expectedPolicy = "default-src 'none';style-src 'unsafe-inline'";
+
+ $this->contentSecurityPolicy->allowInlineStyle(true);
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyStyleAllowInlineWithDomain() {
+ $expectedPolicy = "default-src 'none';style-src www.owncloud.com 'unsafe-inline'";
+
+ $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->allowInlineStyle(true);
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyStyleDisallowInline() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->allowInlineStyle(false);
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyImageDomainValid() {
+ $expectedPolicy = "default-src 'none';img-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyImageDomainValidMultiple() {
+ $expectedPolicy = "default-src 'none';img-src www.owncloud.com www.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowImageDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowImageDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowImageDomainMultiple() {
+ $expectedPolicy = "default-src 'none';img-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowImageDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowImageDomainMultipleStakes() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowImageDomain('www.owncloud.org')->disallowImageDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyFontDomainValid() {
+ $expectedPolicy = "default-src 'none';font-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyFontDomainValidMultiple() {
+ $expectedPolicy = "default-src 'none';font-src www.owncloud.com www.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowFontDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowFontDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowFontDomainMultiple() {
+ $expectedPolicy = "default-src 'none';font-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowFontDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowFontDomainMultipleStakes() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowFontDomain('www.owncloud.org')->disallowFontDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyConnectDomainValid() {
+ $expectedPolicy = "default-src 'none';connect-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyConnectDomainValidMultiple() {
+ $expectedPolicy = "default-src 'none';connect-src www.owncloud.com www.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowConnectDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowConnectDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowConnectDomainMultiple() {
+ $expectedPolicy = "default-src 'none';connect-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowConnectDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowConnectDomainMultipleStakes() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowConnectDomain('www.owncloud.org')->disallowConnectDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyMediaDomainValid() {
+ $expectedPolicy = "default-src 'none';media-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyMediaDomainValidMultiple() {
+ $expectedPolicy = "default-src 'none';media-src www.owncloud.com www.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowMediaDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowMediaDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowMediaDomainMultiple() {
+ $expectedPolicy = "default-src 'none';media-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowMediaDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowMediaDomainMultipleStakes() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowMediaDomain('www.owncloud.org')->disallowMediaDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyObjectDomainValid() {
+ $expectedPolicy = "default-src 'none';object-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyObjectDomainValidMultiple() {
+ $expectedPolicy = "default-src 'none';object-src www.owncloud.com www.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowObjectDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowObjectDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowObjectDomainMultiple() {
+ $expectedPolicy = "default-src 'none';object-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowObjectDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowObjectDomainMultipleStakes() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowObjectDomain('www.owncloud.org')->disallowObjectDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetAllowedFrameDomain() {
+ $expectedPolicy = "default-src 'none';frame-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyFrameDomainValidMultiple() {
+ $expectedPolicy = "default-src 'none';frame-src www.owncloud.com www.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowFrameDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowFrameDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowFrameDomainMultiple() {
+ $expectedPolicy = "default-src 'none';frame-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowFrameDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowFrameDomainMultipleStakes() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowFrameDomain('www.owncloud.org')->disallowFrameDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetAllowedChildSrcDomain() {
+ $expectedPolicy = "default-src 'none';child-src child.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedChildSrcDomain('child.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyChildSrcValidMultiple() {
+ $expectedPolicy = "default-src 'none';child-src child.owncloud.com child.owncloud.org";
+
+ $this->contentSecurityPolicy->addAllowedChildSrcDomain('child.owncloud.com');
+ $this->contentSecurityPolicy->addAllowedChildSrcDomain('child.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowChildSrcDomain() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedChildSrcDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowChildSrcDomainMultiple() {
+ $expectedPolicy = "default-src 'none';child-src www.owncloud.com";
+
+ $this->contentSecurityPolicy->addAllowedChildSrcDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+
+ public function testGetPolicyDisallowChildSrcDomainMultipleStakes() {
+ $expectedPolicy = "default-src 'none'";
+
+ $this->contentSecurityPolicy->addAllowedChildSrcDomain('www.owncloud.com');
+ $this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org')->disallowChildSrcDomain('www.owncloud.com');
+ $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy());
+ }
+}
diff --git a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php
index 62223bbc2d9..9e71a3d0961 100644
--- a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php
+++ b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php
@@ -32,6 +32,7 @@ use OC\Appframework\Middleware\Security\Exceptions\NotAdminException;
use OC\Appframework\Middleware\Security\Exceptions\NotLoggedInException;
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
use OC\AppFramework\Utility\ControllerMethodReflector;
+use OC\Security\CSP\ContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse;
@@ -48,6 +49,7 @@ class SecurityMiddlewareTest extends \Test\TestCase {
private $logger;
private $navigationManager;
private $urlGenerator;
+ private $contentSecurityPolicyManager;
protected function setUp() {
parent::setUp();
@@ -72,6 +74,10 @@ class SecurityMiddlewareTest extends \Test\TestCase {
'OCP\IRequest')
->disableOriginalConstructor()
->getMock();
+ $this->contentSecurityPolicyManager = $this->getMockBuilder(
+ 'OC\Security\CSP\ContentSecurityPolicyManager')
+ ->disableOriginalConstructor()
+ ->getMock();
$this->middleware = $this->getMiddleware(true, true);
$this->secException = new SecurityException('hey', false);
$this->secAjaxException = new SecurityException('hey', true);
@@ -91,7 +97,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->logger,
'files',
$isLoggedIn,
- $isAdminUser
+ $isAdminUser,
+ $this->contentSecurityPolicyManager
);
}
@@ -410,5 +417,31 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->assertTrue($response instanceof JSONResponse);
}
-
+ public function testAfterController() {
+ $response = $this->getMockBuilder('\OCP\AppFramework\Http\Response')->disableOriginalConstructor()->getMock();
+ $defaultPolicy = new ContentSecurityPolicy();
+ $defaultPolicy->addAllowedImageDomain('defaultpolicy');
+ $currentPolicy = new ContentSecurityPolicy();
+ $currentPolicy->addAllowedConnectDomain('currentPolicy');
+ $mergedPolicy = new ContentSecurityPolicy();
+ $mergedPolicy->addAllowedMediaDomain('mergedPolicy');
+ $response
+ ->expects($this->exactly(2))
+ ->method('getContentSecurityPolicy')
+ ->willReturn($currentPolicy);
+ $this->contentSecurityPolicyManager
+ ->expects($this->once())
+ ->method('getDefaultPolicy')
+ ->willReturn($defaultPolicy);
+ $this->contentSecurityPolicyManager
+ ->expects($this->once())
+ ->method('mergePolicies')
+ ->with($defaultPolicy, $currentPolicy)
+ ->willReturn($mergedPolicy);
+ $response->expects($this->once())
+ ->method('setContentSecurityPolicy')
+ ->with($mergedPolicy);
+
+ $this->middleware->afterController($this->controller, 'test', $response);
+ }
}
diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php
index de4ae9cd8e9..54aa7ad789a 100644
--- a/tests/lib/files/cache/cache.php
+++ b/tests/lib/files/cache/cache.php
@@ -641,8 +641,8 @@ class Cache extends \Test\TestCase {
$this->cache->put($name, $folderData);
$this->cache->put('other', $folderData);
$childs = ['asd', 'bar', 'foo', 'sub/folder'];
- $this->cache->put($name . '/sub/folder', $folderData);
- $this->cache->put('other/sub/folder', $folderData);
+ $this->cache->put($name . '/sub', $folderData);
+ $this->cache->put('other/sub', $folderData);
foreach ($childs as $child) {
$this->cache->put($name . '/' . $child, $data);
$this->cache->put('other/' . $child, $data);
diff --git a/tests/lib/files/cache/movefromcachetraittest.php b/tests/lib/files/cache/movefromcachetraittest.php
new file mode 100644
index 00000000000..614c259d996
--- /dev/null
+++ b/tests/lib/files/cache/movefromcachetraittest.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright (c) 2016 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace Test\Files\Cache;
+
+use OC\Files\Cache\MoveFromCacheTrait;
+
+class FallBackCrossCacheMoveCache extends \OC\Files\Cache\Cache {
+ use MoveFromCacheTrait;
+}
+
+/**
+ * Class Cache
+ *
+ * @group DB
+ */
+class MoveFromCacheTraitTest extends Cache {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->storage = new \OC\Files\Storage\Temporary(array());
+ $this->storage2 = new \OC\Files\Storage\Temporary(array());
+ $this->cache = new FallBackCrossCacheMoveCache($this->storage);
+ $this->cache2 = new FallBackCrossCacheMoveCache($this->storage2);
+ }
+}
diff --git a/tests/lib/security/csp/ContentSecurityPolicyManagerTest.php b/tests/lib/security/csp/ContentSecurityPolicyManagerTest.php
new file mode 100644
index 00000000000..975c35d3780
--- /dev/null
+++ b/tests/lib/security/csp/ContentSecurityPolicyManagerTest.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@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/>
+ *
+ */
+
+use OC\Security\CSP\ContentSecurityPolicyManager;
+
+class ContentSecurityPolicyManagerTest extends \Test\TestCase {
+ /** @var ContentSecurityPolicyManager */
+ private $contentSecurityPolicyManager;
+
+ public function setUp() {
+ parent::setUp();
+ $this->contentSecurityPolicyManager = new ContentSecurityPolicyManager();
+ }
+
+ public function testAddDefaultPolicy() {
+ $this->contentSecurityPolicyManager->addDefaultPolicy(new \OCP\AppFramework\Http\ContentSecurityPolicy());
+ }
+
+ public function testGetDefaultPolicyWithPolicies() {
+ $policy = new \OCP\AppFramework\Http\ContentSecurityPolicy();
+ $policy->addAllowedFontDomain('mydomain.com');
+ $policy->addAllowedImageDomain('anotherdomain.de');
+ $this->contentSecurityPolicyManager->addDefaultPolicy($policy);
+ $policy = new \OCP\AppFramework\Http\ContentSecurityPolicy();
+ $policy->addAllowedFontDomain('example.com');
+ $policy->addAllowedImageDomain('example.org');
+ $policy->allowInlineScript(true);
+ $this->contentSecurityPolicyManager->addDefaultPolicy($policy);
+ $policy = new \OCP\AppFramework\Http\EmptyContentSecurityPolicy();
+ $policy->addAllowedChildSrcDomain('childdomain');
+ $policy->addAllowedFontDomain('anotherFontDomain');
+ $this->contentSecurityPolicyManager->addDefaultPolicy($policy);
+
+ $expected = new \OC\Security\CSP\ContentSecurityPolicy();
+ $expected->allowInlineScript(true);
+ $expected->addAllowedFontDomain('mydomain.com');
+ $expected->addAllowedFontDomain('example.com');
+ $expected->addAllowedFontDomain('anotherFontDomain');
+ $expected->addAllowedImageDomain('anotherdomain.de');
+ $expected->addAllowedImageDomain('example.org');
+ $expected->addAllowedChildSrcDomain('childdomain');
+ $expectedStringPolicy = 'default-src \'none\';script-src \'self\' \'unsafe-inline\' \'unsafe-eval\';style-src \'self\' \'unsafe-inline\';img-src \'self\' data: blob: anotherdomain.de example.org;font-src \'self\' mydomain.com example.com anotherFontDomain;connect-src \'self\';media-src \'self\';child-src childdomain';
+
+ $this->assertEquals($expected, $this->contentSecurityPolicyManager->getDefaultPolicy());
+ $this->assertSame($expectedStringPolicy, $this->contentSecurityPolicyManager->getDefaultPolicy()->buildPolicy());
+ }
+
+}
diff --git a/tests/lib/server.php b/tests/lib/server.php
index 8b2fec1f5a1..d13f9be0c9e 100644
--- a/tests/lib/server.php
+++ b/tests/lib/server.php
@@ -62,10 +62,12 @@ class Server extends \Test\TestCase {
['CapabilitiesManager', '\OC\CapabilitiesManager'],
['ContactsManager', '\OC\ContactsManager'],
['ContactsManager', '\OCP\Contacts\IManager'],
+ ['ContentSecurityPolicyManager', '\OC\Security\CSP\ContentSecurityPolicyManager'],
['CommentsManager', '\OCP\Comments\ICommentsManager'],
['Crypto', '\OC\Security\Crypto'],
['Crypto', '\OCP\Security\ICrypto'],
['CryptoWrapper', '\OC\Session\CryptoWrapper'],
+ ['CsrfTokenManager', '\OC\Security\CSRF\CsrfTokenManager'],
['DatabaseConnection', '\OC\DB\Connection'],
['DatabaseConnection', '\OCP\IDBConnection'],
diff --git a/tests/lib/share20/defaultshareprovidertest.php b/tests/lib/share20/defaultshareprovidertest.php
index 45f2bedcb63..28b57435e1d 100644
--- a/tests/lib/share20/defaultshareprovidertest.php
+++ b/tests/lib/share20/defaultshareprovidertest.php
@@ -824,7 +824,7 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->rootFolder->method('getUserFolder')->with('shareOwner')->will($this->returnSelf());
$this->rootFolder->method('getById')->with(42)->willReturn([$file]);
- $share = $this->provider->getSharedWith($user, \OCP\Share::SHARE_TYPE_USER, 1 , 0);
+ $share = $this->provider->getSharedWith($user, \OCP\Share::SHARE_TYPE_USER, null, 1 , 0);
$this->assertCount(1, $share);
$share = $share[0];
@@ -894,7 +894,7 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->rootFolder->method('getUserFolder')->with('shareOwner')->will($this->returnSelf());
$this->rootFolder->method('getById')->with(42)->willReturn([$file]);
- $share = $this->provider->getSharedWith($user, \OCP\Share::SHARE_TYPE_GROUP, 20 , 1);
+ $share = $this->provider->getSharedWith($user, \OCP\Share::SHARE_TYPE_GROUP, null, 20 , 1);
$this->assertCount(1, $share);
$share = $share[0];
@@ -979,7 +979,7 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->rootFolder->method('getUserFolder')->with('shareOwner')->will($this->returnSelf());
$this->rootFolder->method('getById')->with(42)->willReturn([$file]);
- $share = $this->provider->getSharedWith($user, \OCP\Share::SHARE_TYPE_GROUP, -1, 0);
+ $share = $this->provider->getSharedWith($user, \OCP\Share::SHARE_TYPE_GROUP, null, -1, 0);
$this->assertCount(1, $share);
$share = $share[0];
@@ -992,6 +992,78 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->assertSame('userTarget', $share->getTarget());
}
+ public function testGetSharedWithUserWithNode() {
+ $this->addShareToDB(\OCP\Share::SHARE_TYPE_USER, 'user0', 'user1', 'user1',
+ 'file', 42, 'myTarget', 31, null, null, null);
+ $id = $this->addShareToDB(\OCP\Share::SHARE_TYPE_USER, 'user0', 'user1', 'user1',
+ 'file', 43, 'myTarget', 31, null, null, null);
+
+ $user0 = $this->getMock('\OCP\IUser');
+ $user0->method('getUID')->willReturn('user0');
+ $user1 = $this->getMock('\OCP\IUser');
+ $user1->method('getUID')->willReturn('user1');
+
+ $this->userManager->method('get')->willReturnMap([
+ ['user0', $user0],
+ ['user1', $user1],
+ ]);
+
+ $file = $this->getMock('\OCP\Files\File');
+ $file->method('getId')->willReturn(43);
+ $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf());
+ $this->rootFolder->method('getById')->with(43)->willReturn([$file]);
+
+ $share = $this->provider->getSharedWith($user0, \OCP\Share::SHARE_TYPE_USER, $file, -1, 0);
+ $this->assertCount(1, $share);
+
+ $share = $share[0];
+ $this->assertEquals($id, $share->getId());
+ $this->assertSame($user0, $share->getSharedWith());
+ $this->assertSame($user1, $share->getShareOwner());
+ $this->assertSame($user1, $share->getSharedBy());
+ $this->assertSame($file, $share->getNode());
+ $this->assertEquals(\OCP\Share::SHARE_TYPE_USER, $share->getShareType());
+ }
+
+ public function testGetSharedWithGroupWithNode() {
+ $this->addShareToDB(\OCP\Share::SHARE_TYPE_GROUP, 'group0', 'user1', 'user1',
+ 'file', 42, 'myTarget', 31, null, null, null);
+ $id = $this->addShareToDB(\OCP\Share::SHARE_TYPE_GROUP, 'group0', 'user1', 'user1',
+ 'file', 43, 'myTarget', 31, null, null, null);
+
+ $user0 = $this->getMock('\OCP\IUser');
+ $user0->method('getUID')->willReturn('user0');
+ $user1 = $this->getMock('\OCP\IUser');
+ $user1->method('getUID')->willReturn('user1');
+
+ $this->userManager->method('get')->willReturnMap([
+ ['user0', $user0],
+ ['user1', $user1],
+ ]);
+
+ $group0 = $this->getMock('\OCP\IGroup');
+ $group0->method('getGID')->willReturn('group0');
+
+ $this->groupManager->method('get')->with('group0')->willReturn($group0);
+ $this->groupManager->method('getUserGroups')->with($user0)->willReturn([$group0]);
+
+ $node = $this->getMock('\OCP\Files\Folder');
+ $node->method('getId')->willReturn(43);
+ $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf());
+ $this->rootFolder->method('getById')->with(43)->willReturn([$node]);
+
+ $share = $this->provider->getSharedWith($user0, \OCP\Share::SHARE_TYPE_GROUP, $node, -1, 0);
+ $this->assertCount(1, $share);
+
+ $share = $share[0];
+ $this->assertEquals($id, $share->getId());
+ $this->assertSame($group0, $share->getSharedWith());
+ $this->assertSame($user1, $share->getShareOwner());
+ $this->assertSame($user1, $share->getSharedBy());
+ $this->assertSame($node, $share->getNode());
+ $this->assertEquals(\OCP\Share::SHARE_TYPE_GROUP, $share->getShareType());
+ }
+
public function testGetSharesBy() {
$qb = $this->dbConn->getQueryBuilder();
$qb->insert('share')
diff --git a/tests/lib/share20/managertest.php b/tests/lib/share20/managertest.php
index 53b1374eade..b5559bb5172 100644
--- a/tests/lib/share20/managertest.php
+++ b/tests/lib/share20/managertest.php
@@ -607,7 +607,10 @@ class ManagerTest extends \Test\TestCase {
$past = new \DateTime();
$past->sub(new \DateInterval('P1D'));
- $this->invokePrivate($this->manager, 'validateExpirationDate', [$past]);
+ $share = $this->manager->newShare();
+ $share->setExpirationDate($past);
+
+ $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
}
/**
@@ -615,12 +618,14 @@ class ManagerTest extends \Test\TestCase {
* @expectedExceptionMessage Expiration date is enforced
*/
public function testvalidateExpirationDateEnforceButNotSet() {
+ $share = $this->manager->newShare();
+
$this->config->method('getAppValue')
->will($this->returnValueMap([
['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
]));
- $this->invokePrivate($this->manager, 'validateExpirationDate', [null]);
+ $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
}
public function testvalidateExpirationDateEnforceToFarIntoFuture() {
@@ -628,6 +633,9 @@ class ManagerTest extends \Test\TestCase {
$future = new \DateTime();
$future->add(new \DateInterval('P7D'));
+ $share = $this->manager->newShare();
+ $share->setExpirationDate($future);
+
$this->config->method('getAppValue')
->will($this->returnValueMap([
['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
@@ -635,7 +643,7 @@ class ManagerTest extends \Test\TestCase {
]));
try {
- $this->invokePrivate($this->manager, 'validateExpirationDate', [$future]);
+ $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
} catch (\OC\HintException $e) {
$this->assertEquals('Cannot set expiration date more than 3 days in the future', $e->getMessage());
$this->assertEquals('Cannot set expiration date more than 3 days in the future', $e->getHint());
@@ -648,31 +656,61 @@ class ManagerTest extends \Test\TestCase {
$future = new \DateTime();
$future->add(new \DateInterval('P2D'));
$future->setTime(0,0,0);
- $expected = $future->format(\DateTime::ISO8601);
+
+ $expected = clone $future;
$future->setTime(1,2,3);
+ $share = $this->manager->newShare();
+ $share->setExpirationDate($future);
+
$this->config->method('getAppValue')
->will($this->returnValueMap([
['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
['core', 'shareapi_expire_after_n_days', '7', '3'],
]));
- $future = $this->invokePrivate($this->manager, 'validateExpirationDate', [$future]);
+ $hookListner = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
+ \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListner, 'listener');
+ $hookListner->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($future) {
+ return $data['expirationDate'] == $future;
+ }));
+
+ $future = $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
- $this->assertEquals($expected, $future->format(\DateTime::ISO8601));
+ $this->assertEquals($expected, $future);
}
public function testvalidateExpirationDateNoDateNoDefaultNull() {
$date = new \DateTime();
$date->add(new \DateInterval('P5D'));
- $res = $this->invokePrivate($this->manager, 'validateExpirationDate', [$date]);
+ $expected = clone $date;
+ $expected->setTime(0,0,0);
+
+ $share = $this->manager->newShare();
+ $share->setExpirationDate($date);
+
+ $hookListner = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
+ \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListner, 'listener');
+ $hookListner->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
+ return $data['expirationDate'] == $expected;
+ }));
- $this->assertEquals($date, $res);
+ $res = $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
+
+ $this->assertEquals($expected, $res);
}
public function testvalidateExpirationDateNoDateNoDefault() {
- $date = $this->invokePrivate($this->manager, 'validateExpirationDate', [null]);
+ $hookListner = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
+ \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListner, 'listener');
+ $hookListner->expects($this->once())->method('listener')->with($this->callback(function ($data) {
+ return $data['expirationDate'] === null;
+ }));
+
+ $share = $this->manager->newShare();
+
+ $date = $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
$this->assertNull($date);
}
@@ -682,15 +720,70 @@ class ManagerTest extends \Test\TestCase {
$future->add(new \DateInterval('P3D'));
$future->setTime(0,0,0);
+ $expected = clone $future;
+
+ $share = $this->manager->newShare();
+ $share->setExpirationDate($future);
+
$this->config->method('getAppValue')
->will($this->returnValueMap([
['core', 'shareapi_default_expire_date', 'no', 'yes'],
['core', 'shareapi_expire_after_n_days', '7', '3'],
]));
- $date = $this->invokePrivate($this->manager, 'validateExpirationDate', [null]);
+ $hookListner = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
+ \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListner, 'listener');
+ $hookListner->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
+ return $data['expirationDate'] == $expected;
+ }));
+
+ $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
+
+ $this->assertEquals($expected, $share->getExpirationDate());
+ }
+
+ public function testValidateExpirationDateHookModification() {
+ $nextWeek = new \DateTime();
+ $nextWeek->add(new \DateInterval('P7D'));
+ $nextWeek->setTime(0,0,0);
- $this->assertEquals($future->format(\DateTime::ISO8601), $date->format(\DateTime::ISO8601));
+ $save = clone $nextWeek;
+
+ $hookListner = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
+ \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListner, 'listener');
+ $hookListner->expects($this->once())->method('listener')->will($this->returnCallback(function ($data) {
+ $data['expirationDate']->sub(new \DateInterval('P2D'));
+ }));
+
+ $share = $this->manager->newShare();
+ $share->setExpirationDate($nextWeek);
+
+ $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
+
+ $save->sub(new \DateInterval('P2D'));
+ $this->assertEquals($save, $share->getExpirationDate());
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Invalid date!
+ */
+ public function testValidateExpirationDateHookException() {
+ $nextWeek = new \DateTime();
+ $nextWeek->add(new \DateInterval('P7D'));
+ $nextWeek->setTime(0,0,0);
+
+ $share = $this->manager->newShare();
+ $share->setExpirationDate($nextWeek);
+
+ $hookListner = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
+ \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListner, 'listener');
+ $hookListner->expects($this->once())->method('listener')->will($this->returnCallback(function ($data) {
+ $data['accepted'] = false;
+ $data['message'] = 'Invalid date!';
+ }));
+
+ $this->invokePrivate($this->manager, 'validateExpirationDate', [$share]);
}
/**
@@ -1332,7 +1425,7 @@ class ManagerTest extends \Test\TestCase {
$date = new \DateTime();
- $share = new \OC\Share20\Share();
+ $share = $this->manager->newShare();
$share->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setNode($path)
->setSharedBy($sharedBy)
@@ -1355,8 +1448,7 @@ class ManagerTest extends \Test\TestCase {
->with($path);
$manager->expects($this->once())
->method('validateExpirationDate')
- ->with($date)
- ->will($this->returnArgument(0));
+ ->with($share);
$manager->expects($this->once())
->method('verifyPassword')
->with('password');
@@ -1761,14 +1853,10 @@ class ManagerTest extends \Test\TestCase {
$tomorrow->setTime(0,0,0);
$tomorrow->add(new \DateInterval('P1D'));
- $manager->expects($this->once())->method('canShare')->willReturn(true);
- $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
- $manager->expects($this->once())->method('validateExpirationDate')->with($tomorrow)->willReturn($tomorrow);
-
$file = $this->getMock('OCP\Files\File', [], [], 'File');
$file->method('getId')->willReturn(100);
- $share = new \OC\Share20\Share();
+ $share = $this->manager->newShare();
$share->setProviderId('foo')
->setId('42')
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
@@ -1778,6 +1866,10 @@ class ManagerTest extends \Test\TestCase {
->setExpirationDate($tomorrow)
->setNode($file);
+ $manager->expects($this->once())->method('canShare')->willReturn(true);
+ $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
+ $manager->expects($this->once())->method('validateExpirationDate')->with($share);
+
$this->defaultProvider->expects($this->once())
->method('update')
->with($share)