diff options
-rw-r--r-- | .drone.yml | 31 | ||||
-rw-r--r-- | apps/sharebymail/lib/ShareByMailProvider.php | 10 | ||||
-rw-r--r-- | apps/sharebymail/tests/ShareByMailProviderTest.php | 123 | ||||
-rw-r--r-- | build/integration/config/behat.yml | 1 | ||||
-rw-r--r-- | build/integration/features/bootstrap/BasicStructure.php | 1 | ||||
-rw-r--r-- | build/integration/features/bootstrap/FakeSMTPHelper.php | 159 | ||||
-rw-r--r-- | build/integration/features/bootstrap/Mail.php | 57 | ||||
-rw-r--r-- | build/integration/features/bootstrap/Sharing.php | 25 | ||||
-rw-r--r-- | build/integration/features/bootstrap/SharingContext.php | 2 | ||||
-rw-r--r-- | build/integration/features/bootstrap/TalkContext.php | 72 | ||||
-rw-r--r-- | build/integration/sharing_features/sharing-v1-video-verification.feature | 504 | ||||
-rw-r--r-- | build/integration/sharing_features/sharing-v1.feature | 92 | ||||
-rw-r--r-- | lib/private/Share20/Manager.php | 26 | ||||
-rw-r--r-- | tests/lib/Share20/ManagerTest.php | 253 |
14 files changed, 1326 insertions, 30 deletions
diff --git a/.drone.yml b/.drone.yml index e84918ef6a0..fbc9570b6e3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1361,6 +1361,37 @@ trigger: --- kind: pipeline +name: integration-sharing-v1-video-verification + +steps: +- name: submodules + image: docker:git + commands: + - git submodule update --init +- name: install-talk + image: docker:git + commands: + # JavaScript files are not used in integration tests so it is not needed to + # build them. + - git clone https://github.com/nextcloud/spreed apps/spreed +- name: integration-sharing-v1-video-verification + image: nextcloudci/integration-php7.3:integration-php7.3-2 + commands: + - bash tests/drone-run-integration-tests.sh || exit 0 + - ./occ maintenance:install --admin-pass=admin --data-dir=/dev/shm/nc_int + - cd build/integration + - ./run.sh sharing_features/sharing-v1-video-verification.feature + +trigger: + branch: + - master + - stable* + event: + - pull_request + - push + +--- +kind: pipeline name: integration-setup-features steps: diff --git a/apps/sharebymail/lib/ShareByMailProvider.php b/apps/sharebymail/lib/ShareByMailProvider.php index 4292ac9bf18..3a1a069172c 100644 --- a/apps/sharebymail/lib/ShareByMailProvider.php +++ b/apps/sharebymail/lib/ShareByMailProvider.php @@ -187,12 +187,16 @@ class ShareByMailProvider implements IShareProvider { // if the admin enforces a password for all mail shares we create a // random password and send it to the recipient - $password = ''; + $password = $share->getPassword() ?: ''; $passwordEnforced = $this->settingsManager->enforcePasswordProtection(); - if ($passwordEnforced) { + if ($passwordEnforced && empty($password)) { $password = $this->autoGeneratePassword($share); } + if (!empty($password)) { + $share->setPassword($this->hasher->hash($password)); + } + $shareId = $this->createMailShare($share); $send = $this->sendPassword($share, $password); if ($passwordEnforced && $send === false) { @@ -233,8 +237,6 @@ class ShareByMailProvider implements IShareProvider { $password = $this->secureRandom->generate($passwordLength, $passwordCharset); - $share->setPassword($this->hasher->hash($password)); - return $password; } diff --git a/apps/sharebymail/tests/ShareByMailProviderTest.php b/apps/sharebymail/tests/ShareByMailProviderTest.php index f77b3afefce..2dc8ff9c953 100644 --- a/apps/sharebymail/tests/ShareByMailProviderTest.php +++ b/apps/sharebymail/tests/ShareByMailProviderTest.php @@ -240,6 +240,51 @@ class ShareByMailProviderTest extends TestCase { ); } + public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtection() { + $share = $this->getMockBuilder(IShare::class)->getMock(); + $share->expects($this->any())->method('getSharedWith')->willReturn('receiver@example.com'); + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); + + $node = $this->getMockBuilder(File::class)->getMock(); + $node->expects($this->any())->method('getName')->willReturn('filename'); + + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity']); + + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); + $instance->expects($this->once())->method('createShareActivity')->with($share); + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn('rawShare'); + $instance->expects($this->once())->method('createShareObject')->with('rawShare')->willReturn('shareObject'); + $share->expects($this->any())->method('getNode')->willReturn($node); + + $share->expects($this->once())->method('getPassword')->willReturn('password'); + $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); + $share->expects($this->once())->method('setPassword')->with('passwordHashed'); + + // The given password (but not the autogenerated password) should be + // mailed to the receiver of the share. + $this->settingsManager->expects($this->any())->method('enforcePasswordProtection')->willReturn(false); + $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); + $instance->expects($this->never())->method('autoGeneratePassword'); + + $message = $this->createMock(IMessage::class); + $message->expects($this->once())->method('setTo')->with(['receiver@example.com']); + $this->mailer->expects($this->once())->method('createMessage')->willReturn($message); + $this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.RecipientPasswordNotification', [ + 'filename' => 'filename', + 'password' => 'password', + 'initiator' => 'owner', + 'initiatorEmail' => null, + 'shareWith' => 'receiver@example.com', + ]); + $this->mailer->expects($this->once())->method('send'); + + $this->assertSame('shareObject', + $instance->create($share) + ); + } + public function testCreateSendPasswordByMailWithEnforcedPasswordProtection() { $share = $this->getMockBuilder(IShare::class)->getMock(); $share->expects($this->any())->method('getSharedWith')->willReturn('receiver@example.com'); @@ -258,14 +303,70 @@ class ShareByMailProviderTest extends TestCase { $instance->expects($this->once())->method('createShareObject')->with('rawShare')->willReturn('shareObject'); $share->expects($this->any())->method('getNode')->willReturn($node); + $share->expects($this->once())->method('getPassword')->willReturn(null); + $this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed'); + $share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed'); + // The autogenerated password should be mailed to the receiver of the share. $this->settingsManager->expects($this->any())->method('enforcePasswordProtection')->willReturn(true); $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); - $instance->expects($this->once())->method('autoGeneratePassword')->with($share)->willReturn('password'); + $instance->expects($this->once())->method('autoGeneratePassword')->with($share)->willReturn('autogeneratedPassword'); $message = $this->createMock(IMessage::class); $message->expects($this->once())->method('setTo')->with(['receiver@example.com']); $this->mailer->expects($this->once())->method('createMessage')->willReturn($message); + $this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.RecipientPasswordNotification', [ + 'filename' => 'filename', + 'password' => 'autogeneratedPassword', + 'initiator' => 'owner', + 'initiatorEmail' => null, + 'shareWith' => 'receiver@example.com', + ]); + $this->mailer->expects($this->once())->method('send'); + + $this->assertSame('shareObject', + $instance->create($share) + ); + } + + public function testCreateSendPasswordByMailWithPasswordAndWithEnforcedPasswordProtection() { + $share = $this->getMockBuilder(IShare::class)->getMock(); + $share->expects($this->any())->method('getSharedWith')->willReturn('receiver@example.com'); + $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false); + $share->expects($this->any())->method('getSharedBy')->willReturn('owner'); + + $node = $this->getMockBuilder(File::class)->getMock(); + $node->expects($this->any())->method('getName')->willReturn('filename'); + + $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity']); + + $instance->expects($this->once())->method('getSharedWith')->willReturn([]); + $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42); + $instance->expects($this->once())->method('createShareActivity')->with($share); + $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn('rawShare'); + $instance->expects($this->once())->method('createShareObject')->with('rawShare')->willReturn('shareObject'); + $share->expects($this->any())->method('getNode')->willReturn($node); + + $share->expects($this->once())->method('getPassword')->willReturn('password'); + $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed'); + $share->expects($this->once())->method('setPassword')->with('passwordHashed'); + + // The given password (but not the autogenerated password) should be + // mailed to the receiver of the share. + $this->settingsManager->expects($this->any())->method('enforcePasswordProtection')->willReturn(true); + $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); + $instance->expects($this->never())->method('autoGeneratePassword'); + + $message = $this->createMock(IMessage::class); + $message->expects($this->once())->method('setTo')->with(['receiver@example.com']); + $this->mailer->expects($this->once())->method('createMessage')->willReturn($message); + $this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.RecipientPasswordNotification', [ + 'filename' => 'filename', + 'password' => 'password', + 'initiator' => 'owner', + 'initiatorEmail' => null, + 'shareWith' => 'receiver@example.com', + ]); $this->mailer->expects($this->once())->method('send'); $this->assertSame('shareObject', @@ -291,14 +392,25 @@ class ShareByMailProviderTest extends TestCase { $instance->expects($this->once())->method('createShareObject')->with('rawShare')->willReturn('shareObject'); $share->expects($this->any())->method('getNode')->willReturn($node); + $share->expects($this->once())->method('getPassword')->willReturn(null); + $this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed'); + $share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed'); + // The autogenerated password should be mailed to the owner of the share. $this->settingsManager->expects($this->any())->method('enforcePasswordProtection')->willReturn(true); $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true); - $instance->expects($this->once())->method('autoGeneratePassword')->with($share)->willReturn('password'); + $instance->expects($this->once())->method('autoGeneratePassword')->with($share)->willReturn('autogeneratedPassword'); $message = $this->createMock(IMessage::class); $message->expects($this->once())->method('setTo')->with(['owner@example.com' => 'Owner display name']); $this->mailer->expects($this->once())->method('createMessage')->willReturn($message); + $this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.OwnerPasswordNotification', [ + 'filename' => 'filename', + 'password' => 'autogeneratedPassword', + 'initiator' => 'Owner display name', + 'initiatorEmail' => 'owner@example.com', + 'shareWith' => 'receiver@example.com', + ]); $this->mailer->expects($this->once())->method('send'); $user = $this->createMock(IUser::class); @@ -527,6 +639,13 @@ class ShareByMailProviderTest extends TestCase { $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn($newSendPasswordByTalk); if ($sendMail) { + $this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.RecipientPasswordNotification', [ + 'filename' => 'filename', + 'password' => $plainTextPassword, + 'initiator' => null, + 'initiatorEmail' => null, + 'shareWith' => 'receiver@example.com', + ]); $this->mailer->expects($this->once())->method('send'); } else { $this->mailer->expects($this->never())->method('send'); diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml index 27d7daa4818..79ffe58f6b6 100644 --- a/build/integration/config/behat.yml +++ b/build/integration/config/behat.yml @@ -65,6 +65,7 @@ default: - admin - admin regular_user_password: 123456 + - TalkContext setup: paths: - "%paths.base%/../setup_features" diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php index 438a6418f07..eed0f173ced 100644 --- a/build/integration/features/bootstrap/BasicStructure.php +++ b/build/integration/features/bootstrap/BasicStructure.php @@ -45,6 +45,7 @@ require __DIR__ . '/../../vendor/autoload.php'; trait BasicStructure { use Auth; use Download; + use Mail; use Trashbin; /** @var string */ diff --git a/build/integration/features/bootstrap/FakeSMTPHelper.php b/build/integration/features/bootstrap/FakeSMTPHelper.php new file mode 100644 index 00000000000..9a64b6d31de --- /dev/null +++ b/build/integration/features/bootstrap/FakeSMTPHelper.php @@ -0,0 +1,159 @@ +<?php + +// Code below modified from https://github.com/axllent/fake-smtp/blob/f0856f8a0df6f4ca5a573cf31428c09ebc5b9ea3/fakeSMTP.php, +// which is under the MIT license (https://github.com/axllent/fake-smtp/blob/f0856f8a0df6f4ca5a573cf31428c09ebc5b9ea3/LICENSE) + +/** + * fakeSMTP - A PHP / inetd fake smtp server. + * Allows client<->server interaction + * The comunication is based upon the SMPT standards defined in http://www.lesnikowski.com/mail/Rfc/rfc2821.txt + */ + +class fakeSMTP { + public $logFile = false; + public $serverHello = 'fakeSMTP ESMTP PHP Mail Server Ready'; + + public function __construct($fd) { + $this->mail = []; + $this->mail['ipaddress'] = false; + $this->mail['emailSender'] = ''; + $this->mail['emailRecipients'] = []; + $this->mail['emailSubject'] = false; + $this->mail['rawEmail'] = false; + $this->mail['emailHeaders'] = false; + $this->mail['emailBody'] = false; + + $this->fd = $fd; + } + + public function receive() { + $hasValidFrom = false; + $hasValidTo = false; + $receivingData = false; + $header = true; + $this->reply('220 '.$this->serverHello); + $this->mail['ipaddress'] = $this->detectIP(); + while ($data = fgets($this->fd)) { + $data = preg_replace('@\r\n@', "\n", $data); + + if (!$receivingData) { + $this->log($data); + } + + if (!$receivingData && preg_match('/^MAIL FROM:\s?<(.*)>/i', $data, $match)) { + if (preg_match('/(.*)@\[.*\]/i', $match[1]) || $match[1] != '' || $this->validateEmail($match[1])) { + $this->mail['emailSender'] = $match[1]; + $this->reply('250 2.1.0 Ok'); + $hasValidFrom = true; + } else { + $this->reply('551 5.1.7 Bad sender address syntax'); + } + } elseif (!$receivingData && preg_match('/^RCPT TO:\s?<(.*)>/i', $data, $match)) { + if (!$hasValidFrom) { + $this->reply('503 5.5.1 Error: need MAIL command'); + } else { + if (preg_match('/postmaster@\[.*\]/i', $match[1]) || $this->validateEmail($match[1])) { + array_push($this->mail['emailRecipients'], $match[1]); + $this->reply('250 2.1.5 Ok'); + $hasValidTo = true; + } else { + $this->reply('501 5.1.3 Bad recipient address syntax '.$match[1]); + } + } + } elseif (!$receivingData && preg_match('/^RSET$/i', trim($data))) { + $this->reply('250 2.0.0 Ok'); + $hasValidFrom = false; + $hasValidTo = false; + } elseif (!$receivingData && preg_match('/^NOOP$/i', trim($data))) { + $this->reply('250 2.0.0 Ok'); + } elseif (!$receivingData && preg_match('/^VRFY (.*)/i', trim($data), $match)) { + $this->reply('250 2.0.0 '.$match[1]); + } elseif (!$receivingData && preg_match('/^DATA/i', trim($data))) { + if (!$hasValidTo) { + $this->reply('503 5.5.1 Error: need RCPT command'); + } else { + $this->reply('354 Ok Send data ending with <CRLF>.<CRLF>'); + $receivingData = true; + } + } elseif (!$receivingData && preg_match('/^(HELO|EHLO)/i', $data)) { + $this->reply('250 HELO '.$this->mail['ipaddress']); + } elseif (!$receivingData && preg_match('/^QUIT/i', trim($data))) { + break; + } elseif (!$receivingData) { + //~ $this->reply('250 Ok'); + $this->reply('502 5.5.2 Error: command not recognized'); + } elseif ($receivingData && $data == ".\n") { + /* Email Received, now let's look at it */ + $receivingData = false; + $this->reply('250 2.0.0 Ok: queued as '.$this->generateRandom(10)); + $splitmail = explode("\n\n", $this->mail['rawEmail'], 2); + if (count($splitmail) == 2) { + $this->mail['emailHeaders'] = $splitmail[0]; + $this->mail['emailBody'] = $splitmail[1]; + $headers = preg_replace("/ \s+/", ' ', preg_replace("/\n\s/", ' ', $this->mail['emailHeaders'])); + $headerlines = explode("\n", $headers); + for ($i=0; $i<count($headerlines); $i++) { + if (preg_match('/^Subject: (.*)/i', $headerlines[$i], $matches)) { + $this->mail['emailSubject'] = trim($matches[1]); + } + } + } else { + $this->mail['emailBody'] = $splitmail[0]; + } + set_time_limit(5); // Just run the exit to prevent open threads / abuse + } elseif ($receivingData) { + $this->mail['rawEmail'] .= $data; + } + } + /* Say good bye */ + $this->reply('221 2.0.0 Bye '.$this->mail['ipaddress']); + + fclose($this->fd); + } + + public function log($s) { + if ($this->logFile) { + file_put_contents($this->logFile, trim($s)."\n", FILE_APPEND); + } + } + + private function reply($s) { + $this->log("REPLY:$s"); + fwrite($this->fd, $s . "\r\n"); + } + + private function detectIP() { + $raw = explode(':', stream_socket_get_name($this->fd, true)); + return $raw[0]; + } + + private function validateEmail($email) { + return preg_match('/^[_a-z0-9-+]+(\.[_a-z0-9-+]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/', strtolower($email)); + } + + private function generateRandom($length=8) { + $password = ''; + $possible = '2346789BCDFGHJKLMNPQRTVWXYZ'; + $maxlength = strlen($possible); + $i = 0; + for ($i=0; $i < $length; $i++) { + $char = substr($possible, mt_rand(0, $maxlength-1), 1); + if (!strstr($password, $char)) { + $password .= $char; + } + } + return $password; + } +} + +$socket = stream_socket_server('tcp://127.0.0.1:2525', $errno, $errstr); +if (!$socket) { + exit(); +} + +while ($fd = stream_socket_accept($socket)) { + $fakeSMTP = new fakeSMTP($fd); + $fakeSMTP->receive(); +} + +fclose($socket); diff --git a/build/integration/features/bootstrap/Mail.php b/build/integration/features/bootstrap/Mail.php new file mode 100644 index 00000000000..d347636c8aa --- /dev/null +++ b/build/integration/features/bootstrap/Mail.php @@ -0,0 +1,57 @@ +<?php +/** + * @copyright Copyright (c) 2020, Daniel Calviño Sánchez (danxuliu@gmail.com) + * + * @author 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 <http://www.gnu.org/licenses/>. + * + */ + +trait Mail { + + // CommandLine trait is expected to be used in the class that uses this + // trait. + + /** + * @var string + */ + private $fakeSmtpServerPid; + + /** + * @AfterScenario + */ + public function killDummyMailServer() { + if (!$this->fakeSmtpServerPid) { + return; + } + + exec("kill " . $this->fakeSmtpServerPid); + + $this->invokingTheCommand('config:system:delete mail_smtpport'); + } + + /** + * @Given /^dummy mail server is listening$/ + */ + public function dummyMailServerIsListening() { + // Default smtpport (25) is restricted for regular users, so the + // FakeSMTP uses 2525 instead. + $this->invokingTheCommand('config:system:set mail_smtpport --value=2525 --type integer'); + + $this->fakeSmtpServerPid = exec("php features/bootstrap/FakeSMTPHelper.php >/dev/null 2>&1 & echo $!"); + } +} diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php index 22c9fef9f7f..4ce2e12e29d 100644 --- a/build/integration/features/bootstrap/Sharing.php +++ b/build/integration/features/bootstrap/Sharing.php @@ -151,11 +151,9 @@ trait Sharing { } /** - * @Then /^Public shared file "([^"]*)" can be downloaded$/ + * @Then /^last link share can be downloaded$/ */ - public function checkPublicSharedFile($filename) { - $client = new Client(); - $options = []; + public function lastLinkShareCanBeDownloaded() { if (count($this->lastShareData->data->element) > 0) { $url = $this->lastShareData->data[0]->url; } else { @@ -166,10 +164,23 @@ trait Sharing { } /** - * @Then /^Public shared file "([^"]*)" with password "([^"]*)" can be downloaded$/ + * @Then /^last share can be downloaded$/ */ - public function checkPublicSharedFileWithPassword($filename, $password) { - $options = []; + public function lastShareCanBeDownloaded() { + if (count($this->lastShareData->data->element) > 0) { + $token = $this->lastShareData->data[0]->token; + } else { + $token = $this->lastShareData->data->token; + } + + $fullUrl = substr($this->baseUrl, 0, -4) . "index.php/s/" . $token . "/download"; + $this->checkDownload($fullUrl, null, 'text/plain'); + } + + /** + * @Then /^last share with password "([^"]*)" can be downloaded$/ + */ + public function lastShareWithPasswordCanBeDownloaded($password) { if (count($this->lastShareData->data->element) > 0) { $token = $this->lastShareData->data[0]->token; } else { diff --git a/build/integration/features/bootstrap/SharingContext.php b/build/integration/features/bootstrap/SharingContext.php index 83ef9f96041..09b90dbf170 100644 --- a/build/integration/features/bootstrap/SharingContext.php +++ b/build/integration/features/bootstrap/SharingContext.php @@ -33,7 +33,9 @@ require __DIR__ . '/../../vendor/autoload.php'; class SharingContext implements Context, SnippetAcceptingContext { use Sharing; use AppConfiguration; + use CommandLine; protected function resetAppConfigs() { + $this->modifyServerConfig('sharebymail', 'enforcePasswordProtection', 'no'); } } diff --git a/build/integration/features/bootstrap/TalkContext.php b/build/integration/features/bootstrap/TalkContext.php new file mode 100644 index 00000000000..bc61c87ebab --- /dev/null +++ b/build/integration/features/bootstrap/TalkContext.php @@ -0,0 +1,72 @@ +<?php +/** + * @copyright Copyright (c) 2020, Daniel Calviño Sánchez (danxuliu@gmail.com) + * + * @author 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 <http://www.gnu.org/licenses/>. + * + */ + +use Behat\Behat\Context\Context; + +class TalkContext implements Context { + + /** + * @BeforeFeature @Talk + * @BeforeScenario @Talk + */ + public static function skipTestsIfTalkIsNotInstalled() { + if (!TalkContext::isTalkInstalled()) { + throw new Exception('Talk needs to be installed to run features or scenarios tagged with @Talk'); + } + } + + /** + * @AfterScenario @Talk + */ + public static function disableTalk() { + TalkContext::runOcc(['app:disable', 'spreed']); + } + + private static function isTalkInstalled(): bool { + $appList = TalkContext::runOcc(['app:list']); + + return strpos($appList, 'spreed') !== false; + } + + private static function runOcc(array $args): string { + // Based on "runOcc" from CommandLine trait (which can not be used due + // to not being static and being already used in other sibling + // contexts). + $args = array_map(function ($arg) { + return escapeshellarg($arg); + }, $args); + $args[] = '--no-ansi --no-warnings'; + $args = implode(' ', $args); + + $descriptor = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + $process = proc_open('php console.php ' . $args, $descriptor, $pipes, $ocPath = '../..'); + $lastStdOut = stream_get_contents($pipes[1]); + proc_close($process); + + return $lastStdOut; + } +} diff --git a/build/integration/sharing_features/sharing-v1-video-verification.feature b/build/integration/sharing_features/sharing-v1-video-verification.feature new file mode 100644 index 00000000000..e57ec9a9f98 --- /dev/null +++ b/build/integration/sharing_features/sharing-v1-video-verification.feature @@ -0,0 +1,504 @@ +@Talk +Feature: sharing + Background: + Given using api version "1" + Given using old dav path + Given invoking occ with "app:enable spreed" + + Scenario: Creating a link share with send password by Talk + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + | password | secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk with different password in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + | password | secret | + And Updating last share with + | password | another secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another secret" can be downloaded + + Scenario: Enabling send password by Talk with different password set after creation in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + And Updating last share with + | password | secret | + And Updating last share with + | password | another secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another secret" can be downloaded + + Scenario: Enabling send password by Talk with same password in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + | password | secret | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk with same password set after creation in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + And Updating last share with + | password | secret | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk without updating password in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + | password | secret | + And Updating last share with + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk without updating password set after creation in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + And Updating last share with + | password | secret | + And Updating last share with + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk with no password in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + And Updating last share with + | sendPasswordByTalk | true | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share can be downloaded + + Scenario: Enabling send password by Talk with no password removed after creation in a link share + Given user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + | password | secret | + And Updating last share with + | password | | + And Updating last share with + | sendPasswordByTalk | true | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share can be downloaded + + Scenario: Disabling send password by Talk without setting new password in a link share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | sendPasswordByTalk | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Disabling send password by Talk without setting new password set after creation in a link share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | sendPasswordByTalk | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Disabling send password by Talk setting same password in a link share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | password | secret | + | sendPasswordByTalk | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Disabling send password by Talk setting same password set after creation in a link share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | password | secret | + | sendPasswordByTalk | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Disabling send password by Talk setting new password in a link share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | password | another secret | + | sendPasswordByTalk | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another secret" can be downloaded + + Scenario: Disabling send password by Talk setting new password set after creation in a link share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 3 | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | password | another secret | + | sendPasswordByTalk | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another secret" can be downloaded + + + + + + Scenario: Creating a mail share with send password by Talk + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + | password | secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk with different password in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + | password | secret | + And Updating last share with + | password | another secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another secret" can be downloaded + + Scenario: Enabling send password by Talk with different password set after creation in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + And Updating last share with + | password | secret | + And Updating last share with + | password | another secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another secret" can be downloaded + + Scenario: Enabling send password by Talk with same password in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + | password | secret | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk with same password set after creation in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + And Updating last share with + | password | secret | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk without updating password in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + | password | secret | + And Updating last share with + | sendPasswordByTalk | true | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk without updating password set after creation in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + And Updating last share with + | password | secret | + And Updating last share with + | sendPasswordByTalk | true | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Enabling send password by Talk with no password in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + And Updating last share with + | sendPasswordByTalk | true | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share can be downloaded + + Scenario: Enabling send password by Talk with no password removed after creation in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + | password | secret | + And Updating last share with + | password | | + And Updating last share with + | sendPasswordByTalk | true | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share can be downloaded + + Scenario: Disabling send password by Talk without setting new password in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | sendPasswordByTalk | false | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Disabling send password by Talk without setting new password set after creation in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | sendPasswordByTalk | false | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Disabling send password by Talk setting same password in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | password | secret | + | sendPasswordByTalk | false | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Disabling send password by Talk setting same password set after creation in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | password | secret | + | sendPasswordByTalk | false | + Then the OCS status code should be "400" + And the HTTP status code should be "200" + And last share with password "secret" can be downloaded + + Scenario: Disabling send password by Talk setting new password in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | password | another secret | + | sendPasswordByTalk | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another secret" can be downloaded + + Scenario: Disabling send password by Talk setting new password set after creation in a mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dummy@test.com | + And Updating last share with + | password | secret | + | sendPasswordByTalk | true | + And Updating last share with + | password | another secret | + | sendPasswordByTalk | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another secret" can be downloaded diff --git a/build/integration/sharing_features/sharing-v1.feature b/build/integration/sharing_features/sharing-v1.feature index 51a627dc6de..764cd857a3e 100644 --- a/build/integration/sharing_features/sharing-v1.feature +++ b/build/integration/sharing_features/sharing-v1.feature @@ -56,6 +56,92 @@ Feature: sharing Then the OCS status code should be "403" And the HTTP status code should be "401" + Scenario: Creating a new mail share + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dumy@test.com | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share can be downloaded + + Scenario: Creating a new mail share with password + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dumy@test.com | + | password | publicpw | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "publicpw" can be downloaded + + Scenario: Creating a new mail share with password when password protection is enforced + Given dummy mail server is listening + And As an "admin" + And parameter "enforcePasswordProtection" of app "sharebymail" is set to "yes" + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dumy@test.com | + | password | publicpw | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "publicpw" can be downloaded + + Scenario: Creating a new mail share and setting a password + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dumy@test.com | + And Updating last share with + | password | publicpw | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "publicpw" can be downloaded + + Scenario: Creating a new mail share and setting a password twice + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dumy@test.com | + And Updating last share with + | password | publicpw | + And Updating last share with + | password | another publicpw | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "another publicpw" can be downloaded + + Scenario: Creating a new mail share and setting the same password twice + Given dummy mail server is listening + And user "user0" exists + And As an "user0" + When creating a share with + | path | welcome.txt | + | shareType | 4 | + | shareWith | dumy@test.com | + And Updating last share with + | password | publicpw | + And Updating last share with + | password | publicpw | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last share with password "publicpw" can be downloaded + Scenario: Creating a new public share Given user "user0" exists And As an "user0" @@ -64,7 +150,7 @@ Feature: sharing | shareType | 3 | Then the OCS status code should be "100" And the HTTP status code should be "200" - And Public shared file "welcome.txt" can be downloaded + And last link share can be downloaded Scenario: Creating a new public share with password Given user "user0" exists @@ -75,7 +161,7 @@ Feature: sharing | password | publicpw | Then the OCS status code should be "100" And the HTTP status code should be "200" - And Public shared file "welcome.txt" with password "publicpw" can be downloaded + And last share with password "publicpw" can be downloaded Scenario: Creating a new public share of a folder Given user "user0" exists @@ -108,7 +194,7 @@ Feature: sharing | expireDate | +3 days | Then the OCS status code should be "100" And the HTTP status code should be "200" - And Public shared file "welcome.txt" with password "publicpw" can be downloaded + And last share with password "publicpw" can be downloaded Scenario: Creating a new public share, updating its expiration date and getting its info Given user "user0" exists diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 36cbd511b8d..86b34a4b9f0 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -968,8 +968,14 @@ class Manager implements IManager { } elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { $this->linkCreateChecks($share); + $plainTextPassword = $share->getPassword(); + $this->updateSharePasswordIfNeeded($share, $originalShare); + if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) { + throw new \InvalidArgumentException('Can’t enable sending the password by Talk with an empty password'); + } + if ($share->getExpirationDate() != $originalShare->getExpirationDate()) { //Verify the expiration date $this->validateExpirationDate($share); @@ -977,11 +983,9 @@ class Manager implements IManager { } } elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { // The new password is not set again if it is the same as the old - // one, unless when switching from sending by Talk to sending by - // mail. + // one. $plainTextPassword = $share->getPassword(); - if (!empty($plainTextPassword) && !$this->updateSharePasswordIfNeeded($share, $originalShare) && - !($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk())) { + if (!empty($plainTextPassword) && !$this->updateSharePasswordIfNeeded($share, $originalShare)) { $plainTextPassword = null; } if (empty($plainTextPassword) && !$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) { @@ -989,6 +993,8 @@ class Manager implements IManager { // would already have access to the share without having to call // the sharer to verify her identity throw new \InvalidArgumentException('Can’t enable sending the password by Talk without setting a new password'); + } elseif (empty($plainTextPassword) && $originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) { + throw new \InvalidArgumentException('Can’t disable sending the password by Talk without setting a new password'); } } @@ -1075,8 +1081,14 @@ class Manager implements IManager { * @return boolean whether the password was updated or not. */ private function updateSharePasswordIfNeeded(\OCP\Share\IShare $share, \OCP\Share\IShare $originalShare) { + $passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword()) && + (($share->getPassword() !== null && $originalShare->getPassword() === null) || + ($share->getPassword() === null && $originalShare->getPassword() !== null) || + ($share->getPassword() !== null && $originalShare->getPassword() !== null && + !$this->hasher->verify($share->getPassword(), $originalShare->getPassword()))); + // Password updated. - if ($share->getPassword() !== $originalShare->getPassword()) { + if ($passwordsAreDifferent) { //Verify the password $this->verifyPassword($share->getPassword()); @@ -1086,6 +1098,10 @@ class Manager implements IManager { return true; } + } else { + // Reset the password to the original one, as it is either the same + // as the "new" password or a hashed version of it. + $share->setPassword($originalShare->getPassword()); } return false; diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php index 615b5358c9e..54feedf15ce 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -2741,6 +2741,75 @@ class ManagerTest extends \Test\TestCase { $manager->updateShare($share); } + public function testUpdateShareLinkEnableSendPasswordByTalkWithNoPassword() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Can’t enable sending the password by Talk with an empty password'); + + $manager = $this->createManagerMock() + ->setMethods([ + 'canShare', + 'getShareById', + 'generalCreateChecks', + 'linkCreateChecks', + 'pathCreateChecks', + 'verifyPassword', + 'validateExpirationDate', + ]) + ->getMock(); + + $originalShare = $this->manager->newShare(); + $originalShare->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPermissions(15); + + $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_LINK) + ->setToken('token') + ->setSharedBy('owner') + ->setShareOwner('owner') + ->setPassword(null) + ->setSendPasswordByTalk(true) + ->setExpirationDate($tomorrow) + ->setNode($file) + ->setPermissions(15); + + $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('linkCreateChecks')->with($share); + $manager->expects($this->never())->method('verifyPassword'); + $manager->expects($this->never())->method('pathCreateChecks'); + $manager->expects($this->never())->method('validateExpirationDate'); + + $this->hasher->expects($this->never()) + ->method('hash'); + + $this->defaultProvider->expects($this->never()) + ->method('update'); + + $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->never())->method('post'); + + $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); + } + public function testUpdateShareMail() { $manager = $this->createManagerMock() ->setMethods([ @@ -2894,6 +2963,88 @@ class ManagerTest extends \Test\TestCase { $manager->updateShare($share); } + public function testUpdateShareMailEnableSendPasswordByTalkWithDifferentPassword() { + $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) + ->setPassword('anotherPasswordHash') + ->setSendPasswordByTalk(false); + + $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') + ->setSendPasswordByTalk(true) + ->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('verify') + ->with('password', 'anotherPasswordHash') + ->willReturn(false); + + $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); + } public function testUpdateShareMailEnableSendPasswordByTalkWithNoPassword() { $this->expectException(\InvalidArgumentException::class); @@ -2986,7 +3137,7 @@ class ManagerTest extends \Test\TestCase { $originalShare = $this->manager->newShare(); $originalShare->setShareType(\OCP\Share::SHARE_TYPE_EMAIL) ->setPermissions(\OCP\Constants::PERMISSION_ALL) - ->setPassword('password') + ->setPassword('passwordHash') ->setSendPasswordByTalk(false); $tomorrow = new \DateTime(); @@ -3058,7 +3209,7 @@ class ManagerTest extends \Test\TestCase { $originalShare = $this->manager->newShare(); $originalShare->setShareType(\OCP\Share::SHARE_TYPE_EMAIL) ->setPermissions(\OCP\Constants::PERMISSION_ALL) - ->setPassword('password') + ->setPassword('passwordHash') ->setSendPasswordByTalk(false); $tomorrow = new \DateTime(); @@ -3130,7 +3281,7 @@ class ManagerTest extends \Test\TestCase { $originalShare = $this->manager->newShare(); $originalShare->setShareType(\OCP\Share::SHARE_TYPE_EMAIL) ->setPermissions(\OCP\Constants::PERMISSION_ALL) - ->setPassword('password') + ->setPassword('passwordHash') ->setSendPasswordByTalk(false); $tomorrow = new \DateTime(); @@ -3161,6 +3312,11 @@ class ManagerTest extends \Test\TestCase { $manager->expects($this->never())->method('linkCreateChecks'); $manager->expects($this->never())->method('validateExpirationDate'); + $this->hasher->expects($this->once()) + ->method('verify') + ->with('password', 'passwordHash') + ->willReturn(true); + $this->hasher->expects($this->never()) ->method('hash'); @@ -3183,6 +3339,9 @@ class ManagerTest extends \Test\TestCase { } public function testUpdateShareMailDisableSendPasswordByTalkWithPreviousPassword() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Can’t disable sending the password by Talk without setting a new password'); + $manager = $this->createManagerMock() ->setMethods([ 'canShare', @@ -3198,7 +3357,7 @@ class ManagerTest extends \Test\TestCase { $originalShare = $this->manager->newShare(); $originalShare->setShareType(\OCP\Share::SHARE_TYPE_EMAIL) ->setPermissions(\OCP\Constants::PERMISSION_ALL) - ->setPassword('password') + ->setPassword('passwordHash') ->setSendPasswordByTalk(true); $tomorrow = new \DateTime(); @@ -3224,18 +3383,21 @@ 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('generalCreateChecks')->with($share); - $manager->expects($this->once())->method('pathCreateChecks')->with($file); $manager->expects($this->never())->method('verifyPassword'); + $manager->expects($this->never())->method('pathCreateChecks'); $manager->expects($this->never())->method('linkCreateChecks'); $manager->expects($this->never())->method('validateExpirationDate'); + $this->hasher->expects($this->once()) + ->method('verify') + ->with('password', 'passwordHash') + ->willReturn(true); + $this->hasher->expects($this->never()) ->method('hash'); - $this->defaultProvider->expects($this->once()) - ->method('update') - ->with($share, 'password') - ->willReturn($share); + $this->defaultProvider->expects($this->never()) + ->method('update'); $hookListner = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListner, 'post'); @@ -3252,6 +3414,79 @@ class ManagerTest extends \Test\TestCase { $manager->updateShare($share); } + public function testUpdateShareMailDisableSendPasswordByTalkWithoutChangingPassword() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Can’t disable sending the password by Talk without setting a new password'); + + $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) + ->setPassword('passwordHash') + ->setSendPasswordByTalk(true); + + $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('passwordHash') + ->setSendPasswordByTalk(false) + ->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->never())->method('verifyPassword'); + $manager->expects($this->never())->method('pathCreateChecks'); + $manager->expects($this->never())->method('linkCreateChecks'); + $manager->expects($this->never())->method('validateExpirationDate'); + + $this->hasher->expects($this->never()) + ->method('verify'); + + $this->hasher->expects($this->never()) + ->method('hash'); + + $this->defaultProvider->expects($this->never()) + ->method('update'); + + $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->never())->method('post'); + + $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); + } public function testMoveShareLink() { $this->expectException(\InvalidArgumentException::class); |