From 13c84f66294034fe17f806d3db6ddc770c8158e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 23 Apr 2017 18:40:58 +0200 Subject: [PATCH 01/14] Add system to share data between acceptance test steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The data storage (the "notebook") is shared between all the actors, so the data can be stored and retrieved between different steps by any actor in the same scenario. Signed-off-by: Daniel Calviño Sánchez --- tests/acceptance/features/core/Actor.php | 22 ++++++++++++++++++- .../acceptance/features/core/ActorContext.php | 10 +++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/tests/acceptance/features/core/Actor.php b/tests/acceptance/features/core/Actor.php index a27e8e6a015..0c23b5f7a40 100644 --- a/tests/acceptance/features/core/Actor.php +++ b/tests/acceptance/features/core/Actor.php @@ -48,6 +48,10 @@ * before giving up without modifying the tests themselves. Note that the * multiplier affects the timeout, but not the timeout step; the rate at which * find() will try again to find the element does not change. + * + * All actors share a notebook in which data can be annotated. This makes + * possible to share data between different test steps, no matter which Actor + * performs them. */ class Actor { @@ -66,16 +70,23 @@ class Actor { */ private $findTimeoutMultiplier; + /** + * @var array + */ + private $sharedNotebook; + /** * Creates a new Actor. * * @param \Behat\Mink\Session $session the Mink Session used to control its * web browser. * @param string $baseUrl the base URL used when solving relative URLs. + * @param array $sharedNotebook the notebook shared between all actors. */ - public function __construct(\Behat\Mink\Session $session, $baseUrl) { + public function __construct(\Behat\Mink\Session $session, $baseUrl, &$sharedNotebook) { $this->session = $session; $this->baseUrl = $baseUrl; + $this->sharedNotebook = &$sharedNotebook; $this->findTimeoutMultiplier = 1; } @@ -221,4 +232,13 @@ class Actor { return $ancestorElement; } + /** + * Returns the shared notebook of the Actors. + * + * @return array the shared notebook of the Actors. + */ + public function &getSharedNotebook() { + return $this->sharedNotebook; + } + } diff --git a/tests/acceptance/features/core/ActorContext.php b/tests/acceptance/features/core/ActorContext.php index 9667ef2f01c..86fe3832f66 100644 --- a/tests/acceptance/features/core/ActorContext.php +++ b/tests/acceptance/features/core/ActorContext.php @@ -53,6 +53,11 @@ class ActorContext extends RawMinkContext { */ private $actors; + /** + * @var array + */ + private $sharedNotebook; + /** * @var Actor */ @@ -102,8 +107,9 @@ class ActorContext extends RawMinkContext { */ public function initializeActors() { $this->actors = array(); + $this->sharedNotebook = array(); - $this->actors["default"] = new Actor($this->getSession(), $this->getMinkParameter("base_url")); + $this->actors["default"] = new Actor($this->getSession(), $this->getMinkParameter("base_url"), $this->sharedNotebook); $this->actors["default"]->setFindTimeoutMultiplier($this->actorFindTimeoutMultiplier); $this->currentActor = $this->actors["default"]; @@ -127,7 +133,7 @@ class ActorContext extends RawMinkContext { */ public function iActAs($actorName) { if (!array_key_exists($actorName, $this->actors)) { - $this->actors[$actorName] = new Actor($this->getSession($actorName), $this->getMinkParameter("base_url")); + $this->actors[$actorName] = new Actor($this->getSession($actorName), $this->getMinkParameter("base_url"), $this->sharedNotebook); $this->actors[$actorName]->setFindTimeoutMultiplier($this->actorFindTimeoutMultiplier); } From 316710bcb1b612f7c8d114ba8a05c501fab7ba55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sat, 22 Apr 2017 18:20:32 +0200 Subject: [PATCH 02/14] Add acceptance tests for sharing password protected links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .drone.yml | 9 ++ tests/acceptance/config/behat.yml | 1 + tests/acceptance/features/app-files.feature | 31 ++++ .../features/bootstrap/FilesAppContext.php | 138 ++++++++++++++++++ .../bootstrap/FilesSharingAppContext.php | 110 ++++++++++++++ 5 files changed, 289 insertions(+) create mode 100644 tests/acceptance/features/app-files.feature create mode 100644 tests/acceptance/features/bootstrap/FilesSharingAppContext.php diff --git a/.drone.yml b/.drone.yml index 9b6a01bd4f0..4f2e14820de 100644 --- a/.drone.yml +++ b/.drone.yml @@ -485,6 +485,13 @@ pipeline: when: matrix: TESTS-ACCEPTANCE: access-levels + acceptance-app-files: + image: nextcloudci/php7.0:php7.0-7 + commands: + - tests/acceptance/run-local.sh allow-git-repository-modifications features/app-files.feature + when: + matrix: + TESTS-ACCEPTANCE: app-files acceptance-login: image: nextcloudci/php7.0:php7.0-7 commands: @@ -567,6 +574,8 @@ matrix: - TESTS: integration-trashbin - TESTS: acceptance TESTS-ACCEPTANCE: access-levels + - TESTS: acceptance + TESTS-ACCEPTANCE: app-files - TESTS: acceptance TESTS-ACCEPTANCE: login - TESTS: jsunit diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index 6c3d9e4a7b9..15310e6883f 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -11,6 +11,7 @@ default: - FeatureContext - FilesAppContext + - FilesSharingAppContext - LoginPageContext - NotificationContext - SettingsMenuContext diff --git a/tests/acceptance/features/app-files.feature b/tests/acceptance/features/app-files.feature new file mode 100644 index 00000000000..7adc618e02e --- /dev/null +++ b/tests/acceptance/features/app-files.feature @@ -0,0 +1,31 @@ +Feature: app-files + + Scenario: set a password to a shared link + Given I am logged in + And I share the link for "welcome.txt" + When I protect the shared link with the password "abcdef" + Then I see that the working icon for password protect is shown + And I see that the working icon for password protect is eventually not shown + + Scenario: access a shared link protected by password with a valid password + Given I act as John + And I am logged in + And I share the link for "welcome.txt" protected by the password "abcdef" + And I write down the shared link + When I act as Jane + And I visit the shared link I wrote down + And I see that the current page is the Authenticate page for the shared link I wrote down + And I authenticate with password "abcdef" + Then I see that the current page is the shared link I wrote down + And I see that the shared file preview shows the text "Welcome to your Nextcloud account!" + + Scenario: access a shared link protected by password with an invalid password + Given I act as John + And I am logged in + And I share the link for "welcome.txt" protected by the password "abcdef" + And I write down the shared link + When I act as Jane + And I visit the shared link I wrote down + And I authenticate with password "fedcba" + Then I see that the current page is the Authenticate page for the shared link I wrote down + And I see that a wrong password for the shared file message is shown diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 9702e64b552..7e7f592a44e 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -27,6 +27,105 @@ class FilesAppContext implements Context, ActorAwareInterface { use ActorAware; + /** + * @return Locator + */ + public static function currentSectionMainView() { + return Locator::forThe()->xpath("//*[starts-with(@id, 'app-content-') and not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]")-> + describedAs("Current section main view in Files app"); + } + + /** + * @return Locator + */ + public static function currentSectionDetailsView() { + return Locator::forThe()->xpath("/preceding-sibling::*[position() = 1 and @id = 'app-sidebar']")-> + descendantOf(self::currentSectionMainView())-> + describedAs("Current section details view in Files app"); + } + + /** + * @return Locator + */ + public static function shareLinkCheckbox() { + return Locator::forThe()->content("Share link")->descendantOf(self::currentSectionDetailsView())-> + describedAs("Share link checkbox in the details view in Files app"); + } + + /** + * @return Locator + */ + public static function shareLinkField() { + return Locator::forThe()->css(".linkText")->descendantOf(self::currentSectionDetailsView())-> + describedAs("Share link field in the details view in Files app"); + } + + /** + * @return Locator + */ + public static function passwordProtectCheckbox() { + return Locator::forThe()->content("Password protect")->descendantOf(self::currentSectionDetailsView())-> + describedAs("Password protect checkbox in the details view in Files app"); + } + + /** + * @return Locator + */ + public static function passwordProtectField() { + return Locator::forThe()->css(".linkPassText")->descendantOf(self::currentSectionDetailsView())-> + describedAs("Password protect field in the details view in Files app"); + } + + /** + * @return Locator + */ + public static function passwordProtectWorkingIcon() { + return Locator::forThe()->css(".linkPass .icon-loading-small")->descendantOf(self::currentSectionDetailsView())-> + describedAs("Password protect working icon in the details view in Files app"); + } + + /** + * @return Locator + */ + public static function rowForFile($fileName) { + return Locator::forThe()->xpath("//*[@id = 'fileList']//span[contains(concat(' ', normalize-space(@class), ' '), ' nametext ') and normalize-space() = '$fileName']/ancestor::tr")-> + descendantOf(self::currentSectionMainView())-> + describedAs("Row for file $fileName in Files app"); + } + + /** + * @return Locator + */ + public static function shareActionForFile($fileName) { + return Locator::forThe()->css(".action-share")->descendantOf(self::rowForFile($fileName))-> + describedAs("Share action for file $fileName in Files app"); + } + + /** + * @Given I share the link for :fileName + */ + public function iShareTheLinkFor($fileName) { + $this->actor->find(self::shareActionForFile($fileName), 10)->click(); + + $this->actor->find(self::shareLinkCheckbox(), 5)->click(); + } + + /** + * @Given I write down the shared link + */ + public function iWriteDownTheSharedLink() { + $this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::shareLinkField(), 10)->getValue(); + } + + /** + * @When I protect the shared link with the password :password + */ + public function iProtectTheSharedLinkWithThePassword($password) { + $this->actor->find(self::passwordProtectCheckbox(), 10)->click(); + + $this->actor->find(self::passwordProtectField(), 2)->setValue($password . "\r"); + } + /** * @Then I see that the current page is the Files app */ @@ -36,4 +135,43 @@ class FilesAppContext implements Context, ActorAwareInterface { $this->actor->getSession()->getCurrentUrl()); } + /** + * @Then I see that the working icon for password protect is shown + */ + public function iSeeThatTheWorkingIconForPasswordProtectIsShown() { + PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::passwordProtectWorkingIcon(), 10)); + } + + /** + * @Then I see that the working icon for password protect is eventually not shown + */ + public function iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown() { + $timeout = 10; + $timeoutStep = 1; + + $actor = $this->actor; + $passwordProtectWorkingIcon = self::passwordProtectWorkingIcon(); + + $workingIconNotFoundCallback = function() use ($actor, $passwordProtectWorkingIcon) { + try { + return !$actor->find($passwordProtectWorkingIcon)->isVisible(); + } catch (NoSuchElementException $exception) { + return true; + } + }; + if (!Utils::waitFor($workingIconNotFoundCallback, $timeout, $timeoutStep)) { + PHPUnit_Framework_Assert::fail("The working icon for password protect is still shown after $timeout seconds"); + } + } + + /** + * @Given I share the link for :fileName protected by the password :password + */ + public function iShareTheLinkForProtectedByThePassword($fileName, $password) { + $this->iShareTheLinkFor($fileName); + $this->iProtectTheSharedLinkWithThePassword($password); + $this->iSeeThatTheWorkingIconForPasswordProtectIsShown(); + $this->iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown(); + } + } diff --git a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php new file mode 100644 index 00000000000..d9d5eca7359 --- /dev/null +++ b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php @@ -0,0 +1,110 @@ +. + * + */ + +use Behat\Behat\Context\Context; + +class FilesSharingAppContext implements Context, ActorAwareInterface { + + use ActorAware; + + /** + * @return Locator + */ + public static function passwordField() { + return Locator::forThe()->field("password")-> + describedAs("Password field in Authenticate page"); + } + + /** + * @return Locator + */ + public static function authenticateButton() { + return Locator::forThe()->id("password-submit")-> + describedAs("Authenticate button in Authenticate page"); + } + + /** + * @return Locator + */ + public static function wrongPasswordMessage() { + return Locator::forThe()->content("The password is wrong. Try again.")-> + describedAs("Wrong password message in Authenticate page"); + } + + /** + * @return Locator + */ + public static function textPreview() { + return Locator::forThe()->css(".text-preview")-> + describedAs("Text preview in Shared file page"); + } + + /** + * @When I visit the shared link I wrote down + */ + public function iVisitTheSharedLinkIWroteDown() { + $this->actor->getSession()->visit($this->actor->getSharedNotebook()["shared link"]); + } + + /** + * @When I authenticate with password :password + */ + public function iAuthenticateWithPassword($password) { + $this->actor->find(self::passwordField(), 10)->setValue($password); + $this->actor->find(self::authenticateButton())->click(); + } + + /** + * @Then I see that the current page is the Authenticate page for the shared link I wrote down + */ + public function iSeeThatTheCurrentPageIsTheAuthenticatePageForTheSharedLinkIWroteDown() { + PHPUnit_Framework_Assert::assertEquals( + $this->actor->getSharedNotebook()["shared link"] . "/authenticate", + $this->actor->getSession()->getCurrentUrl()); + } + + /** + * @Then I see that the current page is the shared link I wrote down + */ + public function iSeeThatTheCurrentPageIsTheSharedLinkIWroteDown() { + PHPUnit_Framework_Assert::assertEquals( + $this->actor->getSharedNotebook()["shared link"], + $this->actor->getSession()->getCurrentUrl()); + } + + /** + * @Then I see that a wrong password for the shared file message is shown + */ + public function iSeeThatAWrongPasswordForTheSharedFileMessageIsShown() { + PHPUnit_Framework_Assert::assertTrue( + $this->actor->find(self::wrongPasswordMessage(), 10)->isVisible()); + } + + /** + * @Then I see that the shared file preview shows the text :text + */ + public function iSeeThatTheSharedFilePreviewShowsTheText($text) { + PHPUnit_Framework_Assert::assertContains($text, $this->actor->find(self::textPreview(), 10)->getText()); + } + +} From 153d053ee7872fd6669ff082a8c651c5d389a5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 23 Apr 2017 18:54:33 +0200 Subject: [PATCH 03/14] Fix working icon not hidden when successfully setting a password MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a request to set the password of a shared link is sent a working icon is shown. However, as there was no "success" callback, the icon was never hidden again after successfully setting the password (it worked fine if there was an error, though). Signed-off-by: Daniel Calviño Sánchez --- core/js/sharedialoglinkshareview.js | 3 + .../tests/specs/sharedialoglinkshareview.js | 142 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 core/js/tests/specs/sharedialoglinkshareview.js diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index 6017714b305..e7371d98f4a 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -307,6 +307,9 @@ this.model.saveLinkShare({ password: password }, { + success: function(model) { + $loading.removeClass('inlineblock').addClass('hidden'); + }, error: function(model, msg) { // destroy old tooltips $input.tooltip('destroy'); diff --git a/core/js/tests/specs/sharedialoglinkshareview.js b/core/js/tests/specs/sharedialoglinkshareview.js new file mode 100644 index 00000000000..b7144d31942 --- /dev/null +++ b/core/js/tests/specs/sharedialoglinkshareview.js @@ -0,0 +1,142 @@ +/** + * + * @copyright Copyright (c) 2015, Tom Needham (tom@owncloud.com) + * @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com) + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +describe('OC.Share.ShareDialogLinkShareView', function () { + + var configModel; + var shareModel; + var view; + + beforeEach(function () { + + var fileInfoModel = new OCA.Files.FileInfoModel({ + id: 123, + name: 'shared_file_name.txt', + path: '/subdir', + size: 100, + mimetype: 'text/plain', + permissions: OC.PERMISSION_ALL, + sharePermissions: OC.PERMISSION_ALL + }); + + var attributes = { + itemType: fileInfoModel.isDirectory() ? 'folder' : 'file', + itemSource: fileInfoModel.get('id'), + possiblePermissions: OC.PERMISSION_ALL, + permissions: OC.PERMISSION_ALL + }; + + configModel = new OC.Share.ShareConfigModel({ + enforcePasswordForPublicLink: false, + isResharingAllowed: true, + enforcePasswordForPublicLink: false, + isDefaultExpireDateEnabled: false, + isDefaultExpireDateEnforced: false, + defaultExpireDate: 7 + }); + + sinon.stub(configModel, 'isShareWithLinkAllowed'); + + shareModel = new OC.Share.ShareItemModel(attributes, { + configModel: configModel, + fileInfoModel: fileInfoModel + }); + + view = new OC.Share.ShareDialogLinkShareView({ + configModel: configModel, + model: shareModel + }); + + }); + + afterEach(function () { + view.remove(); + configModel.isShareWithLinkAllowed.restore(); + }); + + describe('onPasswordEntered', function () { + + var $passwordText; + var $workingIcon; + + beforeEach(function () { + + // Needed to render the view + configModel.isShareWithLinkAllowed.returns(true); + + // Setting the share also triggers the rendering + shareModel.set({ + linkShare: { + isLinkShare: true, + password: 'password' + } + }); + + var $passwordDiv = view.$el.find('#linkPass'); + $passwordText = view.$el.find('.linkPassText'); + $workingIcon = view.$el.find('.linkPass .icon-loading-small'); + + sinon.stub(shareModel, 'saveLinkShare'); + + expect($passwordDiv.hasClass('hidden')).toBeFalsy(); + expect($passwordText.hasClass('hidden')).toBeFalsy(); + expect($workingIcon.hasClass('hidden')).toBeTruthy(); + + $passwordText.val('myPassword'); + }); + + afterEach(function () { + shareModel.saveLinkShare.restore(); + }); + + it('shows the working icon when called', function () { + view.onPasswordEntered(); + + expect($workingIcon.hasClass('hidden')).toBeFalsy(); + expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy(); + }); + + it('hides the working icon when saving the password succeeds', function () { + view.onPasswordEntered(); + + expect($workingIcon.hasClass('hidden')).toBeFalsy(); + expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy(); + + shareModel.saveLinkShare.yieldTo("success", [shareModel]); + + expect($workingIcon.hasClass('hidden')).toBeTruthy(); + }); + + it('hides the working icon when saving the password fails', function () { + view.onPasswordEntered(); + + expect($workingIcon.hasClass('hidden')).toBeFalsy(); + expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy(); + + shareModel.saveLinkShare.yieldTo("error", [shareModel, "The error message"]); + + expect($workingIcon.hasClass('hidden')).toBeTruthy(); + }); + + }); + +}); From dcc8cce28b3c47dd7f6c1684fdb0793102164fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 23 Apr 2017 19:04:06 +0200 Subject: [PATCH 04/14] Fix double hashing of shared link passwords MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plain text password for a shared links was hashed and, then, the hashed password was hashed again and set as the final password. Due to this the password introduced in the "Authenticate" page for the shared link was always a wrong password, and thus the file could not be accessed. Signed-off-by: Daniel Calviño Sánchez --- lib/private/Share20/Manager.php | 2 +- tests/lib/Share20/ManagerTest.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 3afd38c579f..7bed012fe8f 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -734,7 +734,7 @@ class Manager implements IManager { } $plainTextPassword = null; - if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK || $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { + if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { // Password updated. if ($share->getPassword() !== $originalShare->getPassword()) { //Verify the password diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php index 7de73421d3e..6a389fcdf9a 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -2520,6 +2520,12 @@ class ManagerTest extends \Test\TestCase { $manager->expects($this->once())->method('canShare')->willReturn(true); $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare); $manager->expects($this->once())->method('validateExpirationDate')->with($share); + $manager->expects($this->once())->method('verifyPassword')->with('password'); + + $this->hasher->expects($this->once()) + ->method('hash') + ->with('password') + ->willReturn('hashed'); $this->defaultProvider->expects($this->once()) ->method('update') From de6b05a911118583178830a33f19b9010c480398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 23 Apr 2017 19:10:00 +0200 Subject: [PATCH 05/14] Add missing hook check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- tests/lib/Share20/ManagerTest.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php index 6a389fcdf9a..d8a637ef021 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -2510,6 +2510,7 @@ class ManagerTest extends \Test\TestCase { $share->setProviderId('foo') ->setId('42') ->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setToken('token') ->setSharedBy('owner') ->setShareOwner('owner') ->setPassword('password') @@ -2542,8 +2543,18 @@ class ManagerTest extends \Test\TestCase { ]); $hookListner2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); - \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListner2, 'post'); - $hookListner2->expects($this->never())->method('post'); + \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListner2, 'post'); + $hookListner2->expects($this->once())->method('post')->with([ + 'itemType' => 'file', + 'itemSource' => 100, + 'uidOwner' => 'owner', + 'token' => 'token', + 'disabled' => false, + ]); + + $hookListner3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); + \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListner3, 'post'); + $hookListner3->expects($this->never())->method('post'); $manager->updateShare($share); From a56fb75e690bffcc0c2d35c6748a5778721fe369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 23 Apr 2017 19:11:06 +0200 Subject: [PATCH 06/14] Add missing unit test for updateShare with email share MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- tests/lib/Share20/ManagerTest.php | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php index d8a637ef021..13556285b61 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -2560,6 +2560,81 @@ class ManagerTest extends \Test\TestCase { $manager->updateShare($share); } + public function testUpdateShareMail() { + $manager = $this->createManagerMock() + ->setMethods([ + 'canShare', + 'getShareById', + 'generalCreateChecks', + 'verifyPassword', + 'pathCreateChecks', + 'linkCreateChecks', + 'validateExpirationDate', + ]) + ->getMock(); + + $originalShare = $this->manager->newShare(); + $originalShare->setShareType(\OCP\Share::SHARE_TYPE_EMAIL) + ->setPermissions(\OCP\Constants::PERMISSION_ALL); + + $tomorrow = new \DateTime(); + $tomorrow->setTime(0,0,0); + $tomorrow->add(new \DateInterval('P1D')); + + $file = $this->createMock(File::class); + $file->method('getId')->willReturn(100); + + $share = $this->manager->newShare(); + $share->setProviderId('foo') + ->setId('42') + ->setShareType(\OCP\Share::SHARE_TYPE_EMAIL) + ->setToken('token') + ->setSharedBy('owner') + ->setShareOwner('owner') + ->setPassword('password') + ->setExpirationDate($tomorrow) + ->setNode($file) + ->setPermissions(\OCP\Constants::PERMISSION_ALL); + + $manager->expects($this->once())->method('canShare')->willReturn(true); + $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare); + $manager->expects($this->once())->method('generalCreateChecks')->with($share); + $manager->expects($this->once())->method('verifyPassword')->with('password'); + $manager->expects($this->once())->method('pathCreateChecks')->with($file); + $manager->expects($this->never())->method('linkCreateChecks'); + $manager->expects($this->never())->method('validateExpirationDate'); + + $this->hasher->expects($this->once()) + ->method('hash') + ->with('password') + ->willReturn('hashed'); + + $this->defaultProvider->expects($this->once()) + ->method('update') + ->with($share, 'password') + ->willReturn($share); + + $hookListner = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); + \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListner, 'post'); + $hookListner->expects($this->never())->method('post'); + + $hookListner2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); + \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListner2, 'post'); + $hookListner2->expects($this->once())->method('post')->with([ + 'itemType' => 'file', + 'itemSource' => 100, + 'uidOwner' => 'owner', + 'token' => 'token', + 'disabled' => false, + ]); + + $hookListner3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); + \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListner3, 'post'); + $hookListner3->expects($this->never())->method('post'); + + $manager->updateShare($share); + } + /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Can't change target of link share From 51e658da2aeab432858146ad5f8f09d8e2d6c850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 23 Apr 2017 19:32:15 +0200 Subject: [PATCH 07/14] Join if block to preceding if chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If getShareType() returns "email" it can not also return "user", "group" nor "link", so the if block can be added to the preceding if chain. Signed-off-by: Daniel Calviño Sánchez --- lib/private/Share20/Manager.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 7bed012fe8f..3e4d5cc3813 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -731,10 +731,9 @@ class Manager implements IManager { $this->validateExpirationDate($share); $expirationDateUpdated = true; } - } + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { + $plainTextPassword = null; - $plainTextPassword = null; - if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { // Password updated. if ($share->getPassword() !== $originalShare->getPassword()) { //Verify the password From faea890b874857f2ff144c7eb26dec58a3661005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sun, 23 Apr 2017 19:47:28 +0200 Subject: [PATCH 08/14] Extract updateSharePasswordIfNeeded function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- lib/private/Share20/Manager.php | 52 +++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 3e4d5cc3813..1407d89d112 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -715,16 +715,7 @@ class Manager implements IManager { } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { $this->linkCreateChecks($share); - // Password updated. - if ($share->getPassword() !== $originalShare->getPassword()) { - //Verify the password - $this->verifyPassword($share->getPassword()); - - // If a password is set. Hash it! - if ($share->getPassword() !== null) { - $share->setPassword($this->hasher->hash($share->getPassword())); - } - } + $this->updateSharePasswordIfNeeded($share, $originalShare); if ($share->getExpirationDate() != $originalShare->getExpirationDate()) { //Verify the expiration date @@ -732,18 +723,9 @@ class Manager implements IManager { $expirationDateUpdated = true; } } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { - $plainTextPassword = null; - - // Password updated. - if ($share->getPassword() !== $originalShare->getPassword()) { - //Verify the password - $this->verifyPassword($share->getPassword()); - - // If a password is set. Hash it! - if ($share->getPassword() !== null) { - $plainTextPassword = $share->getPassword(); - $share->setPassword($this->hasher->hash($plainTextPassword)); - } + $plainTextPassword = $share->getPassword(); + if (!$this->updateSharePasswordIfNeeded($share, $originalShare)) { + $plainTextPassword = null; } } @@ -796,6 +778,32 @@ class Manager implements IManager { return $share; } + /** + * Updates the password of the given share if it is not the same as the + * password of the original share. + * + * @param \OCP\Share\IShare $share the share to update its password. + * @param \OCP\Share\IShare $originalShare the original share to compare its + * password with. + * @return boolean whether the password was updated or not. + */ + private function updateSharePasswordIfNeeded(\OCP\Share\IShare $share, \OCP\Share\IShare $originalShare) { + // Password updated. + if ($share->getPassword() !== $originalShare->getPassword()) { + //Verify the password + $this->verifyPassword($share->getPassword()); + + // If a password is set. Hash it! + if ($share->getPassword() !== null) { + $share->setPassword($this->hasher->hash($share->getPassword())); + + return true; + } + } + + return false; + } + /** * Delete all the children of this share * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in From 726c6c73f4f89ba280a3232f60b7ace121c0056b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 24 Apr 2017 21:16:28 +0200 Subject: [PATCH 09/14] Add missing unit test cases and conditions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- core/js/tests/specs/shareitemmodelSpec.js | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index 3d3baf75d15..9305b56b711 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -670,6 +670,30 @@ describe('OC.Share.ShareItemModel', function() { shareWith: 'group1' }); }); + it('calls success handler after refreshing the model', function() { + var successStub = sinon.stub(); + model.addShare({ + shareType: OC.Share.SHARE_TYPE_GROUP, + shareWith: 'group1' + }, { + success: successStub + }); + + expect(fakeServer.requests.length).toEqual(1); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ }) + ); + + expect(successStub.called).toEqual(false); + + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([])); + + expect(successStub.calledOnce).toEqual(true); + expect(successStub.lastCall.args[0]).toEqual(model); + }); it('calls error handler with error message', function() { var errorStub = sinon.stub(); model.addShare({ @@ -693,6 +717,7 @@ describe('OC.Share.ShareItemModel', function() { ); expect(errorStub.calledOnce).toEqual(true); + expect(errorStub.lastCall.args[0]).toEqual(model); expect(errorStub.lastCall.args[1]).toEqual('Some error message'); }); }); @@ -712,6 +737,29 @@ describe('OC.Share.ShareItemModel', function() { permissions: '' + (OC.PERMISSION_READ | OC.PERMISSION_SHARE) }); }); + it('calls success handler after refreshing the model', function() { + var successStub = sinon.stub(); + model.updateShare(123, { + permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE + }, { + success: successStub + }); + + expect(fakeServer.requests.length).toEqual(1); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ }) + ); + + expect(successStub.called).toEqual(false); + + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([])); + + expect(successStub.calledOnce).toEqual(true); + expect(successStub.lastCall.args[0]).toEqual(model); + }); it('calls error handler with error message', function() { var errorStub = sinon.stub(); model.updateShare(123, { @@ -734,6 +782,7 @@ describe('OC.Share.ShareItemModel', function() { ); expect(errorStub.calledOnce).toEqual(true); + expect(errorStub.lastCall.args[0]).toEqual(model); expect(errorStub.lastCall.args[1]).toEqual('Some error message'); }); }); From 488020cf2efaa84f80bf5d39a0065b602b6d0a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 24 Apr 2017 21:18:29 +0200 Subject: [PATCH 10/14] Add "complete" callback support for updateShare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- core/js/shareitemmodel.js | 4 ++ core/js/tests/specs/shareitemmodelSpec.js | 51 +++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index bff006f7ef3..c2f7c78f6ce 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -208,6 +208,10 @@ url: this._getUrl('shares/' + encodeURIComponent(shareId)), data: attrs, dataType: 'json' + }).always(function() { + if (_.isFunction(options.complete)) { + options.complete(self); + } }).done(function() { self.fetch({ success: function() { diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index 9305b56b711..5c5aa9608b2 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -737,6 +737,29 @@ describe('OC.Share.ShareItemModel', function() { permissions: '' + (OC.PERMISSION_READ | OC.PERMISSION_SHARE) }); }); + it('calls complete handler before refreshing the model', function() { + var completeStub = sinon.stub(); + model.updateShare(123, { + permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE + }, { + complete: completeStub + }); + + expect(fakeServer.requests.length).toEqual(1); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ }) + ); + + expect(completeStub.calledOnce).toEqual(true); + expect(completeStub.lastCall.args[0]).toEqual(model); + + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([])); + + expect(completeStub.calledOnce).toEqual(true); + }); it('calls success handler after refreshing the model', function() { var successStub = sinon.stub(); model.updateShare(123, { @@ -760,6 +783,34 @@ describe('OC.Share.ShareItemModel', function() { expect(successStub.calledOnce).toEqual(true); expect(successStub.lastCall.args[0]).toEqual(model); }); + it('calls complete handler before error handler', function() { + var completeStub = sinon.stub(); + var errorStub = sinon.stub(); + model.updateShare(123, { + permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE + }, { + complete: completeStub, + error: errorStub + }); + + expect(fakeServer.requests.length).toEqual(1); + fakeServer.requests[0].respond( + 400, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + ocs: { + meta: { + message: 'Some error message' + } + } + }) + ); + + expect(completeStub.calledOnce).toEqual(true); + expect(completeStub.lastCall.args[0]).toEqual(model); + expect(errorStub.calledOnce).toEqual(true); + expect(completeStub.calledBefore(errorStub)).toEqual(true); + }); it('calls error handler with error message', function() { var errorStub = sinon.stub(); model.updateShare(123, { From 6e9f49f3974c0e77c2d556004723b6192b3036a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 24 Apr 2017 21:31:53 +0200 Subject: [PATCH 11/14] Add "complete" callback support for addShare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- core/js/shareitemmodel.js | 4 ++ core/js/tests/specs/shareitemmodelSpec.js | 53 +++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index c2f7c78f6ce..a11e785adc7 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -179,6 +179,10 @@ url: this._getUrl('shares'), data: attributes, dataType: 'json' + }).always(function() { + if (_.isFunction(options.complete)) { + options.complete(self); + } }).done(function() { self.fetch().done(function() { if (_.isFunction(options.success)) { diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index 5c5aa9608b2..771a9263709 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -670,6 +670,30 @@ describe('OC.Share.ShareItemModel', function() { shareWith: 'group1' }); }); + it('calls complete handler before refreshing the model', function() { + var completeStub = sinon.stub(); + model.addShare({ + shareType: OC.Share.SHARE_TYPE_GROUP, + shareWith: 'group1' + }, { + complete: completeStub + }); + + expect(fakeServer.requests.length).toEqual(1); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ }) + ); + + expect(completeStub.calledOnce).toEqual(true); + expect(completeStub.lastCall.args[0]).toEqual(model); + + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([])); + + expect(completeStub.calledOnce).toEqual(true); + }); it('calls success handler after refreshing the model', function() { var successStub = sinon.stub(); model.addShare({ @@ -694,6 +718,35 @@ describe('OC.Share.ShareItemModel', function() { expect(successStub.calledOnce).toEqual(true); expect(successStub.lastCall.args[0]).toEqual(model); }); + it('calls complete handler before error handler', function() { + var completeStub = sinon.stub(); + var errorStub = sinon.stub(); + model.addShare({ + shareType: OC.Share.SHARE_TYPE_GROUP, + shareWith: 'group1' + }, { + complete: completeStub, + error: errorStub + }); + + expect(fakeServer.requests.length).toEqual(1); + fakeServer.requests[0].respond( + 400, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + ocs: { + meta: { + message: 'Some error message' + } + } + }) + ); + + expect(completeStub.calledOnce).toEqual(true); + expect(completeStub.lastCall.args[0]).toEqual(model); + expect(errorStub.calledOnce).toEqual(true); + expect(completeStub.calledBefore(errorStub)).toEqual(true); + }); it('calls error handler with error message', function() { var errorStub = sinon.stub(); model.addShare({ From 3ab295893086e40fe956ca918f0dcb333dcec683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 24 Apr 2017 22:05:34 +0200 Subject: [PATCH 12/14] Document options parameter in saveLinkShare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- core/js/shareitemmodel.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index a11e785adc7..15af6df2c32 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -104,7 +104,14 @@ /** * Saves the current link share information. * - * This will trigger an ajax call and refetch the model afterwards. + * This will trigger an ajax call and, if successful, refetch the model + * afterwards. Callbacks "success", "error" and "complete" can be given + * in the options object; "success" is called after a successful save + * once the model is refetch, "error" is called after a failed save, and + * "complete" is called both after a successful save and after a failed + * save. Note that "complete" is called before "success" and "error" are + * called (unlike in jQuery, in which it is called after them); this + * ensures that "complete" is called even if refetching the model fails. * * TODO: this should be a separate model */ From e0b0115f99d8b93adfb9a9494aa1273f5e2f2200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 24 Apr 2017 22:07:08 +0200 Subject: [PATCH 13/14] Extract common ajax call for addShare and updateShare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- core/js/shareitemmodel.js | 53 +++++++++++++-------------------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index 15af6df2c32..8144d8faa21 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -156,7 +156,6 @@ addShare: function(attributes, options) { var shareType = attributes.shareType; - options = options || {}; attributes = _.extend({}, attributes); // Default permissions are Edit (CRUD) and Share @@ -180,61 +179,43 @@ attributes.path = this.fileInfoModel.getFullPath(); } - var self = this; - return $.ajax({ + return this._addOrUpdateShare({ type: 'POST', url: this._getUrl('shares'), data: attributes, dataType: 'json' - }).always(function() { - if (_.isFunction(options.complete)) { - options.complete(self); - } - }).done(function() { - self.fetch().done(function() { - if (_.isFunction(options.success)) { - options.success(self); - } - }); - }).fail(function(xhr) { - var msg = t('core', 'Error'); - var result = xhr.responseJSON; - if (result && result.ocs && result.ocs.meta) { - msg = result.ocs.meta.message; - } - - if (_.isFunction(options.error)) { - options.error(self, msg); - } else { - OC.dialogs.alert(msg, t('core', 'Error while sharing')); - } - }); + }, options); }, updateShare: function(shareId, attrs, options) { - var self = this; - options = options || {}; - return $.ajax({ + return this._addOrUpdateShare({ type: 'PUT', url: this._getUrl('shares/' + encodeURIComponent(shareId)), data: attrs, dataType: 'json' - }).always(function() { + }, options); + }, + + _addOrUpdateShare: function(ajaxSettings, options) { + var self = this; + options = options || {}; + + return $.ajax( + ajaxSettings + ).always(function() { if (_.isFunction(options.complete)) { options.complete(self); } }).done(function() { - self.fetch({ - success: function() { - if (_.isFunction(options.success)) { - options.success(self); - } + self.fetch().done(function() { + if (_.isFunction(options.success)) { + options.success(self); } }); }).fail(function(xhr) { var msg = t('core', 'Error'); var result = xhr.responseJSON; - if (result.ocs && result.ocs.meta) { + if (result && result.ocs && result.ocs.meta) { msg = result.ocs.meta.message; } From 58cc1251be33b43a5bb9163e1b042970b8e81b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 24 Apr 2017 22:18:52 +0200 Subject: [PATCH 14/14] Use "complete" callback in onPasswordEntered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- core/js/sharedialoglinkshareview.js | 3 +-- core/js/tests/specs/sharedialoglinkshareview.js | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index e7371d98f4a..0e317d8c921 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -307,13 +307,12 @@ this.model.saveLinkShare({ password: password }, { - success: function(model) { + complete: function(model) { $loading.removeClass('inlineblock').addClass('hidden'); }, error: function(model, msg) { // destroy old tooltips $input.tooltip('destroy'); - $loading.removeClass('inlineblock').addClass('hidden'); $input.addClass('error'); $input.attr('title', msg); $input.tooltip({placement: 'bottom', trigger: 'manual'}); diff --git a/core/js/tests/specs/sharedialoglinkshareview.js b/core/js/tests/specs/sharedialoglinkshareview.js index b7144d31942..811919b5603 100644 --- a/core/js/tests/specs/sharedialoglinkshareview.js +++ b/core/js/tests/specs/sharedialoglinkshareview.js @@ -121,7 +121,7 @@ describe('OC.Share.ShareDialogLinkShareView', function () { expect($workingIcon.hasClass('hidden')).toBeFalsy(); expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy(); - shareModel.saveLinkShare.yieldTo("success", [shareModel]); + shareModel.saveLinkShare.yieldTo("complete", [shareModel]); expect($workingIcon.hasClass('hidden')).toBeTruthy(); }); @@ -132,6 +132,7 @@ describe('OC.Share.ShareDialogLinkShareView', function () { expect($workingIcon.hasClass('hidden')).toBeFalsy(); expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy(); + shareModel.saveLinkShare.yieldTo("complete", [shareModel]); shareModel.saveLinkShare.yieldTo("error", [shareModel, "The error message"]); expect($workingIcon.hasClass('hidden')).toBeTruthy();