Add integration tests for creating and updating a mail share
In most cases, when a mail share is created or updated an e-mail is sent to the sharee, which is done by connecting to the SMTP server set in the configuration. If the server can not be contacted then the creation or update of the mail share fails. To make possible to test mail shares without using a real SMTP server a fake one has been added. The original script, which is MIT licensed, was based on inetd, so it was slightly modified to run on its own. In order to use it from the integration tests the "Given dummy mail server is listening" step has to be called in the scenarios in which the mail server is needed. For now that is the only available step; things like checking the sent mails, while possible (as the script can log the mails to certain file), have not been added yet. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
parent
f7e8034d72
commit
125481a660
|
@ -45,6 +45,7 @@ require __DIR__ . '/../../vendor/autoload.php';
|
||||||
trait BasicStructure {
|
trait BasicStructure {
|
||||||
use Auth;
|
use Auth;
|
||||||
use Download;
|
use Download;
|
||||||
|
use Mail;
|
||||||
use Trashbin;
|
use Trashbin;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
|
|
|
@ -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);
|
|
@ -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 $!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,9 @@ require __DIR__ . '/../../vendor/autoload.php';
|
||||||
class SharingContext implements Context, SnippetAcceptingContext {
|
class SharingContext implements Context, SnippetAcceptingContext {
|
||||||
use Sharing;
|
use Sharing;
|
||||||
use AppConfiguration;
|
use AppConfiguration;
|
||||||
|
use CommandLine;
|
||||||
|
|
||||||
protected function resetAppConfigs() {
|
protected function resetAppConfigs() {
|
||||||
|
$this->modifyServerConfig('sharebymail', 'enforcePasswordProtection', 'no');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,92 @@ Feature: sharing
|
||||||
Then the OCS status code should be "403"
|
Then the OCS status code should be "403"
|
||||||
And the HTTP status code should be "401"
|
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
|
Scenario: Creating a new public share
|
||||||
Given user "user0" exists
|
Given user "user0" exists
|
||||||
And As an "user0"
|
And As an "user0"
|
||||||
|
|
Loading…
Reference in New Issue