aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.drone.yml24
-rw-r--r--.travis.yml14
-rw-r--r--apps/comments/js/commentstabview.js2
-rw-r--r--apps/dav/lib/CardDAV/AddressBookImpl.php42
-rw-r--r--apps/dav/lib/Connector/Sabre/FilesPlugin.php9
-rw-r--r--apps/dav/tests/unit/CardDAV/AddressBookImplTest.php65
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php6
-rw-r--r--apps/federatedfilesharing/appinfo/routes.php11
-rw-r--r--apps/federatedfilesharing/lib/AppInfo/Application.php34
-rw-r--r--apps/federatedfilesharing/lib/Controller/RequestHandlerController.php (renamed from apps/federatedfilesharing/lib/RequestHandler.php)169
-rw-r--r--apps/federatedfilesharing/tests/Controller/RequestHandlerControllerTest.php (renamed from apps/federatedfilesharing/tests/RequestHandlerTest.php)40
-rw-r--r--apps/federation/appinfo/routes.php16
-rw-r--r--apps/federation/lib/Controller/OCSAuthAPIController.php (renamed from apps/federation/lib/API/OCSAuthAPI.php)60
-rw-r--r--apps/federation/tests/Controller/OCSAuthAPIControllerTest.php (renamed from apps/federation/tests/API/OCSAuthAPITest.php)70
-rw-r--r--apps/files/js/filelist.js16
-rw-r--r--apps/files/js/filesummary.js92
-rw-r--r--apps/files/lib/Command/Scan.php27
-rw-r--r--apps/files/tests/js/filelistSpec.js104
-rw-r--r--apps/files/tests/js/filesummarySpec.js74
-rw-r--r--apps/files_sharing/lib/Controller/ShareesAPIController.php2
-rw-r--r--apps/files_sharing/lib/sharedstorage.php10
-rw-r--r--apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php52
-rw-r--r--apps/files_trashbin/appinfo/register_command.php6
-rw-r--r--apps/files_trashbin/lib/Command/ExpireTrash.php127
-rw-r--r--apps/files_versions/appinfo/register_command.php5
-rw-r--r--apps/files_versions/lib/Command/ExpireVersions.php125
-rw-r--r--apps/theming/css/settings-admin.css16
-rw-r--r--apps/theming/js/settings-admin.js11
-rw-r--r--apps/theming/lib/Controller/ThemingController.php24
-rw-r--r--apps/theming/lib/Settings/Admin.php2
-rw-r--r--apps/theming/tests/Controller/ThemingControllerTest.php88
-rw-r--r--apps/theming/tests/Settings/AdminTest.php6
-rw-r--r--build/integration/features/bootstrap/BasicStructure.php24
-rw-r--r--build/integration/features/external-storage.feature26
-rw-r--r--build/integration/features/provisioning-v1.feature13
-rw-r--r--build/integration/features/webdav-related.feature18
-rwxr-xr-xbuild/integration/run.sh15
-rw-r--r--core/Application.php8
-rw-r--r--core/Controller/AvatarController.php75
-rw-r--r--core/Controller/LoginController.php23
-rw-r--r--core/Controller/LostController.php5
-rw-r--r--core/Controller/TokenController.php7
-rw-r--r--core/Controller/TwoFactorChallengeController.php2
-rw-r--r--core/Controller/UserController.php12
-rw-r--r--core/ajax/preview.php2
-rw-r--r--core/css/multiselect.css2
-rw-r--r--core/css/styles.css3
-rw-r--r--core/js/mimetypelist.js3
-rw-r--r--core/js/multiselect.js52
-rw-r--r--core/js/oc-dialogs.js14
-rw-r--r--core/js/sharedialoglinkshareview.js2
-rw-r--r--index.php8
-rw-r--r--lib/base.php5
-rw-r--r--lib/private/Files/Cache/Storage.php8
-rw-r--r--lib/private/Preview.php2
-rw-r--r--lib/private/Repair/RepairUnmergedShares.php50
-rw-r--r--lib/private/Security/Bruteforce/Throttler.php5
-rw-r--r--lib/private/User/Manager.php10
-rw-r--r--lib/private/User/Session.php3
-rw-r--r--lib/private/legacy/image.php26
-rw-r--r--ocs/routes.php73
-rw-r--r--resources/config/mimetypealiases.dist.json3
-rw-r--r--settings/css/settings.css9
-rw-r--r--settings/js/users/groups.js4
-rw-r--r--settings/js/users/users.js174
-rw-r--r--settings/templates/users/main.php4
-rw-r--r--settings/templates/users/part.createuser.php11
-rw-r--r--settings/templates/users/part.userlist.php8
-rw-r--r--status.php6
-rw-r--r--tests/Core/Controller/AvatarControllerTest.php6
-rw-r--r--tests/Core/Controller/LoginControllerTest.php76
-rw-r--r--tests/Core/Controller/TokenControllerTest.php16
-rw-r--r--tests/lib/Repair/RepairLegacyStoragesTest.php44
-rw-r--r--tests/lib/Repair/RepairUnmergedSharesTest.php211
-rw-r--r--tests/lib/User/SessionTest.php12
75 files changed, 1734 insertions, 695 deletions
diff --git a/.drone.yml b/.drone.yml
index 2798176c9a2..6016ebd6981 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -3,18 +3,6 @@ build:
image: nextcloudci/jsunit:1.0.6
commands:
- ./autotest-js.sh
- nodb-php5.4:
- image: nextcloudci/php5.4:1.0.7
- commands:
- - rm -rf data/* config/config.php # TODO: remove this - temporary fix for CI issues
- - git submodule update --init
- - NOCOVERAGE=true TEST_SELECTION=NODB ./autotest.sh sqlite
- nodb-php5.5:
- image: nextcloudci/php5.5:1.0.7
- commands:
- - rm -rf data/* config/config.php # TODO: remove this - temporary fix for CI issues
- - git submodule update --init
- - NOCOVERAGE=true TEST_SELECTION=NODB ./autotest.sh sqlite
nodb-php5.6:
image: nextcloudci/php5.6:1.0.6
commands:
@@ -27,18 +15,6 @@ build:
- rm -rf data/* config/config.php # TODO: remove this - temporary fix for CI issues
- git submodule update --init
- NOCOVERAGE=true TEST_SELECTION=NODB ./autotest.sh sqlite
- sqlite-php5.4:
- image: nextcloudci/php5.4:1.0.7
- commands:
- - rm -rf data/* config/config.php # TODO: remove this - temporary fix for CI issues
- - git submodule update --init
- - NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh sqlite
- sqlite-php5.5:
- image: nextcloudci/php5.5:1.0.7
- commands:
- - rm -rf data/* config/config.php # TODO: remove this - temporary fix for CI issues
- - git submodule update --init
- - NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh sqlite
sqlite-php5.6:
image: nextcloudci/php5.6:1.0.6
commands:
diff --git a/.travis.yml b/.travis.yml
index 05b07beac3d..ddcba167af1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
sudo: false
language: php
php:
- - 5.4
+ - 5.6
env:
global:
@@ -41,21 +41,17 @@ matrix:
include:
- php: 7.0
env: TC=autoloader;TEST_DAV=0
- - php: 5.4
+ - php: 5.6
env: DB=pgsql;TC=litmus-v1
- - php: 5.4
+ - php: 5.6
env: DB=sqlite;TC=carddav
- - php: 5.4
+ - php: 5.6
env: DB=sqlite;TC=caldav
- - php: 5.4
- env: DB=sqlite;TC=syntax;TEST_DAV=0
- - php: 5.5
- env: DB=sqlite;TC=syntax;TEST_DAV=0
- php: 5.6
env: DB=sqlite;TC=syntax;TEST_DAV=0
- php: 7.0
env: DB=sqlite;TC=syntax;TEST_DAV=0
- - php: 5.4
+ - php: 5.6
env: DB=sqlite;TC=app:check-code;TEST_DAV=0
fast_finish: true
diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js
index 9451e828f91..eae18c1d485 100644
--- a/apps/comments/js/commentstabview.js
+++ b/apps/comments/js/commentstabview.js
@@ -32,7 +32,7 @@
'{{/if}}' +
' </div>' +
' <form class="newCommentForm">' +
- ' <input type="text" class="message" placeholder="{{newMessagePlaceholder}}" value="{{{message}}}" />' +
+ ' <input type="text" class="message" placeholder="{{newMessagePlaceholder}}" value="{{message}}" />' +
' <input class="submit icon-confirm" type="submit" value="" />' +
'{{#if isEditMode}}' +
' <input class="cancel pull-right" type="button" value="{{cancelText}}" />' +
diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php
index c83ee613d37..9de54eec33d 100644
--- a/apps/dav/lib/CardDAV/AddressBookImpl.php
+++ b/apps/dav/lib/CardDAV/AddressBookImpl.php
@@ -28,6 +28,7 @@ use OCP\Constants;
use OCP\IAddressBook;
use OCP\IURLGenerator;
use Sabre\VObject\Component\VCard;
+use Sabre\VObject\Property;
use Sabre\VObject\Property\Text;
use Sabre\VObject\Reader;
use Sabre\VObject\UUIDUtil;
@@ -225,7 +226,7 @@ class AddressBookImpl implements IAddressBook {
];
foreach ($vCard->children as $property) {
- $result[$property->name] = $property->getValue();
+ /** @var \Sabre\VObject\Property\Unknown $property */
if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') {
$url = $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
@@ -237,14 +238,53 @@ class AddressBookImpl implements IAddressBook {
]) . '?photo';
$result['PHOTO'] = 'VALUE=uri:' . $url;
+
+ } else if ($property->name === 'X-SOCIALPROFILE') {
+ $type = $this->getTypeFromProperty($property);
+
+ // Type is the social network, when it's empty we don't need this.
+ if ($type !== null) {
+ if (!isset($result[$property->name])) {
+ $result[$property->name] = [];
+ }
+ $result[$property->name][$type] = $property->getValue();
+ }
+
+ // The following properties can be set multiple times
+ } else if (in_array($property->name, ['CLOUD', 'EMAIL', 'IMPP', 'TEL', 'URL'])) {
+ if (!isset($result[$property->name])) {
+ $result[$property->name] = [];
+ }
+
+ $result[$property->name][] = $property->getValue();
+
} else {
$result[$property->name] = $property->getValue();
}
}
+
if ($this->addressBookInfo['principaluri'] === 'principals/system/system' &&
$this->addressBookInfo['uri'] === 'system') {
$result['isLocalSystemBook'] = true;
}
return $result;
}
+
+ /**
+ * Get the type of the current property
+ *
+ * @param Property $property
+ * @return null|string
+ */
+ protected function getTypeFromProperty(Property $property) {
+ $parameters = $property->parameters();
+ // Type is the social network, when it's empty we don't need this.
+ if (isset($parameters['TYPE'])) {
+ /** @var \Sabre\VObject\Parameter $type */
+ $type = $parameters['TYPE'];
+ return $type->getValue();
+ }
+
+ return null;
+ }
}
diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
index c472f75b6bf..dd5f958ed4c 100644
--- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
@@ -324,18 +324,13 @@ class FilesPlugin extends ServerPlugin {
return $displayName;
});
- $propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) {
- if ($node->getPath() === '/') {
- return $this->config->getSystemValue('data-fingerprint', '');
- }
- });
-
$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
return json_encode($this->previewManager->isAvailable($node->getFileInfo()));
});
}
- if ($node instanceof \OCA\DAV\Files\FilesHome) {
+ if ($node instanceof \OCA\DAV\Connector\Sabre\Node
+ || $node instanceof \OCA\DAV\Files\FilesHome) {
$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) {
return $this->config->getSystemValue('data-fingerprint', '');
});
diff --git a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php
index ba8527dc76e..fa3cae27dec 100644
--- a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php
+++ b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php
@@ -60,7 +60,9 @@ class AddressBookImplTest extends TestCase {
$this->addressBookInfo = [
'id' => 42,
- '{DAV:}displayname' => 'display name'
+ 'uri' => 'system',
+ 'principaluri' => 'principals/system/system',
+ '{DAV:}displayname' => 'display name',
];
$this->addressBook = $this->getMockBuilder('OCA\DAV\CardDAV\AddressBook')
->disableOriginalConstructor()->getMock();
@@ -306,4 +308,65 @@ class AddressBookImplTest extends TestCase {
$this->assertSame($expectedVCardSerialized, $resultSerialized);
}
+ public function testVCard2Array() {
+ $vCard = new VCard();
+
+ $vCard->add($vCard->createProperty('FN', 'Full Name'));
+
+ // Multi-value properties
+ $vCard->add($vCard->createProperty('CLOUD', 'cloud-user1@localhost'));
+ $vCard->add($vCard->createProperty('CLOUD', 'cloud-user2@example.tld'));
+ $vCard->add($vCard->createProperty('EMAIL', 'email-user1@localhost'));
+ $vCard->add($vCard->createProperty('EMAIL', 'email-user2@example.tld'));
+ $vCard->add($vCard->createProperty('IMPP', 'impp-user1@localhost'));
+ $vCard->add($vCard->createProperty('IMPP', 'impp-user2@example.tld'));
+ $vCard->add($vCard->createProperty('TEL', '+49 123456789'));
+ $vCard->add($vCard->createProperty('TEL', '+1 555 123456789'));
+ $vCard->add($vCard->createProperty('URL', 'https://localhost'));
+ $vCard->add($vCard->createProperty('URL', 'https://example.tld'));
+
+ // Type depending properties
+ $property = $vCard->createProperty('X-SOCIALPROFILE', 'tw-example');
+ $property->add('TYPE', 'twitter');
+ $vCard->add($property);
+ $property = $vCard->createProperty('X-SOCIALPROFILE', 'fb-example');
+ $property->add('TYPE', 'facebook');
+ $vCard->add($property);
+
+ $array = $this->invokePrivate($this->addressBookImpl, 'vCard2Array', ['uri', $vCard]);
+ unset($array['PRODID']);
+
+ $this->assertEquals([
+ 'URI' => 'uri',
+ 'VERSION' => '3.0',
+ 'FN' => 'Full Name',
+ 'CLOUD' => [
+ 'cloud-user1@localhost',
+ 'cloud-user2@example.tld',
+ ],
+ 'EMAIL' => [
+ 'email-user1@localhost',
+ 'email-user2@example.tld',
+ ],
+ 'IMPP' => [
+ 'impp-user1@localhost',
+ 'impp-user2@example.tld',
+ ],
+ 'TEL' => [
+ '+49 123456789',
+ '+1 555 123456789',
+ ],
+ 'URL' => [
+ 'https://localhost',
+ 'https://example.tld',
+ ],
+
+ 'X-SOCIALPROFILE' => [
+ 'twitter'=> 'tw-example',
+ 'facebook'=> 'fb-example',
+ ],
+
+ 'isLocalSystemBook' => true,
+ ], $array);
+ }
}
diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php
index 6630c027541..e2d63868af0 100644
--- a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php
+++ b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php
@@ -213,7 +213,8 @@ class FilesPluginTest extends TestCase {
$this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME));
$this->assertEquals('foo', $propFind->get(self::OWNER_ID_PROPERTYNAME));
$this->assertEquals('M. Foo', $propFind->get(self::OWNER_DISPLAY_NAME_PROPERTYNAME));
- $this->assertEquals([self::SIZE_PROPERTYNAME, self::DATA_FINGERPRINT_PROPERTYNAME], $propFind->get404Properties());
+ $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME));
+ $this->assertEquals([self::SIZE_PROPERTYNAME], $propFind->get404Properties());
}
public function testGetPropertiesForFileHome() {
@@ -357,7 +358,8 @@ class FilesPluginTest extends TestCase {
$this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME));
$this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME));
$this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME));
- $this->assertEquals([self::DOWNLOADURL_PROPERTYNAME, self::DATA_FINGERPRINT_PROPERTYNAME], $propFind->get404Properties());
+ $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME));
+ $this->assertEquals([self::DOWNLOADURL_PROPERTYNAME], $propFind->get404Properties());
}
public function testGetPropertiesForRootDirectory() {
diff --git a/apps/federatedfilesharing/appinfo/routes.php b/apps/federatedfilesharing/appinfo/routes.php
index a4f56e372c9..9caaa939348 100644
--- a/apps/federatedfilesharing/appinfo/routes.php
+++ b/apps/federatedfilesharing/appinfo/routes.php
@@ -26,5 +26,14 @@ return [
'routes' => [
['name' => 'MountPublicLink#createFederatedShare', 'url' => '/createFederatedShare', 'verb' => 'POST'],
['name' => 'MountPublicLink#askForFederatedShare', 'url' => '/askForFederatedShare', 'verb' => 'POST'],
- ]
+ ],
+ 'ocs' => [
+ ['root' => '/cloud', 'name' => 'RequestHandler#createShare', 'url' => '/shares', 'verb' => 'POST'],
+ ['root' => '/cloud', 'name' => 'RequestHandler#reShare', 'url' => '/shares/{id}/reshare', 'verb' => 'POST'],
+ ['root' => '/cloud', 'name' => 'RequestHandler#updatePermissions', 'url' => '/shares/{id}/permissions', 'verb' => 'POST'],
+ ['root' => '/cloud', 'name' => 'RequestHandler#acceptShare', 'url' => '/shares/{id}/accept', 'verb' => 'POST'],
+ ['root' => '/cloud', 'name' => 'RequestHandler#declineShare', 'url' => '/shares/{id}/decline', 'verb' => 'POST'],
+ ['root' => '/cloud', 'name' => 'RequestHandler#unshare', 'url' => '/shares/{id}/unshare', 'verb' => 'POST'],
+ ['root' => '/cloud', 'name' => 'RequestHandler#revoke', 'url' => '/shares/{id}/revoke', 'verb' => 'POST'],
+ ],
];
diff --git a/apps/federatedfilesharing/lib/AppInfo/Application.php b/apps/federatedfilesharing/lib/AppInfo/Application.php
index b767a322505..b470bb3e584 100644
--- a/apps/federatedfilesharing/lib/AppInfo/Application.php
+++ b/apps/federatedfilesharing/lib/AppInfo/Application.php
@@ -24,7 +24,12 @@
namespace OCA\FederatedFileSharing\AppInfo;
+use OC\AppFramework\Utility\SimpleContainer;
+use OCA\FederatedFileSharing\AddressHandler;
+use OCA\FederatedFileSharing\Controller\RequestHandlerController;
use OCA\FederatedFileSharing\FederatedShareProvider;
+use OCA\FederatedFileSharing\Notifications;
+use OCA\FederatedFileSharing\RequestHandler;
use OCP\AppFramework\App;
class Application extends App {
@@ -34,6 +39,35 @@ class Application extends App {
public function __construct() {
parent::__construct('federatedfilesharing');
+
+ $container = $this->getContainer();
+ $server = $container->getServer();
+
+ $container->registerService('RequestHandlerController', function(SimpleContainer $c) use ($server) {
+ $addressHandler = new AddressHandler(
+ $server->getURLGenerator(),
+ $server->getL10N('federatedfilesharing')
+ );
+ $notification = new Notifications(
+ $addressHandler,
+ $server->getHTTPClientService(),
+ new \OCA\FederatedFileSharing\DiscoveryManager(
+ $server->getMemCacheFactory(),
+ $server->getHTTPClientService()
+ ),
+ \OC::$server->getJobList()
+ );
+ return new RequestHandlerController(
+ $c->query('AppName'),
+ $server->getRequest(),
+ $this->getFederatedShareProvider(),
+ $server->getDatabaseConnection(),
+ $server->getShareManager(),
+ $notification,
+ $addressHandler,
+ $server->getUserManager()
+ );
+ });
}
/**
diff --git a/apps/federatedfilesharing/lib/RequestHandler.php b/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php
index f531c7bcb4a..9a41962ee3a 100644
--- a/apps/federatedfilesharing/lib/RequestHandler.php
+++ b/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php
@@ -24,25 +24,28 @@
*
*/
-namespace OCA\FederatedFileSharing;
+namespace OCA\FederatedFileSharing\Controller;
+use OCA\FederatedFileSharing\DiscoveryManager;
use OCA\Files_Sharing\Activity;
+use OCA\FederatedFileSharing\AddressHandler;
+use OCA\FederatedFileSharing\FederatedShareProvider;
+use OCA\FederatedFileSharing\Notifications;
use OCP\AppFramework\Http;
+use OCP\AppFramework\OCS\OCSBadRequestException;
+use OCP\AppFramework\OCS\OCSException;
+use OCP\AppFramework\OCS\OCSForbiddenException;
+use OCP\AppFramework\OCS\OCSNotFoundException;
+use OCP\AppFramework\OCSController;
use OCP\Constants;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\Share;
+use OCP\Share\IShare;
-/**
- * Class RequestHandler
- *
- * handles OCS Request to the federated share API
- *
- * @package OCA\FederatedFileSharing\API
- */
-class RequestHandler {
+class RequestHandlerController extends OCSController {
/** @var FederatedShareProvider */
private $federatedShareProvider;
@@ -53,9 +56,6 @@ class RequestHandler {
/** @var Share\IManager */
private $shareManager;
- /** @var IRequest */
- private $request;
-
/** @var Notifications */
private $notifications;
@@ -71,41 +71,47 @@ class RequestHandler {
/**
* Server2Server constructor.
*
+ * @param string $appName
+ * @param IRequest $request
* @param FederatedShareProvider $federatedShareProvider
* @param IDBConnection $connection
* @param Share\IManager $shareManager
- * @param IRequest $request
* @param Notifications $notifications
* @param AddressHandler $addressHandler
* @param IUserManager $userManager
*/
- public function __construct(FederatedShareProvider $federatedShareProvider,
+ public function __construct($appName,
+ IRequest $request,
+ FederatedShareProvider $federatedShareProvider,
IDBConnection $connection,
Share\IManager $shareManager,
- IRequest $request,
Notifications $notifications,
AddressHandler $addressHandler,
IUserManager $userManager
) {
+ parent::__construct($appName, $request);
+
$this->federatedShareProvider = $federatedShareProvider;
$this->connection = $connection;
$this->shareManager = $shareManager;
- $this->request = $request;
$this->notifications = $notifications;
$this->addressHandler = $addressHandler;
$this->userManager = $userManager;
}
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* create a new share
*
- * @param array $params
- * @return \OC_OCS_Result
+ * @return Http\DataResponse
+ * @throws OCSException
*/
- public function createShare($params) {
+ public function createShare() {
if (!$this->isS2SEnabled(true)) {
- return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing');
+ throw new OCSException('Server does not support federated cloud sharing', 503);
}
$remote = isset($_POST['remote']) ? $_POST['remote'] : null;
@@ -121,7 +127,7 @@ class RequestHandler {
if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
if(!\OCP\Util::isValidFileName($name)) {
- return new \OC_OCS_Result(null, 400, 'The mountpoint name contains invalid characters.');
+ throw new OCSException('The mountpoint name contains invalid characters.', 400);
}
// FIXME this should be a method in the user management instead
@@ -134,7 +140,7 @@ class RequestHandler {
\OCP\Util::writeLog('files_sharing', 'shareWith after, ' . $shareWith, \OCP\Util::DEBUG);
if (!\OCP\User::userExists($shareWith)) {
- return new \OC_OCS_Result(null, 400, 'User does not exists');
+ throw new OCSException('User does not exists', 400);
}
\OC_Util::setupFS($shareWith);
@@ -192,25 +198,30 @@ class RequestHandler {
$notificationManager->notify($notification);
- return new \OC_OCS_Result();
+ return new Http\DataResponse();
} catch (\Exception $e) {
\OCP\Util::writeLog('files_sharing', 'server can not add remote share, ' . $e->getMessage(), \OCP\Util::ERROR);
- return new \OC_OCS_Result(null, 500, 'internal server error, was not able to add share from ' . $remote);
+ throw new OCSException('internal server error, was not able to add share from ' . $remote, 500);
}
}
- return new \OC_OCS_Result(null, 400, 'server can not add remote share, missing parameter');
+ throw new OCSException('server can not add remote share, missing parameter', 400);
}
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* create re-share on behalf of another user
*
- * @param $params
- * @return \OC_OCS_Result
+ * @param int $id
+ * @return Http\DataResponse
+ * @throws OCSBadRequestException
+ * @throws OCSForbiddenException
+ * @throws OCSNotFoundException
*/
- public function reShare($params) {
+ public function reShare($id) {
- $id = isset($params['id']) ? (int)$params['id'] : null;
$token = $this->request->getParam('token', null);
$shareWith = $this->request->getParam('shareWith', null);
$permission = (int)$this->request->getParam('permission', null);
@@ -222,13 +233,13 @@ class RequestHandler {
$permission === null ||
$remoteId === null
) {
- return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST);
+ throw new OCSBadRequestException();
}
try {
$share = $this->federatedShareProvider->getShareById($id);
} catch (Share\Exceptions\ShareNotFound $e) {
- return new \OC_OCS_Result(null, Http::STATUS_NOT_FOUND);
+ throw new OCSNotFoundException();
}
// don't allow to share a file back to the owner
@@ -236,7 +247,7 @@ class RequestHandler {
$owner = $share->getShareOwner();
$currentServer = $this->addressHandler->generateRemoteURL();
if ($this->addressHandler->compareAddresses($user, $remote,$owner , $currentServer)) {
- return new \OC_OCS_Result(null, Http::STATUS_FORBIDDEN);
+ throw new OCSForbiddenException();
}
if ($this->verifyShare($share, $token)) {
@@ -250,37 +261,42 @@ class RequestHandler {
try {
$result = $this->federatedShareProvider->create($share);
$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $remoteId);
- return new \OC_OCS_Result(['token' => $result->getToken(), 'remoteId' => $result->getId()]);
+ return new Http\DataResponse([
+ 'token' => $result->getToken(),
+ 'remoteId' => $result->getId()
+ ]);
} catch (\Exception $e) {
- return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST);
+ throw new OCSBadRequestException();
}
} else {
- return new \OC_OCS_Result(null, Http::STATUS_FORBIDDEN);
+ throw new OCSForbiddenException();
}
}
- return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST);
-
+ throw new OCSBadRequestException();
}
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* accept server-to-server share
*
- * @param array $params
- * @return \OC_OCS_Result
+ * @param int $id
+ * @return Http\DataResponse
+ * @throws OCSException
*/
- public function acceptShare($params) {
+ public function acceptShare($id) {
if (!$this->isS2SEnabled()) {
- return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing');
+ throw new OCSException('Server does not support federated cloud sharing', 503);
}
- $id = $params['id'];
$token = isset($_POST['token']) ? $_POST['token'] : null;
try {
$share = $this->federatedShareProvider->getShareById($id);
} catch (Share\Exceptions\ShareNotFound $e) {
- return new \OC_OCS_Result();
+ return new Http\DataResponse();
}
if ($this->verifyShare($share, $token)) {
@@ -292,7 +308,7 @@ class RequestHandler {
}
}
- return new \OC_OCS_Result();
+ return new Http\DataResponse();
}
protected function executeAcceptShare(Share\IShare $share) {
@@ -309,24 +325,27 @@ class RequestHandler {
}
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* decline server-to-server share
*
- * @param array $params
- * @return \OC_OCS_Result
+ * @param int $id
+ * @return Http\DataResponse
+ * @throws OCSException
*/
- public function declineShare($params) {
+ public function declineShare($id) {
if (!$this->isS2SEnabled()) {
- return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing');
+ throw new OCSException('Server does not support federated cloud sharing', 503);
}
- $id = (int)$params['id'];
$token = isset($_POST['token']) ? $_POST['token'] : null;
try {
$share = $this->federatedShareProvider->getShareById($id);
} catch (Share\Exceptions\ShareNotFound $e) {
- return new \OC_OCS_Result();
+ return new Http\DataResponse();
}
if($this->verifyShare($share, $token)) {
@@ -338,7 +357,7 @@ class RequestHandler {
$this->executeDeclineShare($share);
}
- return new \OC_OCS_Result();
+ return new Http\DataResponse();
}
/**
@@ -376,18 +395,21 @@ class RequestHandler {
}
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* remove server-to-server share if it was unshared by the owner
*
- * @param array $params
- * @return \OC_OCS_Result
+ * @param int $id
+ * @return Http\DataResponse
+ * @throws OCSException
*/
- public function unshare($params) {
+ public function unshare($id) {
if (!$this->isS2SEnabled()) {
- return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing');
+ throw new OCSException('Server does not support federated cloud sharing', 503);
}
- $id = $params['id'];
$token = isset($_POST['token']) ? $_POST['token'] : null;
$query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?');
@@ -423,7 +445,7 @@ class RequestHandler {
'', '', $user, Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_MEDIUM);
}
- return new \OC_OCS_Result();
+ return new Http\DataResponse();
}
private function cleanupRemote($remote) {
@@ -434,24 +456,26 @@ class RequestHandler {
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* federated share was revoked, either by the owner or the re-sharer
*
- * @param $params
- * @return \OC_OCS_Result
+ * @param int $id
+ * @return Http\DataResponse
+ * @throws OCSBadRequestException
*/
- public function revoke($params) {
- $id = (int)$params['id'];
+ public function revoke($id) {
$token = $this->request->getParam('token');
$share = $this->federatedShareProvider->getShareById($id);
if ($this->verifyShare($share, $token)) {
$this->federatedShareProvider->removeShareFromTable($share);
- return new \OC_OCS_Result();
+ return new Http\DataResponse();
}
- return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST);
-
+ throw new OCSBadRequestException();
}
/**
@@ -537,20 +561,23 @@ class RequestHandler {
}
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* update share information to keep federated re-shares in sync
*
- * @param array $params
- * @return \OC_OCS_Result
+ * @param int $id
+ * @return Http\DataResponse
+ * @throws OCSBadRequestException
*/
- public function updatePermissions($params) {
- $id = (int)$params['id'];
+ public function updatePermissions($id) {
$token = $this->request->getParam('token', null);
$permissions = $this->request->getParam('permissions', null);
try {
$share = $this->federatedShareProvider->getShareById($id);
} catch (Share\Exceptions\ShareNotFound $e) {
- return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST);
+ throw new OCSBadRequestException();
}
$validPermission = ctype_digit($permissions);
@@ -558,10 +585,10 @@ class RequestHandler {
if ($validPermission && $validToken) {
$this->updatePermissionsInDatabase($share, (int)$permissions);
} else {
- return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST);
+ throw new OCSBadRequestException();
}
- return new \OC_OCS_Result();
+ return new Http\DataResponse();
}
/**
diff --git a/apps/federatedfilesharing/tests/RequestHandlerTest.php b/apps/federatedfilesharing/tests/Controller/RequestHandlerControllerTest.php
index 8f9f1384184..8e1000fb500 100644
--- a/apps/federatedfilesharing/tests/RequestHandlerTest.php
+++ b/apps/federatedfilesharing/tests/Controller/RequestHandlerControllerTest.php
@@ -30,7 +30,7 @@ namespace OCA\FederatedFileSharing\Tests;
use OC\Files\Filesystem;
use OCA\FederatedFileSharing\DiscoveryManager;
use OCA\FederatedFileSharing\FederatedShareProvider;
-use OCA\FederatedFileSharing\RequestHandler;
+use OCA\FederatedFileSharing\Controller\RequestHandlerController;
use OCP\IUserManager;
use OCP\Share\IShare;
@@ -40,7 +40,7 @@ use OCP\Share\IShare;
* @package OCA\FederatedFileSharing\Tests
* @group DB
*/
-class RequestHandlerTest extends TestCase {
+class RequestHandlerControllerTest extends TestCase {
const TEST_FOLDER_NAME = '/folder_share_api_test';
@@ -50,23 +50,23 @@ class RequestHandlerTest extends TestCase {
private $connection;
/**
- * @var RequestHandler
+ * @var RequestHandlerController
*/
private $s2s;
- /** @var \OCA\FederatedFileSharing\FederatedShareProvider | PHPUnit_Framework_MockObject_MockObject */
+ /** @var \OCA\FederatedFileSharing\FederatedShareProvider|\PHPUnit_Framework_MockObject_MockObject */
private $federatedShareProvider;
- /** @var \OCA\FederatedFileSharing\Notifications | PHPUnit_Framework_MockObject_MockObject */
+ /** @var \OCA\FederatedFileSharing\Notifications|\PHPUnit_Framework_MockObject_MockObject */
private $notifications;
- /** @var \OCA\FederatedFileSharing\AddressHandler | PHPUnit_Framework_MockObject_MockObject */
+ /** @var \OCA\FederatedFileSharing\AddressHandler|\PHPUnit_Framework_MockObject_MockObject */
private $addressHandler;
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */
+ /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
private $userManager;
- /** @var IShare | \PHPUnit_Framework_MockObject_MockObject */
+ /** @var IShare|\PHPUnit_Framework_MockObject_MockObject */
private $share;
protected function setUp() {
@@ -77,12 +77,12 @@ class RequestHandlerTest extends TestCase {
$config = $this->getMockBuilder('\OCP\IConfig')
->disableOriginalConstructor()->getMock();
- $clientService = $this->getMock('\OCP\Http\Client\IClientService');
+ $clientService = $this->getMockBuilder('\OCP\Http\Client\IClientService')->getMock();
$httpHelperMock = $this->getMockBuilder('\OC\HTTPHelper')
->setConstructorArgs([$config, $clientService])
->getMock();
$httpHelperMock->expects($this->any())->method('post')->with($this->anything())->will($this->returnValue(true));
- $this->share = $this->getMock('\OCP\Share\IShare');
+ $this->share = $this->getMockBuilder('\OCP\Share\IShare')->getMock();
$this->federatedShareProvider = $this->getMockBuilder('OCA\FederatedFileSharing\FederatedShareProvider')
->disableOriginalConstructor()->getMock();
$this->federatedShareProvider->expects($this->any())
@@ -96,15 +96,16 @@ class RequestHandlerTest extends TestCase {
->disableOriginalConstructor()->getMock();
$this->addressHandler = $this->getMockBuilder('OCA\FederatedFileSharing\AddressHandler')
->disableOriginalConstructor()->getMock();
- $this->userManager = $this->getMock('OCP\IUserManager');
+ $this->userManager = $this->getMockBuilder('OCP\IUserManager')->getMock();
$this->registerHttpHelper($httpHelperMock);
- $this->s2s = new RequestHandler(
+ $this->s2s = new RequestHandlerController(
+ 'federatedfilesharing',
+ \OC::$server->getRequest(),
$this->federatedShareProvider,
\OC::$server->getDatabaseConnection(),
\OC::$server->getShareManager(),
- \OC::$server->getRequest(),
$this->notifications,
$this->addressHandler,
$this->userManager
@@ -127,7 +128,7 @@ class RequestHandlerTest extends TestCase {
/**
* Register an http helper mock for testing purposes.
- * @param $httpHelper http helper mock
+ * @param \OC\HTTPHelper $httpHelper helper mock
*/
private function registerHttpHelper($httpHelper) {
$this->oldHttpHelper = \OC::$server->query('HTTPHelper');
@@ -158,9 +159,7 @@ class RequestHandlerTest extends TestCase {
$_POST['shareWith'] = self::TEST_FILES_SHARING_API_USER2;
$_POST['remoteId'] = 1;
- $result = $this->s2s->createShare(null);
-
- $this->assertTrue($result->succeeded());
+ $this->s2s->createShare(null);
$query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share_external` WHERE `remote_id` = ?');
$result = $query->execute(array('1'));
@@ -178,13 +177,14 @@ class RequestHandlerTest extends TestCase {
function testDeclineShare() {
- $this->s2s = $this->getMockBuilder('\OCA\FederatedFileSharing\RequestHandler')
+ $this->s2s = $this->getMockBuilder('\OCA\FederatedFileSharing\Controller\RequestHandlerController')
->setConstructorArgs(
[
+ 'federatedfilessharing',
+ \OC::$server->getRequest(),
$this->federatedShareProvider,
\OC::$server->getDatabaseConnection(),
\OC::$server->getShareManager(),
- \OC::$server->getRequest(),
$this->notifications,
$this->addressHandler,
$this->userManager
@@ -197,7 +197,7 @@ class RequestHandlerTest extends TestCase {
$_POST['token'] = 'token';
- $this->s2s->declineShare(array('id' => 42));
+ $this->s2s->declineShare(42);
}
diff --git a/apps/federation/appinfo/routes.php b/apps/federation/appinfo/routes.php
index 03acc60c682..b9515812a01 100644
--- a/apps/federation/appinfo/routes.php
+++ b/apps/federation/appinfo/routes.php
@@ -41,8 +41,18 @@ $application->registerRoutes(
'url' => '/auto-add-servers',
'verb' => 'POST'
],
- ]
+ ],
+ 'ocs' => [
+ [
+ 'name' => 'OCSAuthAPI#getSharedSecret',
+ 'url' => '/api/v1/shared-secret',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'OCSAuthAPI#requestSharedSecret',
+ 'url' => '/api/v1/request-shared-secret',
+ 'verb' => 'POST',
+ ],
+ ],
]
);
-
-$application->registerOCSApi();
diff --git a/apps/federation/lib/API/OCSAuthAPI.php b/apps/federation/lib/Controller/OCSAuthAPIController.php
index a22de155d4c..6cd3b1890ef 100644
--- a/apps/federation/lib/API/OCSAuthAPI.php
+++ b/apps/federation/lib/Controller/OCSAuthAPIController.php
@@ -25,11 +25,13 @@
*/
-namespace OCA\Federation\API;
+namespace OCA\Federation\Controller;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
+use OCP\AppFramework\OCS\OCSForbiddenException;
+use OCP\AppFramework\OCSController;
use OCP\BackgroundJob\IJobList;
use OCP\ILogger;
use OCP\IRequest;
@@ -40,12 +42,9 @@ use OCP\Security\ISecureRandom;
*
* OCS API end-points to exchange shared secret between two connected ownClouds
*
- * @package OCA\Federation\API
+ * @package OCA\Federation\Controller
*/
-class OCSAuthAPI {
-
- /** @var IRequest */
- private $request;
+class OCSAuthAPIController extends OCSController{
/** @var ISecureRandom */
private $secureRandom;
@@ -65,6 +64,7 @@ class OCSAuthAPI {
/**
* OCSAuthAPI constructor.
*
+ * @param string $appName
* @param IRequest $request
* @param ISecureRandom $secureRandom
* @param IJobList $jobList
@@ -73,6 +73,7 @@ class OCSAuthAPI {
* @param ILogger $logger
*/
public function __construct(
+ $appName,
IRequest $request,
ISecureRandom $secureRandom,
IJobList $jobList,
@@ -80,7 +81,8 @@ class OCSAuthAPI {
DbHandler $dbHandler,
ILogger $logger
) {
- $this->request = $request;
+ parent::__construct($appName, $request);
+
$this->secureRandom = $secureRandom;
$this->jobList = $jobList;
$this->trustedServers = $trustedServers;
@@ -89,18 +91,20 @@ class OCSAuthAPI {
}
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* request received to ask remote server for a shared secret
*
- * @return \OC_OCS_Result
+ * @param string $url
+ * @param string $token
+ * @return Http\DataResponse
+ * @throws OCSForbiddenException
*/
- public function requestSharedSecret() {
-
- $url = $this->request->getParam('url');
- $token = $this->request->getParam('token');
-
+ public function requestSharedSecret($url, $token) {
if ($this->trustedServers->isTrustedServer($url) === false) {
$this->logger->error('remote server not trusted (' . $url . ') while requesting shared secret', ['app' => 'federation']);
- return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
+ throw new OCSForbiddenException();
}
// if both server initiated the exchange of the shared secret the greater
@@ -111,7 +115,7 @@ class OCSAuthAPI {
'remote server (' . $url . ') presented lower token. We will initiate the exchange of the shared secret.',
['app' => 'federation']
);
- return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
+ throw new OCSForbiddenException();
}
// we ask for the shared secret so we no longer have to ask the other server
@@ -131,23 +135,24 @@ class OCSAuthAPI {
]
);
- return new \OC_OCS_Result(null, Http::STATUS_OK);
-
+ return new Http\DataResponse();
}
/**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
* create shared secret and return it
*
- * @return \OC_OCS_Result
+ * @param string $url
+ * @param string $token
+ * @return Http\DataResponse
+ * @throws OCSForbiddenException
*/
- public function getSharedSecret() {
-
- $url = $this->request->getParam('url');
- $token = $this->request->getParam('token');
-
+ public function getSharedSecret($url, $token) {
if ($this->trustedServers->isTrustedServer($url) === false) {
$this->logger->error('remote server not trusted (' . $url . ') while getting shared secret', ['app' => 'federation']);
- return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
+ throw new OCSForbiddenException();
}
if ($this->isValidToken($url, $token) === false) {
@@ -156,7 +161,7 @@ class OCSAuthAPI {
'remote server (' . $url . ') didn\'t send a valid token (got "' . $token . '" but expected "'. $expectedToken . '") while getting shared secret',
['app' => 'federation']
);
- return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
+ throw new OCSForbiddenException();
}
$sharedSecret = $this->secureRandom->generate(32);
@@ -165,8 +170,9 @@ class OCSAuthAPI {
// reset token after the exchange of the shared secret was successful
$this->dbHandler->addToken($url, '');
- return new \OC_OCS_Result(['sharedSecret' => $sharedSecret], Http::STATUS_OK);
-
+ return new Http\DataResponse([
+ 'sharedSecret' => $sharedSecret
+ ]);
}
protected function isValidToken($url, $token) {
diff --git a/apps/federation/tests/API/OCSAuthAPITest.php b/apps/federation/tests/Controller/OCSAuthAPIControllerTest.php
index 7861e917ff8..2b231b4fca0 100644
--- a/apps/federation/tests/API/OCSAuthAPITest.php
+++ b/apps/federation/tests/Controller/OCSAuthAPIControllerTest.php
@@ -22,20 +22,21 @@
*/
-namespace OCA\Federation\Tests\API;
+namespace OCA\Federation\Tests\Controller;
use OC\BackgroundJob\JobList;
-use OCA\Federation\API\OCSAuthAPI;
+use OCA\Federation\Controller\OCSAuthAPIController;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
+use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\ILogger;
use OCP\IRequest;
use OCP\Security\ISecureRandom;
use Test\TestCase;
-class OCSAuthAPITest extends TestCase {
+class OCSAuthAPIControllerTest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject | IRequest */
private $request;
@@ -55,14 +56,14 @@ class OCSAuthAPITest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject | ILogger */
private $logger;
- /** @var OCSAuthApi */
+ /** @var OCSAuthAPIController */
private $ocsAuthApi;
public function setUp() {
parent::setUp();
- $this->request = $this->getMock('OCP\IRequest');
- $this->secureRandom = $this->getMock('OCP\Security\ISecureRandom');
+ $this->request = $this->getMockBuilder('OCP\IRequest')->getMock();
+ $this->secureRandom = $this->getMockBuilder('OCP\Security\ISecureRandom')->getMock();
$this->trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers')
->disableOriginalConstructor()->getMock();
$this->dbHandler = $this->getMockBuilder('OCA\Federation\DbHandler')
@@ -72,7 +73,8 @@ class OCSAuthAPITest extends TestCase {
$this->logger = $this->getMockBuilder('OCP\ILogger')
->disableOriginalConstructor()->getMock();
- $this->ocsAuthApi = new OCSAuthAPI(
+ $this->ocsAuthApi = new OCSAuthAPIController(
+ 'federation',
$this->request,
$this->secureRandom,
$this->jobList,
@@ -89,21 +91,19 @@ class OCSAuthAPITest extends TestCase {
* @param string $token
* @param string $localToken
* @param bool $isTrustedServer
- * @param int $expected
+ * @param bool $ok
*/
- public function testRequestSharedSecret($token, $localToken, $isTrustedServer, $expected) {
+ public function testRequestSharedSecret($token, $localToken, $isTrustedServer, $ok) {
$url = 'url';
- $this->request->expects($this->at(0))->method('getParam')->with('url')->willReturn($url);
- $this->request->expects($this->at(1))->method('getParam')->with('token')->willReturn($token);
$this->trustedServers
->expects($this->once())
->method('isTrustedServer')->with($url)->willReturn($isTrustedServer);
$this->dbHandler->expects($this->any())
->method('getToken')->with($url)->willReturn($localToken);
- if ($expected === Http::STATUS_OK) {
+ if ($ok) {
$this->jobList->expects($this->once())->method('add')
->with('OCA\Federation\BackgroundJob\GetSharedSecret', ['url' => $url, 'token' => $token]);
$this->jobList->expects($this->once())->method('remove')
@@ -113,15 +113,19 @@ class OCSAuthAPITest extends TestCase {
$this->jobList->expects($this->never())->method('remove');
}
- $result = $this->ocsAuthApi->requestSharedSecret();
- $this->assertSame($expected, $result->getStatusCode());
+ try {
+ $this->ocsAuthApi->requestSharedSecret($url, $token);
+ $this->assertTrue($ok);
+ } catch (OCSForbiddenException $e) {
+ $this->assertFalse($ok);
+ }
}
public function dataTestRequestSharedSecret() {
return [
- ['token2', 'token1', true, Http::STATUS_OK],
- ['token1', 'token2', false, Http::STATUS_FORBIDDEN],
- ['token1', 'token2', true, Http::STATUS_FORBIDDEN],
+ ['token2', 'token1', true, true],
+ ['token1', 'token2', false, false],
+ ['token1', 'token2', true, false],
];
}
@@ -130,20 +134,18 @@ class OCSAuthAPITest extends TestCase {
*
* @param bool $isTrustedServer
* @param bool $isValidToken
- * @param int $expected
+ * @param bool $ok
*/
- public function testGetSharedSecret($isTrustedServer, $isValidToken, $expected) {
+ public function testGetSharedSecret($isTrustedServer, $isValidToken, $ok) {
$url = 'url';
$token = 'token';
- $this->request->expects($this->at(0))->method('getParam')->with('url')->willReturn($url);
- $this->request->expects($this->at(1))->method('getParam')->with('token')->willReturn($token);
-
- /** @var OCSAuthAPI | \PHPUnit_Framework_MockObject_MockObject $ocsAuthApi */
- $ocsAuthApi = $this->getMockBuilder('OCA\Federation\API\OCSAuthAPI')
+ /** @var OCSAuthAPIController | \PHPUnit_Framework_MockObject_MockObject $ocsAuthApi */
+ $ocsAuthApi = $this->getMockBuilder('OCA\Federation\Controller\OCSAuthAPIController')
->setConstructorArgs(
[
+ 'federation',
$this->request,
$this->secureRandom,
$this->jobList,
@@ -159,7 +161,7 @@ class OCSAuthAPITest extends TestCase {
$ocsAuthApi->expects($this->any())
->method('isValidToken')->with($url, $token)->willReturn($isValidToken);
- if($expected === Http::STATUS_OK) {
+ if($ok) {
$this->secureRandom->expects($this->once())->method('generate')->with(32)
->willReturn('secret');
$this->trustedServers->expects($this->once())
@@ -173,22 +175,22 @@ class OCSAuthAPITest extends TestCase {
$this->dbHandler->expects($this->never())->method('addToken');
}
- $result = $ocsAuthApi->getSharedSecret();
-
- $this->assertSame($expected, $result->getStatusCode());
-
- if ($expected === Http::STATUS_OK) {
+ try {
+ $result = $ocsAuthApi->getSharedSecret($url, $token);
+ $this->assertTrue($ok);
$data = $result->getData();
$this->assertSame('secret', $data['sharedSecret']);
+ } catch (OCSForbiddenException $e) {
+ $this->assertFalse($ok);
}
}
public function dataTestGetSharedSecret() {
return [
- [true, true, Http::STATUS_OK],
- [false, true, Http::STATUS_FORBIDDEN],
- [true, false, Http::STATUS_FORBIDDEN],
- [false, false, Http::STATUS_FORBIDDEN],
+ [true, true, true],
+ [false, true, false],
+ [true, false, false],
+ [false, false, false],
];
}
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index f191ade240b..ca41012764a 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -199,6 +199,7 @@
* @param options.folderDropOptions folder drop options, disabled by default
* @param options.scrollTo name of file to scroll to after the first load
* @param {OC.Files.Client} [options.filesClient] files API client
+ * @param {OC.Backbone.Model} [options.filesConfig] files app configuration
* @private
*/
initialize: function($el, options) {
@@ -212,6 +213,10 @@
this._filesConfig = options.config;
} else if (!_.isUndefined(OCA.Files) && !_.isUndefined(OCA.Files.App)) {
this._filesConfig = OCA.Files.App.getFilesConfig();
+ } else {
+ this._filesConfig = new OC.Backbone.Model({
+ 'showhidden': false
+ });
}
if (options.dragOptions) {
@@ -239,6 +244,7 @@
this._filesConfig.on('change:showhidden', function() {
var showHidden = this.get('showhidden');
self.$el.toggleClass('hide-hidden-files', !showHidden);
+ self.updateSelectionSummary();
if (!showHidden) {
// hiding files could make the page too small, need to try rendering next page
@@ -264,7 +270,7 @@
this.files = [];
this._selectedFiles = {};
- this._selectionSummary = new OCA.Files.FileSummary();
+ this._selectionSummary = new OCA.Files.FileSummary(undefined, {config: this._filesConfig});
// dummy root dir info
this.dirInfo = new OC.Files.FileInfo({});
@@ -2304,7 +2310,7 @@
var $tr = $('<tr class="summary"></tr>');
this.$el.find('tfoot').append($tr);
- return new OCA.Files.FileSummary($tr);
+ return new OCA.Files.FileSummary($tr, {config: this._filesConfig});
},
updateEmptyContent: function() {
var permissions = this.getDirectoryPermissions();
@@ -2451,6 +2457,7 @@
var summary = this._selectionSummary.summary;
var selection;
+ var showHidden = !!this._filesConfig.get('showhidden');
if (summary.totalFiles === 0 && summary.totalDirs === 0) {
this.$el.find('#headerName a.name>span:first').text(t('files','Name'));
this.$el.find('#headerSize a>span:first').text(t('files','Size'));
@@ -2477,6 +2484,11 @@
selection = fileInfo;
}
+ if (!showHidden && summary.totalHidden > 0) {
+ var hiddenInfo = n('files', 'including %n hidden', 'including %n hidden', summary.totalHidden);
+ selection += ' (' + hiddenInfo + ')';
+ }
+
this.$el.find('#headerName a.name>span:first').text(selection);
this.$el.find('#modified a>span:first').text('');
this.$el.find('table').addClass('multiselect');
diff --git a/apps/files/js/filesummary.js b/apps/files/js/filesummary.js
index a4cefe692a8..519718cfc82 100644
--- a/apps/files/js/filesummary.js
+++ b/apps/files/js/filesummary.js
@@ -20,6 +20,15 @@
*/
(function() {
+ var INFO_TEMPLATE =
+ '<span class="info">' +
+ '<span class="dirinfo"></span>' +
+ '<span class="connector"> and </span>' +
+ '<span class="fileinfo"></span>' +
+ '<span class="hiddeninfo"></span>' +
+ '<span class="filter"></span>' +
+ '</span>';
+
/**
* The FileSummary class encapsulates the file summary values and
* the logic to render it in the given container
@@ -28,26 +37,51 @@
* @memberof OCA.Files
*
* @param $tr table row element
+ * @param {OC.Backbone.Model} [options.filesConfig] files app configuration
*/
- var FileSummary = function($tr) {
+ var FileSummary = function($tr, options) {
+ options = options || {};
+ var self = this;
this.$el = $tr;
+ var filesConfig = options.config;
+ if (filesConfig) {
+ this._showHidden = !!filesConfig.get('showhidden');
+ filesConfig.on('change:showhidden', function() {
+ self._showHidden = !!this.get('showhidden');
+ self.update();
+ });
+ }
this.clear();
this.render();
};
FileSummary.prototype = {
+ _showHidden: null,
+
summary: {
totalFiles: 0,
totalDirs: 0,
+ totalHidden: 0,
totalSize: 0,
filter:'',
sumIsPending:false
},
/**
+ * Returns whether the given file info must be hidden
+ *
+ * @param {OC.Files.FileInfo} fileInfo file info
+ *
+ * @return {boolean} true if the file is a hidden file, false otherwise
+ */
+ _isHiddenFile: function(file) {
+ return file.name && file.name.charAt(0) === '.';
+ },
+
+ /**
* Adds file
- * @param file file to add
- * @param update whether to update the display
+ * @param {OC.Files.FileInfo} file file to add
+ * @param {boolean} update whether to update the display
*/
add: function(file, update) {
if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
@@ -59,6 +93,10 @@
else {
this.summary.totalFiles++;
}
+ if (this._isHiddenFile(file)) {
+ this.summary.totalHidden++;
+ }
+
var size = parseInt(file.size, 10) || 0;
if (size >=0) {
this.summary.totalSize += size;
@@ -71,8 +109,8 @@
},
/**
* Removes file
- * @param file file to remove
- * @param update whether to update the display
+ * @param {OC.Files.FileInfo} file file to remove
+ * @param {boolean} update whether to update the display
*/
remove: function(file, update) {
if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
@@ -84,6 +122,9 @@
else {
this.summary.totalFiles--;
}
+ if (this._isHiddenFile(file)) {
+ this.summary.totalHidden--;
+ }
var size = parseInt(file.size, 10) || 0;
if (size >=0) {
this.summary.totalSize -= size;
@@ -111,6 +152,7 @@
var summary = {
totalDirs: 0,
totalFiles: 0,
+ totalHidden: 0,
totalSize: 0,
filter: this.summary.filter,
sumIsPending: false
@@ -127,6 +169,9 @@
else {
summary.totalFiles++;
}
+ if (this._isHiddenFile(file)) {
+ summary.totalHidden++;
+ }
var size = parseInt(file.size, 10) || 0;
if (size >=0) {
summary.totalSize += size;
@@ -154,6 +199,13 @@
this.update();
},
+ _infoTemplate: function(data) {
+ if (!this._infoTemplateCompiled) {
+ this._infoTemplateCompiled = Handlebars.compile(INFO_TEMPLATE);
+ }
+ return this._infoTemplateCompiled(data);
+ },
+
/**
* Renders the file summary element
*/
@@ -171,10 +223,12 @@
var $fileInfo = this.$el.find('.fileinfo');
var $connector = this.$el.find('.connector');
var $filterInfo = this.$el.find('.filter');
+ var $hiddenInfo = this.$el.find('.hiddeninfo');
// Substitute old content with new translations
$dirInfo.html(n('files', '%n folder', '%n folders', this.summary.totalDirs));
$fileInfo.html(n('files', '%n file', '%n files', this.summary.totalFiles));
+ $hiddenInfo.html(' (' + n('files', 'including %n hidden', 'including %n hidden', this.summary.totalHidden) + ')');
var fileSize = this.summary.sumIsPending ? t('files', 'Pending') : OC.Util.humanFileSize(this.summary.totalSize);
this.$el.find('.filesize').html(fileSize);
@@ -194,6 +248,7 @@
if (this.summary.totalDirs > 0 && this.summary.totalFiles > 0) {
$connector.removeClass('hidden');
}
+ $hiddenInfo.toggleClass('hidden', this.summary.totalHidden === 0 || this._showHidden)
if (this.summary.filter === '') {
$filterInfo.html('');
$filterInfo.addClass('hidden');
@@ -206,19 +261,7 @@
if (!this.$el) {
return;
}
- // TODO: ideally this should be separate to a template or something
var summary = this.summary;
- var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs);
- var fileInfo = n('files', '%n file', '%n files', summary.totalFiles);
- var filterInfo = '';
- if (this.summary.filter !== '') {
- filterInfo = ' ' + n('files', 'matches \'{filter}\'', 'match \'{filter}\'', summary.totalFiles + summary.totalDirs, {filter: summary.filter});
- }
-
- var infoVars = {
- dirs: '<span class="dirinfo">'+directoryInfo+'</span><span class="connector">',
- files: '</span><span class="fileinfo">'+fileInfo+'</span>'
- };
// don't show the filesize column, if filesize is NaN (e.g. in trashbin)
var fileSize = '';
@@ -227,15 +270,14 @@
fileSize = '<td class="filesize">' + fileSize + '</td>';
}
- var info = t('files', '{dirs} and {files}', infoVars, null, {'escape': false});
-
- var $summary = $('<td><span class="info">'+info+'<span class="filter">'+filterInfo+'</span></span></td>'+fileSize+'<td class="date"></td>');
-
- if (!this.summary.totalFiles && !this.summary.totalDirs) {
- this.$el.addClass('hidden');
- }
-
+ var $summary = $(
+ '<td>' + this._infoTemplate() + '</td>' +
+ fileSize +
+ '<td class="date"></td>'
+ );
+ this.$el.addClass('hidden');
this.$el.append($summary);
+ this.update();
}
};
OCA.Files.FileSummary = FileSummary;
diff --git a/apps/files/lib/Command/Scan.php b/apps/files/lib/Command/Scan.php
index 25933ae25aa..0234fb435a7 100644
--- a/apps/files/lib/Command/Scan.php
+++ b/apps/files/lib/Command/Scan.php
@@ -28,9 +28,11 @@
namespace OCA\Files\Command;
+use Doctrine\DBAL\Connection;
use OC\Core\Command\Base;
use OC\ForbiddenException;
use OCP\Files\StorageNotAvailableException;
+use OCP\IDBConnection;
use OCP\IUserManager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -106,7 +108,8 @@ class Scan extends Base {
}
protected function scanFiles($user, $path, $verbose, OutputInterface $output, $backgroundScan = false) {
- $scanner = new \OC\Files\Utils\Scanner($user, \OC::$server->getDatabaseConnection(), \OC::$server->getLogger());
+ $connection = $this->reconnectToDatabase($output);
+ $scanner = new \OC\Files\Utils\Scanner($user, $connection, \OC::$server->getLogger());
# check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
# printout and count
if ($verbose) {
@@ -318,4 +321,26 @@ class Scan extends Base {
return date('H:i:s', $secs);
}
+ /**
+ * @return \OCP\IDBConnection
+ */
+ protected function reconnectToDatabase(OutputInterface $output) {
+ /** @var Connection | IDBConnection $connection*/
+ $connection = \OC::$server->getDatabaseConnection();
+ try {
+ $connection->close();
+ } catch (\Exception $ex) {
+ $output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>");
+ }
+ while (!$connection->isConnected()) {
+ try {
+ $connection->connect();
+ } catch (\Exception $ex) {
+ $output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>");
+ sleep(60);
+ }
+ }
+ return $connection;
+ }
+
}
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index cf9f43f2d59..0a4812f3a81 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -385,8 +385,9 @@ describe('OCA.Files.FileList tests', function() {
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(false);
// yes, ugly...
- expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+ expect($summary.find('.connector').hasClass('hidden')).toEqual(true);
expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.filesize').text()).toEqual('12 B');
expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
@@ -456,7 +457,8 @@ describe('OCA.Files.FileList tests', function() {
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual('1 folder and 2 files');
+ expect($summary.find('.dirinfo').text()).toEqual('1 folder');
+ expect($summary.find('.fileinfo').text()).toEqual('2 files');
expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.filesize').text()).toEqual('69 KB');
@@ -511,7 +513,8 @@ describe('OCA.Files.FileList tests', function() {
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual('1 folder and 1 file');
+ expect($summary.find('.dirinfo').text()).toEqual('1 folder');
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.filesize').text()).toEqual('57 KB');
@@ -677,12 +680,14 @@ describe('OCA.Files.FileList tests', function() {
deferredRename.resolve(201);
- expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
+ expect($summary.find('.dirinfo').text()).toEqual('1 folder');
+ expect($summary.find('.fileinfo').text()).toEqual('3 files');
});
it('Leaves the summary alone when cancel renaming', function() {
var $summary = $('#filestable .summary');
doCancelRename();
- expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
+ expect($summary.find('.dirinfo').text()).toEqual('1 folder');
+ expect($summary.find('.fileinfo').text()).toEqual('3 files');
});
it('Shows busy state while rename in progress', function() {
var $tr;
@@ -856,11 +861,14 @@ describe('OCA.Files.FileList tests', function() {
});
var $tr = fileList.add(fileData);
- expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
+ expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
var model = fileList.getModelForFile('test file');
model.set({size: '100'});
- expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
+
+ expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
});
})
describe('List rendering', function() {
@@ -877,7 +885,8 @@ describe('OCA.Files.FileList tests', function() {
fileList.setFiles(testFiles);
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
+ expect($summary.find('.dirinfo').text()).toEqual('1 folder');
+ expect($summary.find('.fileinfo').text()).toEqual('3 files');
expect($summary.find('.filesize').text()).toEqual('69 KB');
});
it('shows headers, summary and hide empty content message after setting files', function(){
@@ -962,10 +971,12 @@ describe('OCA.Files.FileList tests', function() {
fileList.setFiles([testFiles[0]]);
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
+ expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
fileList.remove('unexist.txt');
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual('0 folders and 1 file');
+ expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
});
});
describe('Filtered list rendering', function() {
@@ -987,14 +998,18 @@ describe('OCA.Files.FileList tests', function() {
expect($('#fileList tr:not(.hidden)').length).toEqual(3);
expect(fileList.files.length).toEqual(4);
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual("1 folder and 2 files match 'e'");
+ expect($summary.find('.dirinfo').text()).toEqual('1 folder');
+ expect($summary.find('.fileinfo').text()).toEqual('2 files');
+ expect($summary.find('.filter').text()).toEqual(" match 'e'");
expect($nofilterresults.hasClass('hidden')).toEqual(true);
fileList.setFilter('ee');
expect($('#fileList tr:not(.hidden)').length).toEqual(1);
expect(fileList.files.length).toEqual(4);
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual("0 folders and 1 file matches 'ee'");
+ expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
+ expect($summary.find('.filter').text()).toEqual(" matches 'ee'");
expect($nofilterresults.hasClass('hidden')).toEqual(true);
fileList.setFilter('eee');
@@ -1007,21 +1022,26 @@ describe('OCA.Files.FileList tests', function() {
expect($('#fileList tr:not(.hidden)').length).toEqual(1);
expect(fileList.files.length).toEqual(4);
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual("0 folders and 1 file matches 'ee'");
+ expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
+ expect($summary.find('.filter').text()).toEqual(" matches 'ee'");
expect($nofilterresults.hasClass('hidden')).toEqual(true);
fileList.setFilter('e');
expect($('#fileList tr:not(.hidden)').length).toEqual(3);
expect(fileList.files.length).toEqual(4);
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual("1 folder and 2 files match 'e'");
+ expect($summary.find('.dirinfo').text()).toEqual('1 folder');
+ expect($summary.find('.fileinfo').text()).toEqual('2 files');
+ expect($summary.find('.filter').text()).toEqual(" match 'e'");
expect($nofilterresults.hasClass('hidden')).toEqual(true);
fileList.setFilter('');
expect($('#fileList tr:not(.hidden)').length).toEqual(4);
expect(fileList.files.length).toEqual(4);
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual("1 folder and 3 files");
+ expect($summary.find('.dirinfo').text()).toEqual('1 folder');
+ expect($summary.find('.fileinfo').text()).toEqual('3 files');
expect($nofilterresults.hasClass('hidden')).toEqual(true);
});
it('filters the list of non-rendered rows using filter()', function() {
@@ -1032,7 +1052,9 @@ describe('OCA.Files.FileList tests', function() {
fileList.setFilter('63');
expect($('#fileList tr:not(.hidden)').length).toEqual(1);
expect($summary.hasClass('hidden')).toEqual(false);
- expect($summary.find('.info').text()).toEqual("0 folders and 1 file matches '63'");
+ expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true);
+ expect($summary.find('.fileinfo').text()).toEqual('1 file');
+ expect($summary.find('.filter').text()).toEqual(" matches '63'");
expect($nofilterresults.hasClass('hidden')).toEqual(true);
});
it('hides the emptyfiles notice when using filter()', function() {
@@ -1654,6 +1676,18 @@ describe('OCA.Files.FileList tests', function() {
$('#fileList tr td.filename input:checkbox').click();
expect($('.select-all').prop('checked')).toEqual(false);
});
+ it('Selecting all files also selects hidden files when invisible', function() {
+ filesConfig.set('showhidden', false);
+ var $tr = fileList.add(new FileInfo({
+ name: '.hidden',
+ type: 'dir',
+ mimetype: 'httpd/unix-directory',
+ size: 150
+ }));
+ $('.select-all').click();
+ expect($tr.find('td.filename input:checkbox').prop('checked')).toEqual(true);
+ expect(_.pluck(fileList.getSelectedFiles(), 'name')).toContain('.hidden');
+ });
it('Clicking "select all" will select/deselect all files', function() {
fileList.setFiles(generateFiles(0, 41));
$('.select-all').click();
@@ -1731,6 +1765,44 @@ describe('OCA.Files.FileList tests', function() {
fileList.findFileEl('One.txt').find('input:checkbox').click().click();
expect($summary.text()).toEqual('Name');
});
+ it('Displays the number of hidden files in selection summary if hidden files are invisible', function() {
+ filesConfig.set('showhidden', false);
+ var $tr = fileList.add(new FileInfo({
+ name: '.hidden',
+ type: 'dir',
+ mimetype: 'httpd/unix-directory',
+ size: 150
+ }));
+ $('.select-all').click();
+ var $summary = $('#headerName a.name>span:first');
+ expect($summary.text()).toEqual('2 folders and 3 files (including 1 hidden)');
+ });
+ it('Does not displays the number of hidden files in selection summary if hidden files are visible', function() {
+ filesConfig.set('showhidden', true);
+ var $tr = fileList.add(new FileInfo({
+ name: '.hidden',
+ type: 'dir',
+ mimetype: 'httpd/unix-directory',
+ size: 150
+ }));
+ $('.select-all').click();
+ var $summary = $('#headerName a.name>span:first');
+ expect($summary.text()).toEqual('2 folders and 3 files');
+ });
+ it('Toggling hidden file visibility updates selection summary', function() {
+ filesConfig.set('showhidden', false);
+ var $tr = fileList.add(new FileInfo({
+ name: '.hidden',
+ type: 'dir',
+ mimetype: 'httpd/unix-directory',
+ size: 150
+ }));
+ $('.select-all').click();
+ var $summary = $('#headerName a.name>span:first');
+ expect($summary.text()).toEqual('2 folders and 3 files (including 1 hidden)');
+ filesConfig.set('showhidden', true);
+ expect($summary.text()).toEqual('2 folders and 3 files');
+ });
it('Select/deselect files shows/hides file actions', function() {
var $actions = $('#headerName .selectedActions');
var $checkbox = fileList.findFileEl('One.txt').find('input:checkbox');
diff --git a/apps/files/tests/js/filesummarySpec.js b/apps/files/tests/js/filesummarySpec.js
index ec94c28acb6..e3f24d9ad43 100644
--- a/apps/files/tests/js/filesummarySpec.js
+++ b/apps/files/tests/js/filesummarySpec.js
@@ -39,7 +39,8 @@ describe('OCA.Files.FileSummary tests', function() {
totalSize: 256000
});
expect($container.hasClass('hidden')).toEqual(false);
- expect($container.find('.info').text()).toEqual('5 folders and 2 files');
+ expect($container.find('.dirinfo').text()).toEqual('5 folders');
+ expect($container.find('.fileinfo').text()).toEqual('2 files');
expect($container.find('.filesize').text()).toEqual('250 KB');
});
it('hides summary when no files or folders', function() {
@@ -62,7 +63,8 @@ describe('OCA.Files.FileSummary tests', function() {
s.add({type: 'dir', size: 100});
s.update();
expect($container.hasClass('hidden')).toEqual(false);
- expect($container.find('.info').text()).toEqual('6 folders and 3 files');
+ expect($container.find('.dirinfo').text()).toEqual('6 folders');
+ expect($container.find('.fileinfo').text()).toEqual('3 files');
expect($container.find('.filesize').text()).toEqual('500 KB');
expect(s.summary.totalDirs).toEqual(6);
expect(s.summary.totalFiles).toEqual(3);
@@ -79,7 +81,8 @@ describe('OCA.Files.FileSummary tests', function() {
s.remove({type: 'dir', size: 100});
s.update();
expect($container.hasClass('hidden')).toEqual(false);
- expect($container.find('.info').text()).toEqual('4 folders and 1 file');
+ expect($container.find('.dirinfo').text()).toEqual('4 folders');
+ expect($container.find('.fileinfo').text()).toEqual('1 file');
expect($container.find('.filesize').text()).toEqual('125 KB');
expect(s.summary.totalDirs).toEqual(4);
expect(s.summary.totalFiles).toEqual(1);
@@ -95,7 +98,9 @@ describe('OCA.Files.FileSummary tests', function() {
filter: 'foo'
});
expect($container.hasClass('hidden')).toEqual(false);
- expect($container.find('.info').text()).toEqual('5 folders and 2 files match \'foo\'');
+ expect($container.find('.dirinfo').text()).toEqual('5 folders');
+ expect($container.find('.fileinfo').text()).toEqual('2 files');
+ expect($container.find('.filter').text()).toEqual(' match \'foo\'');
expect($container.find('.filesize').text()).toEqual('250 KB');
});
it('hides filtered summary when no files or folders', function() {
@@ -122,7 +127,9 @@ describe('OCA.Files.FileSummary tests', function() {
s.add({name: 'foo', type: 'dir', size: 102});
s.update();
expect($container.hasClass('hidden')).toEqual(false);
- expect($container.find('.info').text()).toEqual('6 folders and 3 files match \'foo\'');
+ expect($container.find('.dirinfo').text()).toEqual('6 folders');
+ expect($container.find('.fileinfo').text()).toEqual('3 files');
+ expect($container.find('.filter').text()).toEqual(' match \'foo\'');
expect($container.find('.filesize').text()).toEqual('500 KB');
expect(s.summary.totalDirs).toEqual(6);
expect(s.summary.totalFiles).toEqual(3);
@@ -142,7 +149,9 @@ describe('OCA.Files.FileSummary tests', function() {
s.remove({name: 'foo', type: 'dir', size: 98});
s.update();
expect($container.hasClass('hidden')).toEqual(false);
- expect($container.find('.info').text()).toEqual('4 folders and 1 file match \'foo\'');
+ expect($container.find('.dirinfo').text()).toEqual('4 folders');
+ expect($container.find('.fileinfo').text()).toEqual('1 file');
+ expect($container.find('.filter').text()).toEqual(' match \'foo\'');
expect($container.find('.filesize').text()).toEqual('125 KB');
expect(s.summary.totalDirs).toEqual(4);
expect(s.summary.totalFiles).toEqual(1);
@@ -158,7 +167,8 @@ describe('OCA.Files.FileSummary tests', function() {
s.add({type: 'dir', size: -1});
s.update();
expect($container.hasClass('hidden')).toEqual(false);
- expect($container.find('.info').text()).toEqual('1 folder and 0 files');
+ expect($container.find('.dirinfo').text()).toEqual('1 folder');
+ expect($container.find('.fileinfo').hasClass('hidden')).toEqual(true);
expect($container.find('.filesize').text()).toEqual('Pending');
expect(s.summary.totalDirs).toEqual(1);
expect(s.summary.totalFiles).toEqual(0);
@@ -175,10 +185,56 @@ describe('OCA.Files.FileSummary tests', function() {
s.remove({type: 'dir', size: -1});
s.update();
expect($container.hasClass('hidden')).toEqual(true);
- expect($container.find('.info').text()).toEqual('0 folders and 0 files');
- expect($container.find('.filesize').text()).toEqual('0 B');
expect(s.summary.totalDirs).toEqual(0);
expect(s.summary.totalFiles).toEqual(0);
expect(s.summary.totalSize).toEqual(0);
});
+ describe('hidden files', function() {
+ var config;
+ var summary;
+
+ beforeEach(function() {
+ config = new OC.Backbone.Model();
+ summary = new FileSummary($container, {
+ config: config
+ });
+ });
+
+ it('renders hidden count section when hidden files are hidden', function() {
+ config.set('showhidden', false);
+ summary.add({name: 'abc', type: 'file', size: 256000});
+ summary.add({name: 'def', type: 'dir', size: 100});
+ summary.add({name: '.hidden', type: 'dir', size: 512000});
+ summary.update();
+ expect($container.hasClass('hidden')).toEqual(false);
+ expect($container.find('.dirinfo').text()).toEqual('2 folders');
+ expect($container.find('.fileinfo').text()).toEqual('1 file');
+ expect($container.find('.hiddeninfo').hasClass('hidden')).toEqual(false);
+ expect($container.find('.hiddeninfo').text()).toEqual(' (including 1 hidden)');
+ expect($container.find('.filesize').text()).toEqual('750 KB');
+ });
+ it('does not render hidden count section when hidden files exist but are visible', function() {
+ config.set('showhidden', true);
+ summary.add({name: 'abc', type: 'file', size: 256000});
+ summary.add({name: 'def', type: 'dir', size: 100});
+ summary.add({name: '.hidden', type: 'dir', size: 512000});
+ summary.update();
+ expect($container.hasClass('hidden')).toEqual(false);
+ expect($container.find('.dirinfo').text()).toEqual('2 folders');
+ expect($container.find('.fileinfo').text()).toEqual('1 file');
+ expect($container.find('.hiddeninfo').hasClass('hidden')).toEqual(true);
+ expect($container.find('.filesize').text()).toEqual('750 KB');
+ });
+ it('does not render hidden count section when no hidden files exist', function() {
+ config.set('showhidden', false);
+ summary.add({name: 'abc', type: 'file', size: 256000});
+ summary.add({name: 'def', type: 'dir', size: 100});
+ summary.update();
+ expect($container.hasClass('hidden')).toEqual(false);
+ expect($container.find('.dirinfo').text()).toEqual('1 folder');
+ expect($container.find('.fileinfo').text()).toEqual('1 file');
+ expect($container.find('.hiddeninfo').hasClass('hidden')).toEqual(true);
+ expect($container.find('.filesize').text()).toEqual('250 KB');
+ });
+ });
});
diff --git a/apps/files_sharing/lib/Controller/ShareesAPIController.php b/apps/files_sharing/lib/Controller/ShareesAPIController.php
index b884aa9f1d4..a2063803450 100644
--- a/apps/files_sharing/lib/Controller/ShareesAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareesAPIController.php
@@ -321,7 +321,7 @@ class ShareesAPIController extends OCSController {
$this->result['remotes'] = [];
}
- if (!$foundRemoteById && substr_count($search, '@') >= 1 && substr_count($search, ' ') === 0 && $this->offset === 0) {
+ if (!$foundRemoteById && substr_count($search, '@') >= 1 && $this->offset === 0) {
$this->result['exact']['remotes'][] = [
'label' => $search,
'value' => [
diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php
index 3ceca430424..e1875fe2394 100644
--- a/apps/files_sharing/lib/sharedstorage.php
+++ b/apps/files_sharing/lib/sharedstorage.php
@@ -106,6 +106,16 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
}
/**
+ * @inheritdoc
+ */
+ public function instanceOfStorage($class) {
+ if (in_array($class, ['\OC\Files\Storage\Home', '\OC\Files\ObjectStore\HomeObjectStoreStorage'])) {
+ return false;
+ }
+ return parent::instanceOfStorage($class);
+ }
+
+ /**
* @return string
*/
public function getShareId() {
diff --git a/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
index 5cb073ecf08..161cc8a184b 100644
--- a/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
+++ b/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
@@ -945,6 +945,58 @@ class ShareesAPIControllerTest extends TestCase {
[],
true,
],
+ // contact with space
+ [
+ 'user name@localhost',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'CLOUD' => [
+ ],
+ ],
+ [
+ 'FN' => 'User Name @ Localhost',
+ 'CLOUD' => [
+ 'user name@localhost',
+ ],
+ ],
+ ],
+ false,
+ [
+ ['label' => 'User Name @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'user name@localhost', 'server' => 'localhost']],
+ ],
+ [],
+ true,
+ ],
+ // remote with space, no contact
+ [
+ 'user space@remote',
+ [
+ [
+ 'FN' => 'User3 @ Localhost',
+ ],
+ [
+ 'FN' => 'User2 @ Localhost',
+ 'CLOUD' => [
+ ],
+ ],
+ [
+ 'FN' => 'User @ Localhost',
+ 'CLOUD' => [
+ 'username@localhost',
+ ],
+ ],
+ ],
+ false,
+ [
+ ['label' => 'user space@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'user space@remote']],
+ ],
+ [],
+ true,
+ ],
];
}
diff --git a/apps/files_trashbin/appinfo/register_command.php b/apps/files_trashbin/appinfo/register_command.php
index 676e3f5891c..e0dafc60cd9 100644
--- a/apps/files_trashbin/appinfo/register_command.php
+++ b/apps/files_trashbin/appinfo/register_command.php
@@ -21,10 +21,16 @@
*/
+use OCA\Files_Trashbin\AppInfo\Application;
use OCA\Files_Trashbin\Command\CleanUp;
+use OCA\Files_Trashbin\Command\ExpireTrash;
+$app = new Application();
+$expiration = $app->getContainer()->query('Expiration');
$userManager = OC::$server->getUserManager();
$rootFolder = \OC::$server->getRootFolder();
$dbConnection = \OC::$server->getDatabaseConnection();
+
/** @var Symfony\Component\Console\Application $application */
$application->add(new CleanUp($rootFolder, $userManager, $dbConnection));
+$application->add(new ExpireTrash($userManager, $expiration));
diff --git a/apps/files_trashbin/lib/Command/ExpireTrash.php b/apps/files_trashbin/lib/Command/ExpireTrash.php
new file mode 100644
index 00000000000..ff827718885
--- /dev/null
+++ b/apps/files_trashbin/lib/Command/ExpireTrash.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud GmbH.
+ * @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_Trashbin\Command;
+
+use OCP\IUser;
+use OCP\IUserManager;
+use OCA\Files_Trashbin\Expiration;
+use OCA\Files_Trashbin\Helper;
+use OCA\Files_Trashbin\Trashbin;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ExpireTrash extends Command {
+
+ /**
+ * @var Expiration
+ */
+ private $expiration;
+
+ /**
+ * @var IUserManager
+ */
+ private $userManager;
+
+ /**
+ * @param IUserManager|null $userManager
+ * @param Expiration|null $expiration
+ */
+ public function __construct(IUserManager $userManager = null,
+ Expiration $expiration = null) {
+ parent::__construct();
+
+ $this->userManager = $userManager;
+ $this->expiration = $expiration;
+ }
+
+ protected function configure() {
+ $this
+ ->setName('trashbin:expire')
+ ->setDescription('Expires the users trashbin')
+ ->addArgument(
+ 'user_id',
+ InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
+ 'expires the trashbin of the given user(s), if no user is given the trash for all users will be expired'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+
+ $maxAge = $this->expiration->getMaxAgeAsTimestamp();
+ if (!$maxAge) {
+ $output->writeln("No expiry configured.");
+ return;
+ }
+
+ $users = $input->getArgument('user_id');
+ if (!empty($users)) {
+ foreach ($users as $user) {
+ if ($this->userManager->userExists($user)) {
+ $output->writeln("Remove deleted files of <info>$user</info>");
+ $userObject = $this->userManager->get($user);
+ $this->expireTrashForUser($userObject);
+ } else {
+ $output->writeln("<error>Unknown user $user</error>");
+ }
+ }
+ } else {
+ $p = new ProgressBar($output);
+ $p->start();
+ $this->userManager->callForAllUsers(function(IUser $user) use ($p) {
+ $p->advance();
+ $this->expireTrashForUser($user);
+ });
+ $p->finish();
+ $output->writeln('');
+ }
+ }
+
+ function expireTrashForUser(IUser $user) {
+ $uid = $user->getUID();
+ if ($user->getLastLogin() === 0 || !$this->setupFS($uid)) {
+ return;
+ }
+ $dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
+ Trashbin::deleteExpiredFiles($dirContent, $uid);
+ }
+
+ /**
+ * Act on behalf on trash item owner
+ * @param string $user
+ * @return boolean
+ */
+ protected function setupFS($user) {
+ \OC_Util::tearDownFS();
+ \OC_Util::setupFS($user);
+
+ // Check if this user has a trashbin directory
+ $view = new \OC\Files\View('/' . $user);
+ if (!$view->is_dir('/files_trashbin/files')) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/apps/files_versions/appinfo/register_command.php b/apps/files_versions/appinfo/register_command.php
index b991c72b944..bca869075aa 100644
--- a/apps/files_versions/appinfo/register_command.php
+++ b/apps/files_versions/appinfo/register_command.php
@@ -21,9 +21,14 @@
*/
+use OCA\Files_Versions\AppInfo\Application;
use OCA\Files_Versions\Command\CleanUp;
+use OCA\Files_Versions\Command\ExpireVersions;
+$app = new Application();
+$expiration = $app->getContainer()->query('Expiration');
$userManager = OC::$server->getUserManager();
$rootFolder = \OC::$server->getRootFolder();
/** @var Symfony\Component\Console\Application $application */
$application->add(new CleanUp($rootFolder, $userManager));
+$application->add(new ExpireVersions($userManager, $expiration));
diff --git a/apps/files_versions/lib/Command/ExpireVersions.php b/apps/files_versions/lib/Command/ExpireVersions.php
new file mode 100644
index 00000000000..f384420f22f
--- /dev/null
+++ b/apps/files_versions/lib/Command/ExpireVersions.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud GmbH.
+ * @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_Versions\Command;
+
+use OCA\Files_Versions\Expiration;
+use OCA\Files_Versions\Storage;
+use OCP\IUser;
+use OCP\IUserManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ExpireVersions extends Command {
+
+ /**
+ * @var Expiration
+ */
+ private $expiration;
+
+ /**
+ * @var IUserManager
+ */
+ private $userManager;
+
+ /**
+ * @param IUserManager|null $userManager
+ * @param Expiration|null $expiration
+ */
+ public function __construct(IUserManager $userManager = null,
+ Expiration $expiration = null) {
+ parent::__construct();
+
+ $this->userManager = $userManager;
+ $this->expiration = $expiration;
+ }
+
+ protected function configure() {
+ $this
+ ->setName('versions:expire')
+ ->setDescription('Expires the users file versions')
+ ->addArgument(
+ 'user_id',
+ InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
+ 'expire file versions of the given user(s), if no user is given file versions for all users will be expired.'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+
+ $maxAge = $this->expiration->getMaxAgeAsTimestamp();
+ if (!$maxAge) {
+ $output->writeln("No expiry configured.");
+ return;
+ }
+
+ $users = $input->getArgument('user_id');
+ if (!empty($users)) {
+ foreach ($users as $user) {
+ if ($this->userManager->userExists($user)) {
+ $output->writeln("Remove deleted files of <info>$user</info>");
+ $userObject = $this->userManager->get($user);
+ $this->expireVersionsForUser($userObject);
+ } else {
+ $output->writeln("<error>Unknown user $user</error>");
+ }
+ }
+ } else {
+ $p = new ProgressBar($output);
+ $p->start();
+ $this->userManager->callForAllUsers(function(IUser $user) use ($p) {
+ $p->advance();
+ $this->expireVersionsForUser($user);
+ });
+ $p->finish();
+ $output->writeln('');
+ }
+ }
+
+ function expireVersionsForUser(IUser $user) {
+ $uid = $user->getUID();
+ if ($user->getLastLogin() === 0 || !$this->setupFS($uid)) {
+ return;
+ }
+ Storage::expireOlderThanMaxForUser($uid);
+ }
+
+ /**
+ * Act on behalf on versions item owner
+ * @param string $user
+ * @return boolean
+ */
+ protected function setupFS($user) {
+ \OC_Util::tearDownFS();
+ \OC_Util::setupFS($user);
+
+ // Check if this user has a version directory
+ $view = new \OC\Files\View('/' . $user);
+ if (!$view->is_dir('/files_versions')) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/apps/theming/css/settings-admin.css b/apps/theming/css/settings-admin.css
index 4139b2f46a3..5d2b08f5e43 100644
--- a/apps/theming/css/settings-admin.css
+++ b/apps/theming/css/settings-admin.css
@@ -9,12 +9,13 @@
#theming .theme-undo {
cursor: pointer;
opacity: .5;
- padding: 9px;
- vertical-align: bottom;
+ padding: 11px 5px;
+ vertical-align: top;
+ display: inline-block;
}
-#theming .icon {
- display: inline-block;
+#theming .icon-loading-small:after {
+ margin: -10px 0 0 -10px;
}
#theming label span {
@@ -23,10 +24,11 @@
padding: 8px 0px;
}
-#theming .icon-upload {
+#theming .icon-upload,
+#theming .icon-loading-small {
display: inline-flex;
padding: 8px;
- margin: 0;
+ margin: 2px 0px;
}
div#theming_settings_msg {
@@ -46,4 +48,4 @@ div#theming_settings_msg {
max-width: 20%;
max-height: 20%;
margin-top: 20px;
-}
+} \ No newline at end of file
diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js
index c896da321c8..216463b15e0 100644
--- a/apps/theming/js/settings-admin.js
+++ b/apps/theming/js/settings-admin.js
@@ -25,6 +25,7 @@ function setThemingValue(setting, value) {
OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : setting, 'value' : value}
).done(function(response) {
OC.msg.finishedSaving('#theming_settings_msg', response);
+ hideUndoButton(setting, value);
}).fail(function(response) {
OC.msg.finishedSaving('#theming_settings_msg', response);
});
@@ -114,7 +115,6 @@ function preview(setting, value) {
if (setting === 'name') {
window.document.title = t('core', 'Admin') + " - " + value;
}
- hideUndoButton(setting, value);
}
function hideUndoButton(setting, value) {
@@ -153,12 +153,16 @@ $(document).ready(function () {
done: function (e, response) {
preview('logoMime', response.result.data.name);
OC.msg.finishedSaving('#theming_settings_msg', response.result);
+ $('label#uploadlogo').addClass('icon-upload').removeClass('icon-loading-small');
+ $('.theme-undo[data-setting=logoMime]').show();
},
submit: function(e, response) {
OC.msg.startSaving('#theming_settings_msg');
+ $('label#uploadlogo').removeClass('icon-upload').addClass('icon-loading-small');
},
fail: function (e, response){
OC.msg.finishedError('#theming_settings_msg', response._response.jqXHR.responseJSON.data.message);
+ $('label#uploadlogo').addClass('icon-upload').removeClass('icon-loading-small');
}
};
var uploadParamsLogin = {
@@ -167,11 +171,15 @@ $(document).ready(function () {
done: function (e, response) {
preview('backgroundMime', response.result.data.name);
OC.msg.finishedSaving('#theming_settings_msg', response.result);
+ $('label#upload-login-background').addClass('icon-upload').removeClass('icon-loading-small');
+ $('.theme-undo[data-setting=backgroundMime]').show();
},
submit: function(e, response) {
OC.msg.startSaving('#theming_settings_msg');
+ $('label#upload-login-background').removeClass('icon-upload').addClass('icon-loading-small');
},
fail: function (e, response){
+ $('label#upload-login-background').removeClass('icon-loading-small').addClass('icon-upload');
OC.msg.finishedError('#theming_settings_msg', response._response.jqXHR.responseJSON.data.message);
}
};
@@ -216,6 +224,7 @@ $(document).ready(function () {
$('.theme-undo').click(function (e) {
var setting = $(this).data('setting');
OC.msg.startSaving('#theming_settings_msg');
+ $('.theme-undo[data-setting=' + setting + ']').hide();
$.post(
OC.generateUrl('/apps/theming/ajax/undoChanges'), {'setting' : setting}
).done(function(response) {
diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php
index fbb4c904773..b4e3a95710f 100644
--- a/apps/theming/lib/Controller/ThemingController.php
+++ b/apps/theming/lib/Controller/ThemingController.php
@@ -304,6 +304,13 @@ class ThemingController extends Controller {
$responseCss = '';
$color = $this->config->getAppValue($this->appName, 'color');
$elementColor = $this->util->elementColor($color);
+
+ if($this->util->invertTextColor($color)) {
+ $textColor = '#000000';
+ } else {
+ $textColor = '#ffffff';
+ }
+
if($color !== '') {
$responseCss .= sprintf(
'#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: %s}' . "\n",
@@ -321,19 +328,26 @@ class ThemingController extends Controller {
'background-image: url(\'data:image/svg+xml;base64,'.$this->util->generateRadioButton($elementColor).'\');' .
"}\n";
$responseCss .= '.primary, input[type="submit"].primary, input[type="button"].primary, button.primary, .button.primary,' .
- '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active,' .
- '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
- '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
- '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
+ '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active {' .
'border: 1px solid '.$elementColor.';'.
'background-color: '.$elementColor.';'.
- 'opacity: 0.8' .
+ 'opacity: 0.8;' .
+ 'color: ' . $textColor . ';'.
"}\n" .
'.primary:hover, input[type="submit"].primary:hover, input[type="button"].primary:hover, button.primary:hover, .button.primary:hover,' .
'.primary:focus, input[type="submit"].primary:focus, input[type="button"].primary:focus, button.primary:focus, .button.primary:focus {' .
'border: 1px solid '.$elementColor.';'.
'background-color: '.$elementColor.';'.
'opacity: 1.0;' .
+ 'color: ' . $textColor . ';'.
+ "}\n" .
+ '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
+ '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
+ '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
+ 'border: 1px solid '.$elementColor.';'.
+ 'background-color: '.$elementColor.';'.
+ 'opacity: 0.4;' .
+ 'color: '.$textColor.';'.
"}\n";
$responseCss .= '.ui-widget-header { border: 1px solid ' . $color . '; background: '. $color . '; color: #ffffff;' . "}\n";
$responseCss .= '.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active {' .
diff --git a/apps/theming/lib/Settings/Admin.php b/apps/theming/lib/Settings/Admin.php
index afd74ced217..22ab5650e5b 100644
--- a/apps/theming/lib/Settings/Admin.php
+++ b/apps/theming/lib/Settings/Admin.php
@@ -61,7 +61,7 @@ class Admin implements ISettings {
$theme = $this->config->getSystemValue('theme', '');
if ($theme !== '') {
$themable = false;
- $errorMessage = $this->l->t('You already use a custom theme');
+ $errorMessage = $this->l->t('You are already using a custom theme');
}
$parameters = [
diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php
index d053d8c1a1c..193e0bdcb4b 100644
--- a/apps/theming/tests/Controller/ThemingControllerTest.php
+++ b/apps/theming/tests/Controller/ThemingControllerTest.php
@@ -437,19 +437,26 @@ class ThemingControllerTest extends TestCase {
'background-image: url(\'data:image/svg+xml;base64,'.$this->util->generateRadioButton($color).'\');' .
"}\n";
$expectedData .= '.primary, input[type="submit"].primary, input[type="button"].primary, button.primary, .button.primary,' .
- '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active,' .
- '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
- '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
- '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
- 'border: 1px solid '.$color .';'.
+ '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active {' .
+ 'border: 1px solid '.$color.';'.
'background-color: '.$color.';'.
- 'opacity: 0.8' .
+ 'opacity: 0.8;' .
+ 'color: #ffffff;'.
"}\n" .
'.primary:hover, input[type="submit"].primary:hover, input[type="button"].primary:hover, button.primary:hover, .button.primary:hover,' .
'.primary:focus, input[type="submit"].primary:focus, input[type="button"].primary:focus, button.primary:focus, .button.primary:focus {' .
'border: 1px solid '.$color.';'.
'background-color: '.$color.';'.
'opacity: 1.0;' .
+ 'color: #ffffff;'.
+ "}\n" .
+ '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
+ '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
+ '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
+ 'border: 1px solid '.$color.';'.
+ 'background-color: '.$color.';'.
+ 'opacity: 0.4;' .
+ 'color: #ffffff;'.
"}\n";
$expectedData .= '.ui-widget-header { border: 1px solid ' . $color . '; background: '. $color . '; color: #ffffff;' . "}\n";
$expectedData .= '.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active {' .
@@ -520,19 +527,26 @@ class ThemingControllerTest extends TestCase {
'background-image: url(\'data:image/svg+xml;base64,'.$this->util->generateRadioButton('#555555').'\');' .
"}\n";
$expectedData .= '.primary, input[type="submit"].primary, input[type="button"].primary, button.primary, .button.primary,' .
- '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active,' .
- '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
- '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
- '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
- 'border: 1px solid #555555;'.
- 'background-color: #555555;'.
- 'opacity: 0.8' .
+ '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active {' .
+ 'border: 1px solid '.$elementColor.';'.
+ 'background-color: '.$elementColor.';'.
+ 'opacity: 0.8;' .
+ 'color: #000000;'.
"}\n" .
'.primary:hover, input[type="submit"].primary:hover, input[type="button"].primary:hover, button.primary:hover, .button.primary:hover,' .
'.primary:focus, input[type="submit"].primary:focus, input[type="button"].primary:focus, button.primary:focus, .button.primary:focus {' .
- 'border: 1px solid #555555;'.
- 'background-color: #555555;'.
+ 'border: 1px solid '.$elementColor.';'.
+ 'background-color: '.$elementColor.';'.
'opacity: 1.0;' .
+ 'color: #000000;'.
+ "}\n" .
+ '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
+ '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
+ '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
+ 'border: 1px solid '.$elementColor.';'.
+ 'background-color: '.$elementColor.';'.
+ 'opacity: 0.4;' .
+ 'color: #000000;'.
"}\n";
$expectedData .= '.ui-widget-header { border: 1px solid ' . $color . '; background: '. $color . '; color: #ffffff;' . "}\n";
$expectedData .= '.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active {' .
@@ -689,19 +703,26 @@ class ThemingControllerTest extends TestCase {
'background-image: url(\'data:image/svg+xml;base64,'.$this->util->generateRadioButton($color).'\');' .
"}\n";
$expectedData .= '.primary, input[type="submit"].primary, input[type="button"].primary, button.primary, .button.primary,' .
- '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active,' .
- '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
- '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
- '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
- 'border: 1px solid '.$color .';'.
+ '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active {' .
+ 'border: 1px solid '.$color.';'.
'background-color: '.$color.';'.
- 'opacity: 0.8' .
+ 'opacity: 0.8;' .
+ 'color: #ffffff;'.
"}\n" .
'.primary:hover, input[type="submit"].primary:hover, input[type="button"].primary:hover, button.primary:hover, .button.primary:hover,' .
'.primary:focus, input[type="submit"].primary:focus, input[type="button"].primary:focus, button.primary:focus, .button.primary:focus {' .
'border: 1px solid '.$color.';'.
'background-color: '.$color.';'.
'opacity: 1.0;' .
+ 'color: #ffffff;'.
+ "}\n" .
+ '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
+ '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
+ '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
+ 'border: 1px solid '.$color.';'.
+ 'background-color: '.$color.';'.
+ 'opacity: 0.4;' .
+ 'color: #ffffff;'.
"}\n";
$expectedData .= '.ui-widget-header { border: 1px solid ' . $color . '; background: '. $color . '; color: #ffffff;' . "}\n";
$expectedData .= '.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active {' .
@@ -789,19 +810,26 @@ class ThemingControllerTest extends TestCase {
'background-image: url(\'data:image/svg+xml;base64,'.$this->util->generateRadioButton('#555555').'\');' .
"}\n";
$expectedData .= '.primary, input[type="submit"].primary, input[type="button"].primary, button.primary, .button.primary,' .
- '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active,' .
- '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
- '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
- '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
- 'border: 1px solid #555555;'.
- 'background-color: #555555;'.
- 'opacity: 0.8' .
+ '.primary:active, input[type="submit"].primary:active, input[type="button"].primary:active, button.primary:active, .button.primary:active {' .
+ 'border: 1px solid '.$elementColor.';'.
+ 'background-color: '.$elementColor.';'.
+ 'opacity: 0.8;' .
+ 'color: #000000;'.
"}\n" .
'.primary:hover, input[type="submit"].primary:hover, input[type="button"].primary:hover, button.primary:hover, .button.primary:hover,' .
'.primary:focus, input[type="submit"].primary:focus, input[type="button"].primary:focus, button.primary:focus, .button.primary:focus {' .
- 'border: 1px solid #555555;'.
- 'background-color: #555555;'.
+ 'border: 1px solid '.$elementColor.';'.
+ 'background-color: '.$elementColor.';'.
'opacity: 1.0;' .
+ 'color: #000000;'.
+ "}\n" .
+ '.primary:disabled, input[type="submit"].primary:disabled, input[type="button"].primary:disabled, button.primary:disabled, .button.primary:disabled,' .
+ '.primary:disabled:hover, input[type="submit"].primary:disabled:hover, input[type="button"].primary:disabled:hover, button.primary:disabled:hover, .button.primary:disabled:hover,' .
+ '.primary:disabled:focus, input[type="submit"].primary:disabled:focus, input[type="button"].primary:disabled:focus, button.primary:disabled:focus, .button.primary:disabled:focus {' .
+ 'border: 1px solid '.$elementColor.';'.
+ 'background-color: '.$elementColor.';'.
+ 'opacity: 0.4;' .
+ 'color: #000000;'.
"}\n";
$expectedData .= '.ui-widget-header { border: 1px solid ' . $color . '; background: '. $color . '; color: #ffffff;' . "}\n";
$expectedData .= '.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active {' .
diff --git a/apps/theming/tests/Settings/AdminTest.php b/apps/theming/tests/Settings/AdminTest.php
index 73339cf86b7..d4f5490d352 100644
--- a/apps/theming/tests/Settings/AdminTest.php
+++ b/apps/theming/tests/Settings/AdminTest.php
@@ -112,8 +112,8 @@ class AdminTest extends TestCase {
$this->l10n
->expects($this->once())
->method('t')
- ->with('You already use a custom theme')
- ->willReturn('You already use a custom theme');
+ ->with('You are already using a custom theme')
+ ->willReturn('You are already using a custom theme');
$this->themingDefaults
->expects($this->once())
->method('getEntity')
@@ -137,7 +137,7 @@ class AdminTest extends TestCase {
->willReturn('/my/route');
$params = [
'themable' => false,
- 'errorMessage' => 'You already use a custom theme',
+ 'errorMessage' => 'You are already using a custom theme',
'name' => 'MyEntity',
'url' => 'https://example.com',
'slogan' => 'MySlogan',
diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php
index e9e20c047aa..e6da74601ba 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -344,4 +344,28 @@ trait BasicStructure {
rmdir("../../core/skeleton/PARENT");
}
}
+
+ /**
+ * @BeforeScenario @local_storage
+ */
+ public static function removeFilesFromLocalStorageBefore(){
+ $dir = "./work/local_storage/";
+ $di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
+ $ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ( $ri as $file ) {
+ $file->isDir() ? rmdir($file) : unlink($file);
+ }
+ }
+
+ /**
+ * @AfterScenario @local_storage
+ */
+ public static function removeFilesFromLocalStorageAfter(){
+ $dir = "./work/local_storage/";
+ $di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
+ $ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ( $ri as $file ) {
+ $file->isDir() ? rmdir($file) : unlink($file);
+ }
+ }
}
diff --git a/build/integration/features/external-storage.feature b/build/integration/features/external-storage.feature
new file mode 100644
index 00000000000..9e53b01346e
--- /dev/null
+++ b/build/integration/features/external-storage.feature
@@ -0,0 +1,26 @@
+Feature: external-storage
+ Background:
+ Given using api version "1"
+ Given using dav path "remote.php/webdav"
+
+ @local_storage
+ Scenario: Share by link a file inside a local external storage
+ Given user "user0" exists
+ And user "user1" exists
+ And As an "user0"
+ And user "user0" created a folder "/local_storage/foo"
+ And User "user0" moved file "/textfile0.txt" to "/local_storage/foo/textfile0.txt"
+ And folder "/local_storage/foo" of user "user0" is shared with user "user1"
+ And As an "user1"
+ When creating a share with
+ | path | foo |
+ | shareType | 3 |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And Share fields of last share match with
+ | id | A_NUMBER |
+ | url | AN_URL |
+ | token | A_TOKEN |
+ | mimetype | httpd/unix-directory |
+
+
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index 38ed5213b19..785b795bf35 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -295,6 +295,7 @@ Feature: provisioning
| theming |
| updatenotification |
| workflowengine |
+ | files_external |
Scenario: get app info
Given As an "admin"
@@ -304,19 +305,19 @@ Feature: provisioning
Scenario: enable an app
Given As an "admin"
- And app "files_external" is disabled
- When sending "POST" to "/cloud/apps/files_external"
+ And app "testing" is disabled
+ When sending "POST" to "/cloud/apps/testing"
Then the OCS status code should be "100"
And the HTTP status code should be "200"
- And app "files_external" is enabled
+ And app "testing" is enabled
Scenario: disable an app
Given As an "admin"
- And app "files_external" is enabled
- When sending "DELETE" to "/cloud/apps/files_external"
+ And app "testing" is enabled
+ When sending "DELETE" to "/cloud/apps/testing"
Then the OCS status code should be "100"
And the HTTP status code should be "200"
- And app "files_external" is disabled
+ And app "testing" is disabled
Scenario: disable an user
Given As an "admin"
diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature
index c49db4f8a5d..a59d65a2674 100644
--- a/build/integration/features/webdav-related.feature
+++ b/build/integration/features/webdav-related.feature
@@ -59,6 +59,24 @@ Feature: webdav-related
|{DAV:}quota-available-bytes|
And the single response should contain a property "{DAV:}quota-available-bytes" with value "10485421"
+ Scenario: Uploading a file as recipient using webdav having quota
+ Given using dav path "remote.php/webdav"
+ And As an "admin"
+ And user "user0" exists
+ And user "user1" exists
+ And user "user0" has a quota of "10 MB"
+ And user "user1" has a quota of "10 MB"
+ And As an "user1"
+ And user "user1" created a folder "/testquota"
+ And as "user1" creating a share with
+ | path | testquota |
+ | shareType | 0 |
+ | permissions | 31 |
+ | shareWith | user0 |
+ And As an "user0"
+ When User "user0" uploads file "data/textfile.txt" to "/testquota/asdf.txt"
+ Then the HTTP status code should be "201"
+
Scenario: download a public shared file with range
Given user "user0" exists
And As an "user0"
diff --git a/build/integration/run.sh b/build/integration/run.sh
index eccb378eec4..cf42ed75e4c 100755
--- a/build/integration/run.sh
+++ b/build/integration/run.sh
@@ -36,12 +36,27 @@ echo $PHPPID_FED
export TEST_SERVER_URL="http://localhost:$PORT/ocs/"
export TEST_SERVER_FED_URL="http://localhost:$PORT_FED/ocs/"
+#Enable external storage app
+../../occ app:enable files_external
+
+mkdir -p work/local_storage
+OUTPUT_CREATE_STORAGE=`../../occ files_external:create local_storage local null::null -c datadir=./build/integration/work/local_storage`
+
+ID_STORAGE=`echo $OUTPUT_CREATE_STORAGE | awk {'print $5'}`
+
+../../occ files_external:option $ID_STORAGE enable_sharing true
+
vendor/bin/behat -f junit -f pretty $SCENARIO_TO_RUN
RESULT=$?
kill $PHPPID
kill $PHPPID_FED
+../../occ files_external:delete -y $ID_STORAGE
+
+#Disable external storage app
+../../occ app:disable files_external
+
if [ -z $HIDE_OC_LOGS ]; then
tail "../../data/nextcloud.log"
fi
diff --git a/core/Application.php b/core/Application.php
index 0c69394c979..9a6d0878fee 100644
--- a/core/Application.php
+++ b/core/Application.php
@@ -73,14 +73,6 @@ class Application extends App {
$c->query('TimeFactory')
);
});
- $container->registerService('UserController', function(SimpleContainer $c) {
- return new UserController(
- $c->query('AppName'),
- $c->query('Request'),
- $c->query('UserManager'),
- $c->query('Defaults')
- );
- });
$container->registerService('LoginController', function(SimpleContainer $c) {
return new LoginController(
$c->query('AppName'),
diff --git a/core/Controller/AvatarController.php b/core/Controller/AvatarController.php
index 3aa002634d8..5b64320948a 100644
--- a/core/Controller/AvatarController.php
+++ b/core/Controller/AvatarController.php
@@ -29,8 +29,8 @@ namespace OC\Core\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
-use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\DataDisplayResponse;
+use OCP\AppFramework\Http\JSONResponse;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
@@ -111,7 +111,7 @@ class AvatarController extends Controller {
*
* @param string $userId
* @param int $size
- * @return DataResponse|DataDisplayResponse
+ * @return JSONResponse|DataDisplayResponse
*/
public function getAvatar($userId, $size) {
if ($size > 2048) {
@@ -128,13 +128,13 @@ class AvatarController extends Controller {
$resp->setETag($avatar->getEtag());
} catch (NotFoundException $e) {
$user = $this->userManager->get($userId);
- $resp = new DataResponse([
+ $resp = new JSONResponse([
'data' => [
'displayname' => $user->getDisplayName(),
],
]);
} catch (\Exception $e) {
- $resp = new DataResponse([
+ $resp = new JSONResponse([
'data' => [
'displayname' => '',
],
@@ -152,25 +152,22 @@ class AvatarController extends Controller {
* @NoAdminRequired
*
* @param string $path
- * @return DataResponse
+ * @return JSONResponse
*/
public function postAvatar($path) {
$files = $this->request->getUploadedFile('files');
- $headers = [];
-
if (isset($path)) {
$path = stripslashes($path);
$userFolder = $this->rootFolder->getUserFolder($this->userId);
$node = $userFolder->get($path);
if (!($node instanceof File)) {
- return new DataResponse(['data' => ['message' => $this->l->t('Please select a file.')]], Http::STATUS_OK, $headers);
+ return new JSONResponse(['data' => ['message' => $this->l->t('Please select a file.')]]);
}
if ($node->getSize() > 20*1024*1024) {
- return new DataResponse(
+ return new JSONResponse(
['data' => ['message' => $this->l->t('File is too big')]],
- Http::STATUS_BAD_REQUEST,
- $headers
+ Http::STATUS_BAD_REQUEST
);
}
$content = $node->getContent();
@@ -181,28 +178,25 @@ class AvatarController extends Controller {
!\OC\Files\Filesystem::isFileBlacklisted($files['tmp_name'][0])
) {
if ($files['size'][0] > 20*1024*1024) {
- return new DataResponse(
+ return new JSONResponse(
['data' => ['message' => $this->l->t('File is too big')]],
- Http::STATUS_BAD_REQUEST,
- $headers
+ Http::STATUS_BAD_REQUEST
);
}
$this->cache->set('avatar_upload', file_get_contents($files['tmp_name'][0]), 7200);
$content = $this->cache->get('avatar_upload');
unlink($files['tmp_name'][0]);
} else {
- return new DataResponse(
+ return new JSONResponse(
['data' => ['message' => $this->l->t('Invalid file provided')]],
- Http::STATUS_BAD_REQUEST,
- $headers
+ Http::STATUS_BAD_REQUEST
);
}
} else {
//Add imgfile
- return new DataResponse(
+ return new JSONResponse(
['data' => ['message' => $this->l->t('No image or file provided')]],
- Http::STATUS_BAD_REQUEST,
- $headers
+ Http::STATUS_BAD_REQUEST
);
}
@@ -214,57 +208,54 @@ class AvatarController extends Controller {
if ($image->valid()) {
$mimeType = $image->mimeType();
if ($mimeType !== 'image/jpeg' && $mimeType !== 'image/png') {
- return new DataResponse(
+ return new JSONResponse(
['data' => ['message' => $this->l->t('Unknown filetype')]],
- Http::STATUS_OK,
- $headers
+ Http::STATUS_OK
);
}
$this->cache->set('tmpAvatar', $image->data(), 7200);
- return new DataResponse(
+ return new JSONResponse(
['data' => 'notsquare'],
- Http::STATUS_OK,
- $headers
+ Http::STATUS_OK
);
} else {
- return new DataResponse(
+ return new JSONResponse(
['data' => ['message' => $this->l->t('Invalid image')]],
- Http::STATUS_OK,
- $headers
+ Http::STATUS_OK
);
}
} catch (\Exception $e) {
$this->logger->logException($e, ['app' => 'core']);
- return new DataResponse(['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], Http::STATUS_OK, $headers);
+ return new JSONResponse(['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], Http::STATUS_OK);
}
}
/**
* @NoAdminRequired
*
- * @return DataResponse
+ * @return JSONResponse
*/
public function deleteAvatar() {
try {
$avatar = $this->avatarManager->getAvatar($this->userId);
$avatar->remove();
- return new DataResponse();
+ return new JSONResponse();
} catch (\Exception $e) {
$this->logger->logException($e, ['app' => 'core']);
- return new DataResponse(['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], Http::STATUS_BAD_REQUEST);
+ return new JSONResponse(['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], Http::STATUS_BAD_REQUEST);
}
}
/**
* @NoAdminRequired
*
- * @return DataResponse|DataDisplayResponse
+ * @return JSONResponse|DataDisplayResponse
*/
public function getTmpAvatar() {
$tmpAvatar = $this->cache->get('tmpAvatar');
if (is_null($tmpAvatar)) {
- return new DataResponse(['data' => [
+ return new JSONResponse(['data' => [
'message' => $this->l->t("No temporary profile picture available, try again")
]],
Http::STATUS_NOT_FOUND);
@@ -286,22 +277,22 @@ class AvatarController extends Controller {
* @NoAdminRequired
*
* @param array $crop
- * @return DataResponse
+ * @return JSONResponse
*/
public function postCroppedAvatar($crop) {
if (is_null($crop)) {
- return new DataResponse(['data' => ['message' => $this->l->t("No crop data provided")]],
+ return new JSONResponse(['data' => ['message' => $this->l->t("No crop data provided")]],
Http::STATUS_BAD_REQUEST);
}
if (!isset($crop['x'], $crop['y'], $crop['w'], $crop['h'])) {
- return new DataResponse(['data' => ['message' => $this->l->t("No valid crop data provided")]],
+ return new JSONResponse(['data' => ['message' => $this->l->t("No valid crop data provided")]],
Http::STATUS_BAD_REQUEST);
}
$tmpAvatar = $this->cache->get('tmpAvatar');
if (is_null($tmpAvatar)) {
- return new DataResponse(['data' => [
+ return new JSONResponse(['data' => [
'message' => $this->l->t("No temporary profile picture available, try again")
]],
Http::STATUS_BAD_REQUEST);
@@ -314,13 +305,13 @@ class AvatarController extends Controller {
$avatar->set($image);
// Clean up
$this->cache->remove('tmpAvatar');
- return new DataResponse(['status' => 'success']);
+ return new JSONResponse(['status' => 'success']);
} catch (\OC\NotSquareException $e) {
- return new DataResponse(['data' => ['message' => $this->l->t('Crop is not square')]],
+ return new JSONResponse(['data' => ['message' => $this->l->t('Crop is not square')]],
Http::STATUS_BAD_REQUEST);
} catch (\Exception $e) {
$this->logger->logException($e, ['app' => 'core']);
- return new DataResponse(['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], Http::STATUS_BAD_REQUEST);
+ return new JSONResponse(['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], Http::STATUS_BAD_REQUEST);
}
}
}
diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php
index 67e1e215289..083f4bb0518 100644
--- a/core/Controller/LoginController.php
+++ b/core/Controller/LoginController.php
@@ -25,7 +25,6 @@
namespace OC\Core\Controller;
-use OC\AppFramework\Utility\TimeFactory;
use OC\Authentication\TwoFactorAuth\Manager;
use OC\Security\Bruteforce\Throttler;
use OC\User\Session;
@@ -242,12 +241,26 @@ class LoginController extends Controller {
if ($this->twoFactorManager->isTwoFactorAuthenticated($loginResult)) {
$this->twoFactorManager->prepareTwoFactorLogin($loginResult);
+
+ $providers = $this->twoFactorManager->getProviders($loginResult);
+ if (count($providers) === 1) {
+ // Single provider, hence we can redirect to that provider's challenge page directly
+ /* @var $provider IProvider */
+ $provider = array_pop($providers);
+ $url = 'core.TwoFactorChallenge.showChallenge';
+ $urlParams = [
+ 'challengeProviderId' => $provider->getId(),
+ ];
+ } else {
+ $url = 'core.TwoFactorChallenge.selectChallenge';
+ $urlParams = [];
+ }
+
if (!is_null($redirect_url)) {
- return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge', [
- 'redirect_url' => $redirect_url
- ]));
+ $urlParams['redirect_url'] = $redirect_url;
}
- return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
+
+ return new RedirectResponse($this->urlGenerator->linkToRoute($url, $urlParams));
}
return $this->generateRedirect($redirect_url);
diff --git a/core/Controller/LostController.php b/core/Controller/LostController.php
index fe6be1e6852..b1111559a6c 100644
--- a/core/Controller/LostController.php
+++ b/core/Controller/LostController.php
@@ -40,7 +40,6 @@ use \OCP\IConfig;
use OCP\IUserManager;
use OCP\Mail\IMailer;
use OCP\Security\ISecureRandom;
-use OCP\Security\StringUtils;
/**
* Class LostController
@@ -144,7 +143,7 @@ class LostController extends Controller {
}
/**
- * @param string $userId
+ * @param string $token
* @param string $userId
* @throws \Exception
*/
@@ -161,7 +160,7 @@ class LostController extends Controller {
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired'));
}
- if (!StringUtils::equals($splittedToken[1], $token)) {
+ if (!hash_equals($splittedToken[1], $token)) {
throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
}
}
diff --git a/core/Controller/TokenController.php b/core/Controller/TokenController.php
index 9d4fd7c9656..6e3ff50fa1d 100644
--- a/core/Controller/TokenController.php
+++ b/core/Controller/TokenController.php
@@ -24,13 +24,10 @@
namespace OC\Core\Controller;
use OC\AppFramework\Http;
-use OC\AppFramework\Utility\TimeFactory;
-use OC\Authentication\Token\DefaultTokenProvider;
use OC\Authentication\Token\IProvider;
use OC\Authentication\Token\IToken;
use OC\Authentication\TwoFactorAuth\Manager as TwoFactorAuthManager;
use OC\User\Manager as UserManager;
-use OCA\User_LDAP\User\Manager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
@@ -100,9 +97,9 @@ class TokenController extends Controller {
$token = $this->secureRandom->generate(128);
$this->tokenProvider->generateToken($token, $user->getUID(), $loginName, $password, $name, IToken::PERMANENT_TOKEN);
- return [
+ return new JSONResponse([
'token' => $token,
- ];
+ ]);
}
}
diff --git a/core/Controller/TwoFactorChallengeController.php b/core/Controller/TwoFactorChallengeController.php
index b9e10b147ce..c19cf523279 100644
--- a/core/Controller/TwoFactorChallengeController.php
+++ b/core/Controller/TwoFactorChallengeController.php
@@ -96,7 +96,7 @@ class TwoFactorChallengeController extends Controller {
*
* @param string $challengeProviderId
* @param string $redirect_url
- * @return TemplateResponse
+ * @return TemplateResponse|RedirectResponse
*/
public function showChallenge($challengeProviderId, $redirect_url) {
$user = $this->userSession->getUser();
diff --git a/core/Controller/UserController.php b/core/Controller/UserController.php
index 0cede94eb6e..fc282e36d9b 100644
--- a/core/Controller/UserController.php
+++ b/core/Controller/UserController.php
@@ -26,26 +26,20 @@ namespace OC\Core\Controller;
use \OCP\AppFramework\Controller;
use \OCP\AppFramework\Http\JSONResponse;
use \OCP\IRequest;
+use \OCP\IUserManager;
class UserController extends Controller {
/**
- * @var \OCP\IUserManager
+ * @var IUserManager
*/
protected $userManager;
- /**
- * @var \OC_Defaults
- */
- protected $defaults;
-
public function __construct($appName,
IRequest $request,
- $userManager,
- $defaults
+ IUserManager $userManager
) {
parent::__construct($appName, $request);
$this->userManager = $userManager;
- $this->defaults = $defaults;
}
/**
diff --git a/core/ajax/preview.php b/core/ajax/preview.php
index 2894efdc8e3..6cfba6aef30 100644
--- a/core/ajax/preview.php
+++ b/core/ajax/preview.php
@@ -53,6 +53,8 @@ $info = \OC\Files\Filesystem::getFileInfo($file);
if (!$info instanceof OCP\Files\FileInfo || !$always && !\OC::$server->getPreviewManager()->isAvailable($info)) {
\OC_Response::setStatus(404);
+} else if (!$info->isReadable()) {
+ \OC_Response::setStatus(403);
} else {
$preview = new \OC\Preview(\OC_User::getUser(), 'files');
$preview->setFile($file, $info);
diff --git a/core/css/multiselect.css b/core/css/multiselect.css
index ef56044fd05..cc1d6a3b468 100644
--- a/core/css/multiselect.css
+++ b/core/css/multiselect.css
@@ -42,6 +42,8 @@ ul.multiselectoptions > li input[type='checkbox']+label {
width: 100%;
padding: 5px 27px;
margin-left: -27px; /* to have area around checkbox clickable as well */
+ text-overflow: ellipsis;
+ overflow: hidden;
}
ul.multiselectoptions > li input[type='checkbox']:checked+label {
font-weight: bold;
diff --git a/core/css/styles.css b/core/css/styles.css
index c2b883e4a36..25bc2d086d5 100644
--- a/core/css/styles.css
+++ b/core/css/styles.css
@@ -301,7 +301,8 @@ body {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)";
opacity: .7;
}
-#body-login input[type="password"] {
+#body-login input[type="password"],
+#body-login input[name="adminpass-clone"] {
padding-right: 40px;
box-sizing: border-box;
min-width: 269px;
diff --git a/core/js/mimetypelist.js b/core/js/mimetypelist.js
index 08b892ce8bb..e1b9dba14af 100644
--- a/core/js/mimetypelist.js
+++ b/core/js/mimetypelist.js
@@ -65,6 +65,9 @@ OC.MimeTypeList={
"application/x-font": "image",
"application/x-gimp": "image",
"application/x-gzip": "package/x-generic",
+ "application/x-iwork-keynote-sffkey": "x-office/presentation",
+ "application/x-iwork-numbers-sffnumbers": "x-office/spreadsheet",
+ "application/x-iwork-pages-sffpages": "x-office/document",
"application/x-mobipocket-ebook": "text",
"application/x-perl": "text/code",
"application/x-photoshop": "image",
diff --git a/core/js/multiselect.js b/core/js/multiselect.js
index 71cf3e10a69..bdf420a2f7f 100644
--- a/core/js/multiselect.js
+++ b/core/js/multiselect.js
@@ -32,7 +32,7 @@
'onuncheck':false,
'minWidth': 'default;'
};
- var slideDuration = 200;
+ var slideDuration = 0;
$(this).attr('data-msid', multiSelectId);
$.extend(settings,options);
$.each(this.children(),function(i,option) {
@@ -75,6 +75,26 @@
var self = this;
self.menuDirection = 'down';
+
+ function closeDropDown() {
+ if(!button.parent().data('preventHide')) {
+ // How can I save the effect in a var?
+ if(self.menuDirection === 'down') {
+ button.parent().children('ul').slideUp(slideDuration,function() {
+ button.parent().children('ul').remove();
+ button.removeClass('active down');
+ $(self).trigger($.Event('dropdownclosed', settings));
+ });
+ } else {
+ button.parent().children('ul').fadeOut(slideDuration,function() {
+ button.parent().children('ul').remove();
+ button.removeClass('active up');
+ $(self).trigger($.Event('dropdownclosed', settings));
+ });
+ }
+ }
+ }
+
button.click(function(event){
var button=$(this);
@@ -83,21 +103,20 @@
button.parent().children('ul').slideUp(slideDuration,function() {
button.parent().children('ul').remove();
button.removeClass('active down');
+ $(self).trigger($.Event('dropdownclosed', settings));
});
} else {
button.parent().children('ul').fadeOut(slideDuration,function() {
button.parent().children('ul').remove();
button.removeClass('active up');
+ $(self).trigger($.Event('dropdownclosed', settings));
});
}
return;
}
+ // tell other lists to shut themselves
var lists=$('ul.multiselectoptions');
- lists.slideUp(slideDuration,function(){
- lists.remove();
- $('div.multiselect').removeClass('active');
- button.addClass('active');
- });
+ lists.trigger($.Event('shut'));
button.addClass('active');
event.stopPropagation();
var options=$(this).parent().next().children();
@@ -309,29 +328,16 @@
list.detach().insertBefore($(this));
list.addClass('up');
button.addClass('up');
- list.fadeIn();
+ list.show();
self.menuDirection = 'up';
}
list.click(function(event) {
event.stopPropagation();
});
+ list.one('shut', closeDropDown);
});
- $(window).click(function() {
- if(!button.parent().data('preventHide')) {
- // How can I save the effect in a var?
- if(self.menuDirection === 'down') {
- button.parent().children('ul').slideUp(slideDuration,function() {
- button.parent().children('ul').remove();
- button.removeClass('active down');
- });
- } else {
- button.parent().children('ul').fadeOut(slideDuration,function() {
- button.parent().children('ul').remove();
- button.removeClass('active up');
- });
- }
- }
- });
+
+ $(window).click(closeDropDown);
return span;
};
diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js
index b77063a9eae..75c8ef9020e 100644
--- a/core/js/oc-dialogs.js
+++ b/core/js/oc-dialogs.js
@@ -218,6 +218,13 @@ var OCdialogs = {
self.$filePicker = null;
}
});
+
+ // We can access primary class only from oc-dialog.
+ // Hence this is one of the approach to get the choose button.
+ var getOcDialog = self.$filePicker.closest('.oc-dialog');
+ var buttonEnableDisable = getOcDialog.find('.primary');
+ buttonEnableDisable.prop("disabled", "true");
+
if (!OC.Util.hasSVGSupport()) {
OC.Util.replaceSVG(self.$filePicker.parent());
}
@@ -812,18 +819,25 @@ var OCdialogs = {
var self = event.data;
var dir = $(event.target).data('dir');
self._fillFilePicker(dir);
+ var getOcDialog = this.closest('.oc-dialog');
+ var buttonEnableDisable = $('.primary', getOcDialog);
+ buttonEnableDisable.prop("disabled", true);
},
/**
* handle clicks made in the filepicker
*/
_handlePickerClick:function(event, $element) {
+ var getOcDialog = this.$filePicker.closest('.oc-dialog');
+ var buttonEnableDisable = getOcDialog.find('.primary');
if ($element.data('type') === 'file') {
if (this.$filePicker.data('multiselect') !== true || !event.ctrlKey) {
this.$filelist.find('.filepicker_element_selected').removeClass('filepicker_element_selected');
}
$element.toggleClass('filepicker_element_selected');
+ buttonEnableDisable.prop("disabled", false);
} else if ( $element.data('type') === 'dir' ) {
this._fillFilePicker(this.$filePicker.data('path') + '/' + $element.data('entryname'));
+ buttonEnableDisable.prop("disabled", true);
}
}
};
diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js
index 83bf7979000..1d6a0f03d4d 100644
--- a/core/js/sharedialoglinkshareview.js
+++ b/core/js/sharedialoglinkshareview.js
@@ -330,7 +330,7 @@
publicUpload: publicUpload && isLinkShare,
publicUploadChecked: publicUploadChecked,
hideFileListChecked: hideFileListChecked,
- publicUploadLabel: t('core', 'Allow editing'),
+ publicUploadLabel: t('core', 'Allow upload and editing'),
hideFileListLabel: t('core', 'Hide file listing'),
mailPublicNotificationEnabled: isLinkShare && this.configModel.isMailPublicNotificationEnabled(),
mailPrivatePlaceholder: t('core', 'Email link to person'),
diff --git a/index.php b/index.php
index e42a0bbd7bc..8c3066d2409 100644
--- a/index.php
+++ b/index.php
@@ -25,10 +25,10 @@
*
*/
-// Show warning if a PHP version below 5.4.0 is used, this has to happen here
-// because base.php will already use 5.4 syntax.
-if (version_compare(PHP_VERSION, '5.4.0') === -1) {
- echo 'This version of ownCloud requires at least PHP 5.4.0<br/>';
+// Show warning if a PHP version below 5.6.0 is used, this has to happen here
+// because base.php will already use 5.6 syntax.
+if (version_compare(PHP_VERSION, '5.6.0') === -1) {
+ echo 'This version of Nextcloud requires at least PHP 5.6.0<br/>';
echo 'You are currently running ' . PHP_VERSION . '. Please update your PHP version.';
return;
}
diff --git a/lib/base.php b/lib/base.php
index a69a4dffef8..fe7419e6ff3 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -268,7 +268,7 @@ class OC {
if (OC::$CLI) {
throw new Exception('Not installed');
} else {
- $url = 'http://' . $_SERVER['SERVER_NAME'] . OC::$WEBROOT . '/index.php';
+ $url = OC::$WEBROOT . '/index.php';
header('Location: ' . $url);
}
exit();
@@ -922,6 +922,9 @@ class OC {
$request = \OC::$server->getRequest();
$requestPath = $request->getRawPathInfo();
+ if ($requestPath === '/heartbeat') {
+ return;
+ }
if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
self::checkMaintenanceMode();
self::checkUpgrade();
diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php
index 99b127ab220..8a076084ac5 100644
--- a/lib/private/Files/Cache/Storage.php
+++ b/lib/private/Files/Cache/Storage.php
@@ -57,15 +57,15 @@ class Storage {
$this->storageId = self::adjustStorageId($this->storageId);
if ($row = self::getStorageById($this->storageId)) {
- $this->numericId = $row['numeric_id'];
+ $this->numericId = (int)$row['numeric_id'];
} else {
$connection = \OC::$server->getDatabaseConnection();
$available = $isAvailable ? 1 : 0;
if ($connection->insertIfNotExist('*PREFIX*storages', ['id' => $this->storageId, 'available' => $available])) {
- $this->numericId = $connection->lastInsertId('*PREFIX*storages');
+ $this->numericId = (int)$connection->lastInsertId('*PREFIX*storages');
} else {
if ($row = self::getStorageById($this->storageId)) {
- $this->numericId = $row['numeric_id'];
+ $this->numericId = (int)$row['numeric_id'];
} else {
throw new \RuntimeException('Storage could neither be inserted nor be selected from the database');
}
@@ -132,7 +132,7 @@ class Storage {
$storageId = self::adjustStorageId($storageId);
if ($row = self::getStorageById($storageId)) {
- return $row['numeric_id'];
+ return (int)$row['numeric_id'];
} else {
return null;
}
diff --git a/lib/private/Preview.php b/lib/private/Preview.php
index 70b000a30ee..67838a8d4a3 100644
--- a/lib/private/Preview.php
+++ b/lib/private/Preview.php
@@ -763,7 +763,7 @@ class Preview {
$this->preview = null;
$fileInfo = $this->getFileInfo();
- if ($fileInfo === null || $fileInfo === false) {
+ if ($fileInfo === null || $fileInfo === false || !$fileInfo->isReadable()) {
return new \OC_Image();
}
diff --git a/lib/private/Repair/RepairUnmergedShares.php b/lib/private/Repair/RepairUnmergedShares.php
index 353877bb873..d57bc3779f8 100644
--- a/lib/private/Repair/RepairUnmergedShares.php
+++ b/lib/private/Repair/RepairUnmergedShares.php
@@ -93,7 +93,7 @@ class RepairUnmergedShares implements IRepairStep {
*/
$query = $this->connection->getQueryBuilder();
$query
- ->select('item_source', 'id', 'file_target', 'permissions', 'parent', 'share_type')
+ ->select('item_source', 'id', 'file_target', 'permissions', 'parent', 'share_type', 'stime')
->from('share')
->where($query->expr()->eq('share_type', $query->createParameter('shareType')))
->andWhere($query->expr()->in('share_with', $query->createParameter('shareWiths')))
@@ -148,6 +148,52 @@ class RepairUnmergedShares implements IRepairStep {
return $groupedShares;
}
+ private function isPotentialDuplicateName($name) {
+ return (preg_match('/\(\d+\)(\.[^\.]+)?$/', $name) === 1);
+ }
+
+ /**
+ * Decide on the best target name based on all group shares and subshares,
+ * goal is to increase the likeliness that the chosen name matches what
+ * the user is expecting.
+ *
+ * For this, we discard the entries with parenthesis "(2)".
+ * In case the user also renamed the duplicates to a legitimate name, this logic
+ * will still pick the most recent one as it's the one the user is most likely to
+ * remember renaming.
+ *
+ * If no suitable subshare is found, use the least recent group share instead.
+ *
+ * @param array $groupShares group share entries
+ * @param array $subShares sub share entries
+ *
+ * @return string chosen target name
+ */
+ private function findBestTargetName($groupShares, $subShares) {
+ $pickedShare = null;
+ // sort by stime, this also properly sorts the direct user share if any
+ @usort($subShares, function($a, $b) {
+ return ((int)$a['stime'] - (int)$b['stime']);
+ });
+
+ foreach ($subShares as $subShare) {
+ // skip entries that have parenthesis with numbers
+ if ($this->isPotentialDuplicateName($subShare['file_target'])) {
+ continue;
+ }
+ // pick any share found that would match, the last being the most recent
+ $pickedShare = $subShare;
+ }
+
+ // no suitable subshare found
+ if ($pickedShare === null) {
+ // use least recent group share target instead
+ $pickedShare = $groupShares[0];
+ }
+
+ return $pickedShare['file_target'];
+ }
+
/**
* Fix the given received share represented by the set of group shares
* and matching sub shares
@@ -171,7 +217,7 @@ class RepairUnmergedShares implements IRepairStep {
return false;
}
- $targetPath = $groupShares[0]['file_target'];
+ $targetPath = $this->findBestTargetName($groupShares, $subShares);
// check whether the user opted out completely of all subshares
$optedOut = true;
diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php
index 11a343918c6..031c5ffd411 100644
--- a/lib/private/Security/Bruteforce/Throttler.php
+++ b/lib/private/Security/Bruteforce/Throttler.php
@@ -225,8 +225,11 @@ class Throttler {
* Will sleep for the defined amount of time
*
* @param string $ip
+ * @return int the time spent sleeping
*/
public function sleepDelay($ip) {
- usleep($this->getDelay($ip) * 1000);
+ $delay = $this->getDelay($ip);
+ usleep($delay * 1000);
+ return $delay;
}
}
diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php
index f41468d4926..7d8c6d48b2c 100644
--- a/lib/private/User/Manager.php
+++ b/lib/private/User/Manager.php
@@ -157,6 +157,16 @@ class Manager extends PublicEmitter implements IUserManager {
return $this->cachedUsers[$uid];
}
+ if (method_exists($backend, 'loginName2UserName')) {
+ $loginName = $backend->loginName2UserName($uid);
+ if ($loginName !== false) {
+ $uid = $loginName;
+ }
+ if (isset($this->cachedUsers[$uid])) {
+ return $this->cachedUsers[$uid];
+ }
+ }
+
$user = new User($uid, $backend, $this, $this->config);
if ($cacheUser) {
$this->cachedUsers[$uid] = $user;
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index 3b357b69bcf..dec959820f8 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -309,8 +309,7 @@ class Session implements IUserSession, Emitter {
$password,
IRequest $request,
OC\Security\Bruteforce\Throttler $throttler) {
- $currentDelay = $throttler->getDelay($request->getRemoteAddress());
- $throttler->sleepDelay($request->getRemoteAddress());
+ $currentDelay = $throttler->sleepDelay($request->getRemoteAddress());
$isTokenPassword = $this->isTokenPassword($password);
if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php
index 2c20daf5d44..fee1a805c40 100644
--- a/lib/private/legacy/image.php
+++ b/lib/private/legacy/image.php
@@ -84,11 +84,6 @@ class OC_Image implements \OCP\IImage {
$this->logger = \OC::$server->getLogger();
}
- if (!extension_loaded('gd') || !function_exists('gd_info')) {
- $this->logger->error(__METHOD__ . '(): GD module not installed', array('app' => 'core'));
- return false;
- }
-
if (\OC_Util::fileInfoLoaded()) {
$this->fileInfo = new finfo(FILEINFO_MIME_TYPE);
}
@@ -802,8 +797,8 @@ class OC_Image implements \OCP\IImage {
$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
return false;
}
- $widthOrig = imageSX($this->resource);
- $heightOrig = imageSY($this->resource);
+ $widthOrig = imagesx($this->resource);
+ $heightOrig = imagesy($this->resource);
$ratioOrig = $widthOrig / $heightOrig;
if ($ratioOrig > 1) {
@@ -828,8 +823,8 @@ class OC_Image implements \OCP\IImage {
$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
return false;
}
- $widthOrig = imageSX($this->resource);
- $heightOrig = imageSY($this->resource);
+ $widthOrig = imagesx($this->resource);
+ $heightOrig = imagesy($this->resource);
$process = imagecreatetruecolor($width, $height);
if ($process == false) {
@@ -867,8 +862,8 @@ class OC_Image implements \OCP\IImage {
$this->logger->error('OC_Image->centerCrop, No image loaded', array('app' => 'core'));
return false;
}
- $widthOrig = imageSX($this->resource);
- $heightOrig = imageSY($this->resource);
+ $widthOrig = imagesx($this->resource);
+ $heightOrig = imagesy($this->resource);
if ($widthOrig === $heightOrig and $size == 0) {
return true;
}
@@ -967,8 +962,8 @@ class OC_Image implements \OCP\IImage {
$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
return false;
}
- $widthOrig = imageSX($this->resource);
- $heightOrig = imageSY($this->resource);
+ $widthOrig = imagesx($this->resource);
+ $heightOrig = imagesy($this->resource);
$ratio = $widthOrig / $heightOrig;
$newWidth = min($maxWidth, $ratio * $maxHeight);
@@ -990,8 +985,8 @@ class OC_Image implements \OCP\IImage {
$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
return false;
}
- $widthOrig = imageSX($this->resource);
- $heightOrig = imageSY($this->resource);
+ $widthOrig = imagesx($this->resource);
+ $heightOrig = imagesy($this->resource);
if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) {
return $this->fitIn($maxWidth, $maxHeight);
@@ -1024,6 +1019,7 @@ if (!function_exists('imagebmp')) {
* @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
* @author mgutt <marc@gutt.it>
* @version 1.00
+ * @param resource $im
* @param string $fileName [optional] <p>The path to save the file to.</p>
* @param int $bit [optional] <p>Bit depth, (default is 24).</p>
* @param int $compression [optional]
diff --git a/ocs/routes.php b/ocs/routes.php
index d14f32e045c..3085cd9db65 100644
--- a/ocs/routes.php
+++ b/ocs/routes.php
@@ -75,76 +75,3 @@ API::register(
'core',
API::USER_AUTH
);
-
-// Server-to-Server Sharing
-if (\OC::$server->getAppManager()->isEnabledForUser('files_sharing')) {
- $federatedSharingApp = new \OCA\FederatedFileSharing\AppInfo\Application();
- $addressHandler = new \OCA\FederatedFileSharing\AddressHandler(
- \OC::$server->getURLGenerator(),
- \OC::$server->getL10N('federatedfilesharing')
- );
- $notification = new \OCA\FederatedFileSharing\Notifications(
- $addressHandler,
- \OC::$server->getHTTPClientService(),
- new \OCA\FederatedFileSharing\DiscoveryManager(\OC::$server->getMemCacheFactory(), \OC::$server->getHTTPClientService()),
- \OC::$server->getJobList()
- );
- $s2s = new OCA\FederatedFileSharing\RequestHandler(
- $federatedSharingApp->getFederatedShareProvider(),
- \OC::$server->getDatabaseConnection(),
- \OC::$server->getShareManager(),
- \OC::$server->getRequest(),
- $notification,
- $addressHandler,
- \OC::$server->getUserManager()
- );
- API::register('post',
- '/cloud/shares',
- array($s2s, 'createShare'),
- 'files_sharing',
- API::GUEST_AUTH
- );
-
- API::register('post',
- '/cloud/shares/{id}/reshare',
- array($s2s, 'reShare'),
- 'files_sharing',
- API::GUEST_AUTH
- );
-
- API::register('post',
- '/cloud/shares/{id}/permissions',
- array($s2s, 'updatePermissions'),
- 'files_sharing',
- API::GUEST_AUTH
- );
-
-
- API::register('post',
- '/cloud/shares/{id}/accept',
- array($s2s, 'acceptShare'),
- 'files_sharing',
- API::GUEST_AUTH
- );
-
- API::register('post',
- '/cloud/shares/{id}/decline',
- array($s2s, 'declineShare'),
- 'files_sharing',
- API::GUEST_AUTH
- );
-
- API::register('post',
- '/cloud/shares/{id}/unshare',
- array($s2s, 'unshare'),
- 'files_sharing',
- API::GUEST_AUTH
- );
-
- API::register('post',
- '/cloud/shares/{id}/revoke',
- array($s2s, 'revoke'),
- 'files_sharing',
- API::GUEST_AUTH
- );
-}
diff --git a/resources/config/mimetypealiases.dist.json b/resources/config/mimetypealiases.dist.json
index 8b47867447f..602f70393ae 100644
--- a/resources/config/mimetypealiases.dist.json
+++ b/resources/config/mimetypealiases.dist.json
@@ -65,6 +65,9 @@
"application/x-font": "image",
"application/x-gimp": "image",
"application/x-gzip": "package/x-generic",
+ "application/x-iwork-keynote-sffkey": "x-office/presentation",
+ "application/x-iwork-numbers-sffnumbers": "x-office/spreadsheet",
+ "application/x-iwork-pages-sffpages": "x-office/document",
"application/x-mobipocket-ebook": "text",
"application/x-perl": "text/code",
"application/x-photoshop": "image",
diff --git a/settings/css/settings.css b/settings/css/settings.css
index d3fd395747e..6ed707f7c45 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -267,6 +267,15 @@ span.usersLastLoginTooltip { white-space: nowrap; }
top: 3px;
}
+#newuser .groups {
+ display: inline;
+}
+
+#newuser .groupsListContainer.hidden,
+#userlist .groupsListContainer.hidden {
+ display: none;
+}
+
tr:hover>td.password>span, tr:hover>td.displayName>span { margin:0; cursor:pointer; }
tr:hover>td.remove>a, tr:hover>td.password>img,tr:hover>td.displayName>img, tr:hover>td.quota>img { visibility:visible; cursor:pointer; }
td.remove {
diff --git a/settings/js/users/groups.js b/settings/js/users/groups.js
index e83f00970c2..8f4d95432a8 100644
--- a/settings/js/users/groups.js
+++ b/settings/js/users/groups.js
@@ -138,10 +138,6 @@ GroupList = {
var addedGroup = result.groupname;
UserList.availableGroups = $.unique($.merge(UserList.availableGroups, [addedGroup]));
GroupList.addGroup(result.groupname);
-
- $('.groupsselect, .subadminsselect')
- .append($('<option>', { value: result.groupname })
- .text(result.groupname));
}
GroupList.toggleAddGroup();
}).fail(function(result) {
diff --git a/settings/js/users/users.js b/settings/js/users/users.js
index 4ce77648826..f24bf82209b 100644
--- a/settings/js/users/users.js
+++ b/settings/js/users/users.js
@@ -59,9 +59,6 @@ var UserList = {
var $tr = $userListBody.find('tr:first-child').clone();
// this removes just the `display:none` of the template row
$tr.removeAttr('style');
- var subAdminsEl;
- var subAdminSelect;
- var groupsSelect;
/**
* Avatar or placeholder
@@ -88,32 +85,17 @@ var UserList = {
$tr.find('td.mailAddress > .action').tooltip({placement: 'top'});
$tr.find('td.password > .action').tooltip({placement: 'top'});
+
/**
* groups and subadmins
*/
- // make them look like the multiselect buttons
- // until they get time to really get initialized
- groupsSelect = $('<select multiple="multiple" class="groupsselect multiselect button" data-placehoder="Groups" title="' + t('settings', 'No group') + '"></select>')
- .data('username', user.name)
- .data('user-groups', user.groups);
- if ($tr.find('td.subadmins').length > 0) {
- subAdminSelect = $('<select multiple="multiple" class="subadminsselect multiselect button" data-placehoder="subadmins" title="' + t('settings', 'No group') + '">')
- .data('username', user.name)
- .data('user-groups', user.groups)
- .data('subadmin', user.subadmin);
- $tr.find('td.subadmins').empty();
- }
- $.each(this.availableGroups, function (i, group) {
- groupsSelect.append($('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>'));
- if (typeof subAdminSelect !== 'undefined' && group !== 'admin') {
- subAdminSelect.append($('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>'));
- }
- });
- $tr.find('td.groups').empty().append(groupsSelect);
- subAdminsEl = $tr.find('td.subadmins');
- if (subAdminsEl.length > 0) {
- subAdminsEl.append(subAdminSelect);
- }
+ var $tdGroups = $tr.find('td.groups');
+ this._updateGroupListLabel($tdGroups, user.groups);
+ $tdGroups.find('.action').tooltip({placement: 'top'});
+
+ var $tdSubadmins = $tr.find('td.subadmins');
+ this._updateGroupListLabel($tdSubadmins, user.subadmin);
+ $tdSubadmins.find('.action').tooltip({placement: 'top'});
/**
* remove action
@@ -200,10 +182,6 @@ var UserList = {
// defer init so the user first sees the list appear more quickly
window.setTimeout(function(){
$quotaSelect.singleSelect();
- UserList.applyGroupSelect(groupsSelect);
- if (subAdminSelect) {
- UserList.applySubadminSelect(subAdminSelect);
- }
}, 0);
return $tr;
},
@@ -324,7 +302,7 @@ var UserList = {
},
markRemove: function(uid) {
var $tr = UserList.getRow(uid);
- var groups = $tr.find('.groups .groupsselect').val();
+ var groups = $tr.find('.groups').data('groups');
for(var i in groups) {
var gid = groups[i];
var $li = GroupList.getGroupLI(gid);
@@ -339,7 +317,7 @@ var UserList = {
},
undoRemove: function(uid) {
var $tr = UserList.getRow(uid);
- var groups = $tr.find('.groups .groupsselect').val();
+ var groups = $tr.find('.groups').data('groups');
for(var i in groups) {
var gid = groups[i];
var $li = GroupList.getGroupLI(gid);
@@ -440,19 +418,9 @@ var UserList = {
});
},
- applyGroupSelect: function (element) {
- var checked = [];
+ applyGroupSelect: function (element, user, checked) {
var $element = $(element);
- var user = UserList.getUID($element);
- if ($element.data('user-groups')) {
- if (typeof $element.data('user-groups') === 'string') {
- checked = $element.data('user-groups').split(", ");
- }
- else {
- checked = $element.data('user-groups');
- }
- }
var checkHandler = null;
if(user) { // Only if in a user row, and not the #newusergroups select
checkHandler = function (group) {
@@ -492,13 +460,6 @@ var UserList = {
};
}
var addGroup = function (select, group) {
- $('select[multiple]').each(function (index, element) {
- $element = $(element);
- if ($element.find('option').filterAttr('value', group).length === 0 &&
- select.data('msid') !== $element.data('msid')) {
- $element.append('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>');
- }
- });
GroupList.addGroup(escapeHTML(group));
};
var label;
@@ -519,19 +480,8 @@ var UserList = {
});
},
- applySubadminSelect: function (element) {
- var checked = [];
+ applySubadminSelect: function (element, user, checked) {
var $element = $(element);
- var user = UserList.getUID($element);
-
- if ($element.data('subadmin')) {
- if (typeof $element.data('subadmin') === 'string') {
- checked = $element.data('subadmin').split(", ");
- }
- else {
- checked = $element.data('subadmin');
- }
- }
var checkHandler = function (group) {
if (group === 'admin') {
return false;
@@ -547,15 +497,7 @@ var UserList = {
);
};
- var addSubAdmin = function (group) {
- $('select[multiple]').each(function (index, element) {
- if ($(element).find('option').filterAttr('value', group).length === 0) {
- $(element).append('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>');
- }
- });
- };
$element.multiSelect({
- createCallback: addSubAdmin,
createText: null,
checked: checked,
oncheck: checkHandler,
@@ -613,6 +555,76 @@ var UserList = {
}
}
);
+ },
+
+ /**
+ * Creates a temporary jquery.multiselect selector on the given group field
+ */
+ _triggerGroupEdit: function($td, isSubadminSelect) {
+ var $groupsListContainer = $td.find('.groupsListContainer');
+ var placeholder = $groupsListContainer.attr('data-placeholder') || t('settings', 'no group');
+ var user = UserList.getUID($td);
+ var checked = $td.data('groups') || [];
+ var extraGroups = [].concat(checked);
+
+ $td.find('.multiselectoptions').remove();
+
+ // jquery.multiselect can only work with select+options in DOM ? We'll give jquery.multiselect what it wants...
+ var $groupsSelect;
+ if (isSubadminSelect) {
+ $groupsSelect = $('<select multiple="multiple" class="groupsselect multiselect button" title="' + placeholder + '"></select>');
+ } else {
+ $groupsSelect = $('<select multiple="multiple" class="subadminsselect multiselect button" title="' + placeholder + '"></select>')
+ }
+
+ function createItem(group) {
+ if (isSubadminSelect && group === 'admin') {
+ // can't become subadmin of "admin" group
+ return;
+ }
+ $groupsSelect.append($('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>'));
+ }
+
+ $.each(this.availableGroups, function (i, group) {
+ // some new groups might be selected but not in the available groups list yet
+ var extraIndex = extraGroups.indexOf(group);
+ if (extraIndex >= 0) {
+ // remove extra group as it was found
+ extraGroups.splice(extraIndex, 1);
+ }
+ createItem(group);
+ });
+ $.each(extraGroups, function (i, group) {
+ createItem(group);
+ });
+
+ $td.append($groupsSelect);
+
+ if (isSubadminSelect) {
+ UserList.applySubadminSelect($groupsSelect, user, checked);
+ } else {
+ UserList.applyGroupSelect($groupsSelect, user, checked);
+ }
+
+ $groupsListContainer.addClass('hidden');
+ $td.find('.multiselect:not(.groupsListContainer):first').click();
+ $groupsSelect.on('dropdownclosed', function(e) {
+ $groupsSelect.remove();
+ $td.find('.multiselect:not(.groupsListContainer)').parent().remove();
+ $td.find('.multiselectoptions').remove();
+ $groupsListContainer.removeClass('hidden');
+ UserList._updateGroupListLabel($td, e.checked);
+ });
+ },
+
+ /**
+ * Updates the groups list td with the given groups selection
+ */
+ _updateGroupListLabel: function($td, groups) {
+ var placeholder = $td.find('.groupsListContainer').attr('data-placeholder');
+ var $groupsEl = $td.find('.groupsList');
+ $groupsEl.text(groups.join(', ') || placeholder || t('settings', 'no group'));
+ $td.data('groups', groups);
}
};
@@ -637,13 +649,6 @@ $(document).ready(function () {
// TODO: move other init calls inside of initialize
UserList.initialize($('#userlist'));
- $('.groupsselect').each(function (index, element) {
- UserList.applyGroupSelect(element);
- });
- $('.subadminsselect').each(function (index, element) {
- UserList.applySubadminSelect(element);
- });
-
$userListBody.on('click', '.password', function (event) {
event.stopPropagation();
@@ -787,11 +792,24 @@ $(document).ready(function () {
});
});
+ $('#newuser .groupsListContainer').on('click', function (event) {
+ event.stopPropagation();
+ var $div = $(this).closest('.groups');
+ UserList._triggerGroupEdit($div);
+ });
+ $userListBody.on('click', '.groups .groupsListContainer, .subadmins .groupsListContainer', function (event) {
+ event.stopPropagation();
+ var $td = $(this).closest('td');
+ var isSubadminSelect = $td.hasClass('subadmins');
+ UserList._triggerGroupEdit($td, isSubadminSelect);
+ });
+
// init the quota field select box after it is shown the first time
$('#app-settings').one('show', function() {
$(this).find('#default_quota').singleSelect().on('change', UserList.onQuotaSelect);
});
+ UserList._updateGroupListLabel($('#newuser .groups'), []);
$('#newuser').submit(function (event) {
event.preventDefault();
var username = $('#newusername').val();
@@ -827,7 +845,7 @@ $(document).ready(function () {
}
promise.then(function() {
- var groups = $('#newusergroups').val() || [];
+ var groups = $('#newuser .groups').data('groups') || [];
$.post(
OC.generateUrl('/settings/users/users'),
{
diff --git a/settings/templates/users/main.php b/settings/templates/users/main.php
index f50f83b38b3..b363a4c4da8 100644
--- a/settings/templates/users/main.php
+++ b/settings/templates/users/main.php
@@ -19,10 +19,10 @@ style('settings', 'settings');
$userlistParams = array();
$allGroups=array();
-foreach($_["groups"] as $group) {
+foreach($_["adminGroup"] as $group) {
$allGroups[] = $group['name'];
}
-foreach($_["adminGroup"] as $group) {
+foreach($_["groups"] as $group) {
$allGroups[] = $group['name'];
}
$userlistParams['subadmingroups'] = $allGroups;
diff --git a/settings/templates/users/part.createuser.php b/settings/templates/users/part.createuser.php
index 0fc5a2bdeaa..6f23d06cfa3 100644
--- a/settings/templates/users/part.createuser.php
+++ b/settings/templates/users/part.createuser.php
@@ -10,16 +10,7 @@
<input id="newemail" type="text" style="display:none"
placeholder="<?php p($l->t('E-Mail'))?>"
autocomplete="off" autocapitalize="off" autocorrect="off" />
- <select
- class="groupsselect" id="newusergroups" data-placeholder="groups"
- title="<?php p($l->t('Groups'))?>" multiple="multiple">
- <?php foreach($_["adminGroup"] as $adminGroup): ?>
- <option value="<?php p($adminGroup['name']);?>"><?php p($adminGroup['name']); ?></option>
- <?php endforeach; ?>
- <?php foreach($_["groups"] as $group): ?>
- <option value="<?php p($group['name']);?>"><?php p($group['name']);?></option>
- <?php endforeach;?>
- </select>
+ <div class="groups"><div class="groupsListContainer multiselect button" data-placeholder="<?php p($l->t('Groups'))?>"><span class="title groupsList"></span><span class="icon-triangle-s"></span></div></div>
<input type="submit" class="button" value="<?php p($l->t('Create'))?>" />
</form>
<?php if((bool)$_['recoveryAdminEnabled']): ?>
diff --git a/settings/templates/users/part.userlist.php b/settings/templates/users/part.userlist.php
index 2bdd0714a3c..bab68e5a765 100644
--- a/settings/templates/users/part.userlist.php
+++ b/settings/templates/users/part.userlist.php
@@ -38,9 +38,13 @@
src="<?php p(image_path('core', 'actions/rename.svg'))?>"
alt="<?php p($l->t('change email address'))?>" title="<?php p($l->t('change email address'))?>"/>
</td>
- <td class="groups"></td>
+ <td class="groups"><div class="groupsListContainer multiselect button"
+ ><span class="title groupsList"></span><span class="icon-triangle-s"></span></div>
+ </td>
<?php if(is_array($_['subadmins']) || $_['subadmins']): ?>
- <td class="subadmins"></td>
+ <td class="subadmins"><div class="groupsListContainer multiselect button"
+ ><span class="title groupsList"></span><span class="icon-triangle-s"></span></div>
+ </td>
<?php endif;?>
<td class="quota">
<select class="quota-user" data-inputtitle="<?php p($l->t('Please enter storage quota (ex: "512 MB" or "12 GB")')) ?>">
diff --git a/status.php b/status.php
index 0d7c2285679..5b4b950139b 100644
--- a/status.php
+++ b/status.php
@@ -35,12 +35,16 @@ try {
$installed = (bool) $systemConfig->getValue('installed', false);
$maintenance = (bool) $systemConfig->getValue('maintenance', false);
+ # see core/lib/private/legacy/defaults.php and core/themes/example/defaults.php
+ # for description and defaults
+ $defaults = new \OCP\Defaults();
$values=array(
'installed'=>$installed,
'maintenance' => $maintenance,
'version'=>implode('.', \OCP\Util::getVersion()),
'versionstring'=>OC_Util::getVersionString(),
- 'edition'=>OC_Util::getEditionString());
+ 'edition'=>OC_Util::getEditionString(),
+ 'productname'=>$defaults->getName());
if (OC::$CLI) {
print_r($values);
} else {
diff --git a/tests/Core/Controller/AvatarControllerTest.php b/tests/Core/Controller/AvatarControllerTest.php
index a275a8bd16a..fe1a44b28ab 100644
--- a/tests/Core/Controller/AvatarControllerTest.php
+++ b/tests/Core/Controller/AvatarControllerTest.php
@@ -228,7 +228,7 @@ class AvatarControllerTest extends \Test\TestCase {
$this->logger->expects($this->once())
->method('logException')
->with(new \Exception("foo"));
- $expectedResponse = new Http\DataResponse(['data' => ['message' => 'An error occurred. Please contact your admin.']], Http::STATUS_BAD_REQUEST);
+ $expectedResponse = new Http\JSONResponse(['data' => ['message' => 'An error occurred. Please contact your admin.']], Http::STATUS_BAD_REQUEST);
$this->assertEquals($expectedResponse, $this->avatarController->deleteAvatar());
}
@@ -377,7 +377,7 @@ class AvatarControllerTest extends \Test\TestCase {
$this->logger->expects($this->once())
->method('logException')
->with(new \Exception("foo"));
- $expectedResponse = new Http\DataResponse(['data' => ['message' => 'An error occurred. Please contact your admin.']], Http::STATUS_OK);
+ $expectedResponse = new Http\JSONResponse(['data' => ['message' => 'An error occurred. Please contact your admin.']], Http::STATUS_OK);
$this->assertEquals($expectedResponse, $this->avatarController->postAvatar('avatar.jpg'));
}
@@ -437,7 +437,7 @@ class AvatarControllerTest extends \Test\TestCase {
$this->logger->expects($this->once())
->method('logException')
->with(new \Exception('foo'));
- $expectedResponse = new Http\DataResponse(['data' => ['message' => 'An error occurred. Please contact your admin.']], Http::STATUS_BAD_REQUEST);
+ $expectedResponse = new Http\JSONResponse(['data' => ['message' => 'An error occurred. Please contact your admin.']], Http::STATUS_BAD_REQUEST);
$this->assertEquals($expectedResponse, $this->avatarController->postCroppedAvatar(['x' => 0, 'y' => 0, 'w' => 10, 'h' => 11]));
}
diff --git a/tests/Core/Controller/LoginControllerTest.php b/tests/Core/Controller/LoginControllerTest.php
index 417a60a9e5f..ff50ac98fbd 100644
--- a/tests/Core/Controller/LoginControllerTest.php
+++ b/tests/Core/Controller/LoginControllerTest.php
@@ -505,7 +505,7 @@ class LoginControllerTest extends TestCase {
$this->assertEquals($expected, $this->loginController->tryLogin('Jane', $password, $originalUrl));
}
- public function testLoginWithTwoFactorEnforced() {
+ public function testLoginWithOneTwoFactorProvider() {
/** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->getMockBuilder('\OCP\IUser')->getMock();
$user->expects($this->any())
@@ -513,6 +513,7 @@ class LoginControllerTest extends TestCase {
->will($this->returnValue('john'));
$password = 'secret';
$challengeUrl = 'challenge/url';
+ $provider = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock();
$this->request
->expects($this->exactly(2))
@@ -547,6 +548,79 @@ class LoginControllerTest extends TestCase {
$this->twoFactorManager->expects($this->once())
->method('prepareTwoFactorLogin')
->with($user);
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviders')
+ ->with($user)
+ ->will($this->returnValue([$provider]));
+ $provider->expects($this->once())
+ ->method('getId')
+ ->will($this->returnValue('u2f'));
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with('core.TwoFactorChallenge.showChallenge', [
+ 'challengeProviderId' => 'u2f',
+ ])
+ ->will($this->returnValue($challengeUrl));
+ $this->config->expects($this->once())
+ ->method('deleteUserValue')
+ ->with('john', 'core', 'lostpassword');
+
+ $expected = new RedirectResponse($challengeUrl);
+ $this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', $password, null));
+ }
+
+ public function testLoginWithMultpleTwoFactorProviders() {
+ /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */
+ $user = $this->getMockBuilder('\OCP\IUser')->getMock();
+ $user->expects($this->any())
+ ->method('getUID')
+ ->will($this->returnValue('john'));
+ $password = 'secret';
+ $challengeUrl = 'challenge/url';
+ $provider1 = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock();
+ $provider2 = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock();
+
+ $this->request
+ ->expects($this->exactly(2))
+ ->method('getRemoteAddress')
+ ->willReturn('192.168.0.1');
+ $this->request
+ ->expects($this->once())
+ ->method('passesCSRFCheck')
+ ->willReturn(true);
+ $this->throttler
+ ->expects($this->once())
+ ->method('sleepDelay')
+ ->with('192.168.0.1');
+ $this->throttler
+ ->expects($this->once())
+ ->method('getDelay')
+ ->with('192.168.0.1')
+ ->willReturn(200);
+ $this->userManager->expects($this->once())
+ ->method('checkPassword')
+ ->will($this->returnValue($user));
+ $this->userSession->expects($this->once())
+ ->method('login')
+ ->with('john@doe.com', $password);
+ $this->userSession->expects($this->once())
+ ->method('createSessionToken')
+ ->with($this->request, $user->getUID(), 'john@doe.com', $password);
+ $this->twoFactorManager->expects($this->once())
+ ->method('isTwoFactorAuthenticated')
+ ->with($user)
+ ->will($this->returnValue(true));
+ $this->twoFactorManager->expects($this->once())
+ ->method('prepareTwoFactorLogin')
+ ->with($user);
+ $this->twoFactorManager->expects($this->once())
+ ->method('getProviders')
+ ->with($user)
+ ->will($this->returnValue([$provider1, $provider2]));
+ $provider1->expects($this->never())
+ ->method('getId');
+ $provider2->expects($this->never())
+ ->method('getId');
$this->urlGenerator->expects($this->once())
->method('linkToRoute')
->with('core.TwoFactorChallenge.selectChallenge')
diff --git a/tests/Core/Controller/TokenControllerTest.php b/tests/Core/Controller/TokenControllerTest.php
index b6b54b14fad..0e965aac2e5 100644
--- a/tests/Core/Controller/TokenControllerTest.php
+++ b/tests/Core/Controller/TokenControllerTest.php
@@ -41,15 +41,17 @@ class TokenControllerTest extends TestCase {
protected function setUp() {
parent::setUp();
- $this->request = $this->getMock('\OCP\IRequest');
+ $this->request = $this->getMockBuilder('\OCP\IRequest')->getMock();
$this->userManager = $this->getMockBuilder('\OC\User\Manager')
->disableOriginalConstructor()
->getMock();
- $this->tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider');
+ $this->tokenProvider = $this->getMockBuilder('\OC\Authentication\Token\IProvider')
+ ->getMock();
$this->twoFactorAuthManager = $this->getMockBuilder('\OC\Authentication\TwoFactorAuth\Manager')
->disableOriginalConstructor()
->getMock();
- $this->secureRandom = $this->getMock('\OCP\Security\ISecureRandom');
+ $this->secureRandom = $this->getMockBuilder('\OCP\Security\ISecureRandom')
+ ->getMock();
$this->tokenController = new TokenController('core', $this->request, $this->userManager, $this->tokenProvider, $this->twoFactorAuthManager, $this->secureRandom);
}
@@ -77,7 +79,7 @@ class TokenControllerTest extends TestCase {
}
public function testWithValidCredentials() {
- $user = $this->getMock('\OCP\IUser');
+ $user = $this->getMockBuilder('\OCP\IUser')->getMock();
$this->userManager->expects($this->once())
->method('checkPassword')
->with('john', '123456')
@@ -96,9 +98,9 @@ class TokenControllerTest extends TestCase {
$this->tokenProvider->expects($this->once())
->method('generateToken')
->with('verysecurerandomtoken', 'john', 'john', '123456', 'unknown client', IToken::PERMANENT_TOKEN);
- $expected = [
+ $expected = new JSONResponse([
'token' => 'verysecurerandomtoken'
- ];
+ ]);
$actual = $this->tokenController->generateToken('john', '123456');
@@ -106,7 +108,7 @@ class TokenControllerTest extends TestCase {
}
public function testWithValidCredentialsBut2faEnabled() {
- $user = $this->getMock('\OCP\IUser');
+ $user = $this->getMockBuilder('\OCP\IUser')->getMock();
$this->userManager->expects($this->once())
->method('checkPassword')
->with('john', '123456')
diff --git a/tests/lib/Repair/RepairLegacyStoragesTest.php b/tests/lib/Repair/RepairLegacyStoragesTest.php
index aa51fe06a35..8d8366dde06 100644
--- a/tests/lib/Repair/RepairLegacyStoragesTest.php
+++ b/tests/lib/Repair/RepairLegacyStoragesTest.php
@@ -98,23 +98,9 @@ class RepairLegacyStoragesTest extends TestCase {
$storageId = Storage::adjustStorageId($storageId);
$numRows = $this->connection->executeUpdate($sql, array($storageId));
- $this->assertEquals(1, $numRows);
+ $this->assertSame(1, $numRows);
- return \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*storages');
- }
-
- /**
- * Returns the storage id based on the numeric id
- *
- * @param int $storageId numeric id of the storage
- * @return string storage id or null if not found
- */
- private function getStorageId($storageId) {
- $numericId = Storage::getNumericStorageId($storageId);
- if (!is_null($numericId)) {
- return (int)$numericId;
- }
- return null;
+ return (int)\OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*storages');
}
/**
@@ -144,8 +130,8 @@ class RepairLegacyStoragesTest extends TestCase {
$this->repair->run($this->outputMock);
- $this->assertNull($this->getStorageId($this->legacyStorageId));
- $this->assertEquals($newStorageNumId, $this->getStorageId($this->newStorageId));
+ $this->assertNull(Storage::getNumericStorageId($this->legacyStorageId));
+ $this->assertSame($newStorageNumId, Storage::getNumericStorageId($this->newStorageId));
}
/**
@@ -163,8 +149,8 @@ class RepairLegacyStoragesTest extends TestCase {
$this->repair->run($this->outputMock);
- $this->assertNull($this->getStorageId($this->legacyStorageId));
- $this->assertEquals($legacyStorageNumId, $this->getStorageId($this->newStorageId));
+ $this->assertNull(Storage::getNumericStorageId($this->legacyStorageId));
+ $this->assertSame($legacyStorageNumId, Storage::getNumericStorageId($this->newStorageId));
}
/**
@@ -185,8 +171,8 @@ class RepairLegacyStoragesTest extends TestCase {
$this->repair->run($this->outputMock);
- $this->assertNull($this->getStorageId($this->legacyStorageId));
- $this->assertEquals($legacyStorageNumId, $this->getStorageId($this->newStorageId));
+ $this->assertNull(Storage::getNumericStorageId($this->legacyStorageId));
+ $this->assertSame($legacyStorageNumId, Storage::getNumericStorageId($this->newStorageId));
}
/**
@@ -208,8 +194,8 @@ class RepairLegacyStoragesTest extends TestCase {
$this->repair->run($this->outputMock);
- $this->assertNull($this->getStorageId($this->legacyStorageId));
- $this->assertEquals($newStorageNumId, $this->getStorageId($this->newStorageId));
+ $this->assertNull(Storage::getNumericStorageId($this->legacyStorageId));
+ $this->assertSame($newStorageNumId, Storage::getNumericStorageId($this->newStorageId));
}
/**
@@ -233,8 +219,8 @@ class RepairLegacyStoragesTest extends TestCase {
$this->repair->run($this->outputMock);
// storages left alone
- $this->assertEquals($legacyStorageNumId, $this->getStorageId($this->legacyStorageId));
- $this->assertEquals($newStorageNumId, $this->getStorageId($this->newStorageId));
+ $this->assertSame($legacyStorageNumId, Storage::getNumericStorageId($this->legacyStorageId));
+ $this->assertSame($newStorageNumId, Storage::getNumericStorageId($this->newStorageId));
// do not set the done flag
$this->assertNotEquals('yes', $this->config->getAppValue('core', 'repairlegacystoragesdone'));
@@ -255,7 +241,7 @@ class RepairLegacyStoragesTest extends TestCase {
$this->repair->run($this->outputMock);
- $this->assertEquals($numId, $this->getStorageId($storageId));
+ $this->assertSame($numId, Storage::getNumericStorageId($storageId));
}
/**
@@ -273,7 +259,7 @@ class RepairLegacyStoragesTest extends TestCase {
$this->repair->run($this->outputMock);
- $this->assertEquals($numId, $this->getStorageId($storageId));
+ $this->assertSame($numId, Storage::getNumericStorageId($storageId));
}
/**
@@ -291,7 +277,7 @@ class RepairLegacyStoragesTest extends TestCase {
$this->repair->run($this->outputMock);
- $this->assertEquals($numId, $this->getStorageId($storageId));
+ $this->assertSame($numId, Storage::getNumericStorageId($storageId));
}
/**
diff --git a/tests/lib/Repair/RepairUnmergedSharesTest.php b/tests/lib/Repair/RepairUnmergedSharesTest.php
index fe9b3e5b96f..7b9d2579389 100644
--- a/tests/lib/Repair/RepairUnmergedSharesTest.php
+++ b/tests/lib/Repair/RepairUnmergedSharesTest.php
@@ -28,6 +28,8 @@ use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use Test\TestCase;
use OC\Share20\DefaultShareProvider;
+use OCP\IUserManager;
+use OCP\IGroupManager;
/**
* Tests for repairing invalid shares
@@ -44,6 +46,15 @@ class RepairUnmergedSharesTest extends TestCase {
/** @var \OCP\IDBConnection */
private $connection;
+ /** @var int */
+ private $lastShareTime;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var IGroupManager */
+ private $groupManager;
+
protected function setUp() {
parent::setUp();
@@ -58,42 +69,14 @@ class RepairUnmergedSharesTest extends TestCase {
$this->connection = \OC::$server->getDatabaseConnection();
$this->deleteAllShares();
- $user1 = $this->getMock('\OCP\IUser');
- $user1->expects($this->any())
- ->method('getUID')
- ->will($this->returnValue('user1'));
-
- $user2 = $this->getMock('\OCP\IUser');
- $user2->expects($this->any())
- ->method('getUID')
- ->will($this->returnValue('user2'));
-
- $users = [$user1, $user2];
-
- $groupManager = $this->getMock('\OCP\IGroupManager');
- $groupManager->expects($this->any())
- ->method('getUserGroupIds')
- ->will($this->returnValueMap([
- // owner
- [$user1, ['samegroup1', 'samegroup2']],
- // recipient
- [$user2, ['recipientgroup1', 'recipientgroup2']],
- ]));
+ $this->userManager = $this->getMock('\OCP\IUserManager');
+ $this->groupManager = $this->getMock('\OCP\IGroupManager');
- $userManager = $this->getMock('\OCP\IUserManager');
- $userManager->expects($this->once())
- ->method('countUsers')
- ->will($this->returnValue([2]));
- $userManager->expects($this->once())
- ->method('callForAllUsers')
- ->will($this->returnCallback(function(\Closure $closure) use ($users) {
- foreach ($users as $user) {
- $closure($user);
- }
- }));
+ // used to generate incremental stimes
+ $this->lastShareTime = time();
/** @var \OCP\IConfig $config */
- $this->repair = new RepairUnmergedShares($config, $this->connection, $userManager, $groupManager);
+ $this->repair = new RepairUnmergedShares($config, $this->connection, $this->userManager, $this->groupManager);
}
protected function tearDown() {
@@ -108,6 +91,7 @@ class RepairUnmergedSharesTest extends TestCase {
}
private function createShare($type, $sourceId, $recipient, $targetName, $permissions, $parentId = null) {
+ $this->lastShareTime += 100;
$qb = $this->connection->getQueryBuilder();
$values = [
'share_type' => $qb->expr()->literal($type),
@@ -119,7 +103,7 @@ class RepairUnmergedSharesTest extends TestCase {
'file_source' => $qb->expr()->literal($sourceId),
'file_target' => $qb->expr()->literal($targetName),
'permissions' => $qb->expr()->literal($permissions),
- 'stime' => $qb->expr()->literal(time()),
+ 'stime' => $qb->expr()->literal($this->lastShareTime),
];
if ($parentId !== null) {
$values['parent'] = $qb->expr()->literal($parentId);
@@ -204,7 +188,7 @@ class RepairUnmergedSharesTest extends TestCase {
[
// #2 bogus share
// - outsider shares with group1, group2
- // - one subshare for each group share
+ // - one subshare for each group share, both with parenthesis
// - but the targets do not match when grouped
[
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
@@ -218,7 +202,7 @@ class RepairUnmergedSharesTest extends TestCase {
[
['/test', 31],
['/test', 31],
- // reset to original name
+ // reset to original name as the sub-names have parenthesis
['/test', 31],
['/test', 31],
// leave unrelated alone
@@ -228,6 +212,54 @@ class RepairUnmergedSharesTest extends TestCase {
[
// #3 bogus share
// - outsider shares with group1, group2
+ // - one subshare for each group share, both renamed manually
+ // - but the targets do not match when grouped
+ [
+ [Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
+ [Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 31],
+ // child of the previous ones
+ [DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test_renamed (1 legit paren)', 31, 0],
+ [DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test_renamed (2 legit paren)', 31, 1],
+ // different unrelated share
+ [Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
+ ],
+ [
+ ['/test', 31],
+ ['/test', 31],
+ // reset to less recent subshare name
+ ['/test_renamed (2 legit paren)', 31],
+ ['/test_renamed (2 legit paren)', 31],
+ // leave unrelated alone
+ ['/test (4)', 31],
+ ]
+ ],
+ [
+ // #4 bogus share
+ // - outsider shares with group1, group2
+ // - one subshare for each group share, one with parenthesis
+ // - but the targets do not match when grouped
+ [
+ [Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
+ [Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 31],
+ // child of the previous ones
+ [DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (2)', 31, 0],
+ [DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test_renamed', 31, 1],
+ // different unrelated share
+ [Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
+ ],
+ [
+ ['/test', 31],
+ ['/test', 31],
+ // reset to less recent subshare name but without parenthesis
+ ['/test_renamed', 31],
+ ['/test_renamed', 31],
+ // leave unrelated alone
+ ['/test (4)', 31],
+ ]
+ ],
+ [
+ // #5 bogus share
+ // - outsider shares with group1, group2
// - one subshare for each group share
// - first subshare not renamed (as in real world scenario)
// - but the targets do not match when grouped
@@ -251,7 +283,7 @@ class RepairUnmergedSharesTest extends TestCase {
]
],
[
- // #4 bogus share:
+ // #6 bogus share:
// - outsider shares with group1, group2
// - one subshare for each group share
// - non-matching targets
@@ -276,7 +308,7 @@ class RepairUnmergedSharesTest extends TestCase {
]
],
[
- // #5 bogus share:
+ // #7 bogus share:
// - outsider shares with group1, group2
// - one subshare for each group share
// - non-matching targets
@@ -301,7 +333,7 @@ class RepairUnmergedSharesTest extends TestCase {
]
],
[
- // #6 bogus share:
+ // #8 bogus share:
// - outsider shares with group1, group2 and also user2
// - one subshare for each group share
// - one extra share entry for direct share to user2
@@ -329,7 +361,7 @@ class RepairUnmergedSharesTest extends TestCase {
]
],
[
- // #7 bogus share:
+ // #9 bogus share:
// - outsider shares with group1 and also user2
// - no subshare at all
// - one extra share entry for direct share to user2
@@ -350,7 +382,7 @@ class RepairUnmergedSharesTest extends TestCase {
]
],
[
- // #8 legitimate share with own group:
+ // #10 legitimate share with own group:
// - insider shares with both groups the user is already in
// - no subshares in this case
[
@@ -368,7 +400,7 @@ class RepairUnmergedSharesTest extends TestCase {
]
],
[
- // #9 legitimate shares:
+ // #11 legitimate shares:
// - group share with same group
// - group share with other group
// - user share where recipient renamed
@@ -392,7 +424,7 @@ class RepairUnmergedSharesTest extends TestCase {
]
],
[
- // #10 legitimate share:
+ // #12 legitimate share:
// - outsider shares with group and user directly with different permissions
// - no subshares
// - same targets
@@ -410,6 +442,42 @@ class RepairUnmergedSharesTest extends TestCase {
['/test (4)', 31],
]
],
+ [
+ // #13 bogus share:
+ // - outsider shares with group1, user2 and then group2
+ // - user renamed share as soon as it arrived before the next share (order)
+ // - one subshare for each group share
+ // - one extra share entry for direct share to user2
+ // - non-matching targets
+ [
+ // first share with group
+ [Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
+ // recipient renames
+ [DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/first', 31, 0],
+ // then direct share, user renames too
+ [Constants::SHARE_TYPE_USER, 123, 'user2', '/second', 31],
+ // another share with the second group
+ [Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 31],
+ // use renames it
+ [DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/third', 31, 1],
+ // different unrelated share
+ [Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (5)', 31],
+ ],
+ [
+ // group share with group1 left alone
+ ['/test', 31],
+ // first subshare repaired
+ ['/third', 31],
+ // direct user share repaired
+ ['/third', 31],
+ // group share with group2 left alone
+ ['/test', 31],
+ // second subshare repaired
+ ['/third', 31],
+ // leave unrelated alone
+ ['/test (5)', 31],
+ ]
+ ],
];
}
@@ -419,6 +487,38 @@ class RepairUnmergedSharesTest extends TestCase {
* @dataProvider sharesDataProvider
*/
public function testMergeGroupShares($shares, $expectedShares) {
+ $user1 = $this->getMock('\OCP\IUser');
+ $user1->expects($this->any())
+ ->method('getUID')
+ ->will($this->returnValue('user1'));
+
+ $user2 = $this->getMock('\OCP\IUser');
+ $user2->expects($this->any())
+ ->method('getUID')
+ ->will($this->returnValue('user2'));
+
+ $users = [$user1, $user2];
+
+ $this->groupManager->expects($this->any())
+ ->method('getUserGroupIds')
+ ->will($this->returnValueMap([
+ // owner
+ [$user1, ['samegroup1', 'samegroup2']],
+ // recipient
+ [$user2, ['recipientgroup1', 'recipientgroup2']],
+ ]));
+
+ $this->userManager->expects($this->once())
+ ->method('countUsers')
+ ->will($this->returnValue([2]));
+ $this->userManager->expects($this->once())
+ ->method('callForAllUsers')
+ ->will($this->returnCallback(function(\Closure $closure) use ($users) {
+ foreach ($users as $user) {
+ $closure($user);
+ }
+ }));
+
$shareIds = [];
foreach ($shares as $share) {
@@ -445,5 +545,30 @@ class RepairUnmergedSharesTest extends TestCase {
$this->assertEquals($expectedShare[1], $share['permissions']);
}
}
+
+ public function duplicateNamesProvider() {
+ return [
+ // matching
+ ['filename (1).txt', true],
+ ['folder (2)', true],
+ ['filename (1)(2).txt', true],
+ // non-matching
+ ['filename ().txt', false],
+ ['folder ()', false],
+ ['folder (1x)', false],
+ ['folder (x1)', false],
+ ['filename (a)', false],
+ ['filename (1).', false],
+ ['filename (1).txt.txt', false],
+ ['filename (1)..txt', false],
+ ];
+ }
+
+ /**
+ * @dataProvider duplicateNamesProvider
+ */
+ public function testIsPotentialDuplicateName($name, $expectedResult) {
+ $this->assertEquals($expectedResult, $this->invokePrivate($this->repair, 'isPotentialDuplicateName', [$name]));
+ }
}
diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php
index 379c7e39442..4b8067117b1 100644
--- a/tests/lib/User/SessionTest.php
+++ b/tests/lib/User/SessionTest.php
@@ -371,7 +371,7 @@ class SessionTest extends \Test\TestCase {
->with('token_auth_enforced', false)
->will($this->returnValue(true));
$request
- ->expects($this->exactly(2))
+ ->expects($this->any())
->method('getRemoteAddress')
->willReturn('192.168.0.1');
$this->throttler
@@ -379,7 +379,7 @@ class SessionTest extends \Test\TestCase {
->method('sleepDelay')
->with('192.168.0.1');
$this->throttler
- ->expects($this->once())
+ ->expects($this->any())
->method('getDelay')
->with('192.168.0.1')
->willReturn(0);
@@ -412,7 +412,7 @@ class SessionTest extends \Test\TestCase {
->method('set')
->with('app_password', 'I-AM-AN-APP-PASSWORD');
$request
- ->expects($this->exactly(2))
+ ->expects($this->any())
->method('getRemoteAddress')
->willReturn('192.168.0.1');
$this->throttler
@@ -420,7 +420,7 @@ class SessionTest extends \Test\TestCase {
->method('sleepDelay')
->with('192.168.0.1');
$this->throttler
- ->expects($this->once())
+ ->expects($this->any())
->method('getDelay')
->with('192.168.0.1')
->willReturn(0);
@@ -459,7 +459,7 @@ class SessionTest extends \Test\TestCase {
->will($this->returnValue(true));
$request
- ->expects($this->exactly(2))
+ ->expects($this->any())
->method('getRemoteAddress')
->willReturn('192.168.0.1');
$this->throttler
@@ -467,7 +467,7 @@ class SessionTest extends \Test\TestCase {
->method('sleepDelay')
->with('192.168.0.1');
$this->throttler
- ->expects($this->once())
+ ->expects($this->any())
->method('getDelay')
->with('192.168.0.1')
->willReturn(0);