mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2025-04-19 02:42:15 +00:00
Merge a2350c4591
into 65983b8ac7
This commit is contained in:
commit
dfdc150f05
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ tools/__pycache__/
|
||||
externals/
|
||||
.env
|
||||
.vagrant
|
||||
*.pyc
|
||||
|
24
test/README.md
Normal file
24
test/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
This is the mailinabox test suite. It uses the excellent pytest module to check the functionality of the different services.
|
||||
|
||||
# Usage
|
||||
|
||||
start-up a vagrant box
|
||||
|
||||
vagrant up
|
||||
|
||||
install test requirements (using a virtualenv is highly recommended)
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
run the tests
|
||||
|
||||
pytest
|
||||
|
||||
to just run a subset of the tests (e.g. the ssh related ones):
|
||||
|
||||
pytest test_ssh.py
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
pytest auto-discovers all tests in this directory. The test functions need to be named "test_..." and there needs to be at least one assert statement.
|
5
test/common.py
Normal file
5
test/common.py
Normal file
@ -0,0 +1,5 @@
|
||||
import uuid
|
||||
|
||||
|
||||
def random_id():
|
||||
return uuid.uuid4().hex[:8]
|
3
test/requirements.txt
Normal file
3
test/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
pytest>=3.0.5
|
||||
pyCardDAV==0.7.0
|
||||
caldav==0.5
|
11
test/settings.py
Normal file
11
test/settings.py
Normal file
@ -0,0 +1,11 @@
|
||||
import socket
|
||||
|
||||
TEST_SERVER = '127.0.0.1'
|
||||
TEST_DOMAIN = 'mailinabox.lan'
|
||||
TEST_PORT = 2222
|
||||
TEST_PASSWORD = '1234'
|
||||
TEST_USER = 'me'
|
||||
TEST_ADDRESS = TEST_USER + '@' + TEST_DOMAIN
|
||||
TEST_SENDER = "someone@example.com"
|
||||
|
||||
socket.setdefaulttimeout(5)
|
44
test/test_backup.py
Normal file
44
test/test_backup.py
Normal file
@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
from time import sleep
|
||||
from subprocess import check_call, check_output
|
||||
import smtplib
|
||||
|
||||
from settings import *
|
||||
from test_mail import new_message, check_imap_received
|
||||
|
||||
|
||||
def test_backup_mail():
|
||||
# send a mail, to ensure we have something to backup
|
||||
msg, subject = new_message(TEST_ADDRESS, TEST_ADDRESS)
|
||||
s = smtplib.SMTP(TEST_DOMAIN, 587)
|
||||
s.starttls()
|
||||
s.login(TEST_ADDRESS, TEST_PASSWORD)
|
||||
s.sendmail(TEST_ADDRESS, [TEST_ADDRESS], msg)
|
||||
s.quit()
|
||||
|
||||
# trigger a backup
|
||||
sleep(2)
|
||||
cmd_ssh = "sshpass -p vagrant ssh vagrant@{} -p {} ".format(TEST_SERVER, TEST_PORT)
|
||||
cmd_count = cmd_ssh + "ls -l /home/user-data/backup/encrypted | wc -l"
|
||||
num_backup_files = int(check_output(cmd_count, shell=True))
|
||||
cmd = cmd_ssh + "sudo /vagrant/management/backup.py"
|
||||
check_call(cmd, shell=True)
|
||||
num_backup_files_new = int(check_output(cmd_count, shell=True))
|
||||
assert num_backup_files_new > num_backup_files
|
||||
|
||||
# delete mail
|
||||
assert check_imap_received(subject)
|
||||
assert not check_imap_received(subject)
|
||||
|
||||
# restore backup
|
||||
path = "/home/user-data"
|
||||
passphrase = "export PASSPHRASE=\$(sudo cat /home/user-data/backup/secret_key.txt) &&"
|
||||
# extract to temp directory
|
||||
restore = "sudo -E duplicity restore --force file://{0}/backup/encrypted {0}/restore &&".format(path)
|
||||
# move restored backup using rsync, because it allows to overwrite files
|
||||
move = "sudo rsync -av {0}/restore/* {0}/ &&".format(path)
|
||||
rm = "sudo rm -rf {0}/restore/".format(path)
|
||||
check_call(cmd_ssh + "\"" + passphrase + restore + move + rm + "\"", shell=True)
|
||||
|
||||
# check the mail is there again
|
||||
assert check_imap_received(subject)
|
68
test/test_caldav.py
Normal file
68
test/test_caldav.py
Normal file
@ -0,0 +1,68 @@
|
||||
import pytest
|
||||
import caldav
|
||||
from time import sleep
|
||||
|
||||
from settings import *
|
||||
from common import random_id
|
||||
|
||||
|
||||
def connect():
|
||||
url = "https://" + TEST_DOMAIN + "/cloud/remote.php/dav/calendars/" + TEST_ADDRESS + "/personal/"
|
||||
client = caldav.DAVClient(url, username=TEST_ADDRESS, password=TEST_PASSWORD, ssl_verify_cert=False)
|
||||
principal = client.principal()
|
||||
calendars = principal.calendars()
|
||||
return client, calendars[0]
|
||||
|
||||
|
||||
vcal = """BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||
BEGIN:VEVENT
|
||||
UID:{}
|
||||
DTSTAMP:20170510T182145Z
|
||||
DTSTART:20170512T170000Z
|
||||
DTEND:20170512T180000Z
|
||||
SUMMARY: this is a sample event
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
"""
|
||||
|
||||
|
||||
def create_event():
|
||||
uid = random_id()
|
||||
event = vcal.format(uid)
|
||||
return event, uid
|
||||
|
||||
|
||||
def event_exists(uid):
|
||||
c, cal = connect()
|
||||
try:
|
||||
event = cal.event(uid)
|
||||
return True
|
||||
except caldav.lib.error.NotFoundError:
|
||||
return False
|
||||
|
||||
|
||||
def test_addremove_event():
|
||||
c, cal = connect()
|
||||
event, uid = create_event()
|
||||
cal.add_event(event)
|
||||
assert event_exists(uid)
|
||||
|
||||
# now delete the event again
|
||||
event = cal.event(uid)
|
||||
event.delete()
|
||||
sleep(3)
|
||||
assert (not event_exists(uid))
|
||||
|
||||
|
||||
def test_addremove_calendar():
|
||||
c, _ = connect()
|
||||
cal_id = random_id()
|
||||
cal = c.principal().make_calendar(name="test", cal_id=cal_id)
|
||||
matching = [calendar for calendar in c.principal().calendars() if cal_id in str(calendar.url)]
|
||||
assert len(matching) == 1
|
||||
|
||||
c.delete(cal.url)
|
||||
matching = [calendar for calendar in c.principal().calendars() if cal_id in str(calendar.url)]
|
||||
assert len(matching) == 0
|
48
test/test_carddav.py
Normal file
48
test/test_carddav.py
Normal file
@ -0,0 +1,48 @@
|
||||
import pytest
|
||||
from pycarddav import carddav
|
||||
|
||||
from settings import *
|
||||
|
||||
|
||||
test_vcf = """
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
EMAIL;TYPE=PREF:foo@example.com
|
||||
N:John Doe;;;;
|
||||
FN:John Doe
|
||||
REV:2012-08-02T21:16:14+00:00
|
||||
PRODID:-//ownCloud//NONSGML Contacts 0.2//EN
|
||||
UID:c292c7212b
|
||||
END:VCARD
|
||||
"""
|
||||
|
||||
def connect():
|
||||
url = "https://" + TEST_DOMAIN + "/cloud/remote.php/carddav/addressbooks/" + TEST_ADDRESS + "/contacts/"
|
||||
return carddav.PyCardDAV(url, user=TEST_ADDRESS, passwd=TEST_PASSWORD, verify=False, write_support=True)
|
||||
|
||||
|
||||
def test_adddelete_contact():
|
||||
c = connect()
|
||||
abook = c.get_abook()
|
||||
prev_len = len(abook)
|
||||
|
||||
url, etag = c.upload_new_card(test_vcf)
|
||||
abook = c.get_abook()
|
||||
assert len(abook) == prev_len + 1
|
||||
|
||||
c.delete_vcard(url, etag)
|
||||
abook = c.get_abook()
|
||||
assert len(abook) == prev_len
|
||||
|
||||
|
||||
def test_update_contact():
|
||||
c = connect()
|
||||
url, etag = c.upload_new_card(test_vcf)
|
||||
|
||||
card = c.get_vcard(url)
|
||||
new_card = card.replace("John Doe", "Jane Doe")
|
||||
c.update_vcard(new_card, url, etag)
|
||||
|
||||
card = c.get_vcard(url)
|
||||
assert "John Doe" not in card
|
||||
assert "Jane Doe" in card
|
170
test/test_mail.py
Normal file
170
test/test_mail.py
Normal file
@ -0,0 +1,170 @@
|
||||
from time import sleep
|
||||
import requests
|
||||
import os
|
||||
import pytest
|
||||
import imaplib
|
||||
import poplib
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from settings import *
|
||||
from common import random_id
|
||||
|
||||
|
||||
def new_message(from_email, to_email):
|
||||
"""Creates an email (headers & body) with a random subject"""
|
||||
msg = MIMEText('Testing')
|
||||
msg['Subject'] = random_id()
|
||||
msg['From'] = from_email
|
||||
msg['To'] = to_email
|
||||
return msg.as_string(), msg['subject']
|
||||
|
||||
|
||||
|
||||
def check_imap_received(subject):
|
||||
"""Connects with IMAP and asserts the existence of an email, then deletes it"""
|
||||
|
||||
sleep(3)
|
||||
|
||||
# Login to IMAP
|
||||
m = imaplib.IMAP4_SSL(TEST_DOMAIN, 993)
|
||||
m.login(TEST_ADDRESS, TEST_PASSWORD)
|
||||
m.select()
|
||||
|
||||
# check the message exists
|
||||
typ, data = m.search(None, '(SUBJECT \"{}\")'.format(subject))
|
||||
res = len(data[0].split()) == 1
|
||||
|
||||
if res:
|
||||
m.store(data[0].strip(), '+FLAGS', '\\Deleted')
|
||||
m.expunge()
|
||||
m.close()
|
||||
m.logout()
|
||||
return res
|
||||
|
||||
|
||||
def assert_pop3_received(subject):
|
||||
"""Connects with POP3S and asserts the existence of an email, then deletes it"""
|
||||
|
||||
sleep(3)
|
||||
|
||||
# Login to POP3
|
||||
mail = poplib.POP3_SSL(TEST_DOMAIN, 995)
|
||||
mail.user(TEST_ADDRESS)
|
||||
mail.pass_(TEST_PASSWORD)
|
||||
|
||||
# Assert the message exists
|
||||
num = len(mail.list()[1])
|
||||
resp, text, octets = mail.retr(num)
|
||||
assert "Subject: " + subject in text
|
||||
|
||||
# Delete it and log out
|
||||
mail.dele(num)
|
||||
mail.quit()
|
||||
|
||||
|
||||
def test_imap_requires_ssl():
|
||||
"""IMAP without SSL is NOT available"""
|
||||
with pytest.raises(socket.timeout):
|
||||
imaplib.IMAP4(TEST_DOMAIN, 143)
|
||||
|
||||
|
||||
def test_pop3_requires_ssl():
|
||||
"""POP3 without SSL is NOT available"""
|
||||
with pytest.raises(socket.timeout):
|
||||
poplib.POP3(TEST_DOMAIN, 110)
|
||||
|
||||
|
||||
def test_smtps():
|
||||
"""Email sent from an MUA via SMTPS is delivered"""
|
||||
msg, subject = new_message(TEST_ADDRESS, TEST_ADDRESS)
|
||||
s = smtplib.SMTP(TEST_DOMAIN, 587)
|
||||
s.starttls()
|
||||
s.login(TEST_ADDRESS, TEST_PASSWORD)
|
||||
s.sendmail(TEST_ADDRESS, [TEST_ADDRESS], msg)
|
||||
s.quit()
|
||||
assert check_imap_received(subject)
|
||||
|
||||
|
||||
def test_smtps_tag():
|
||||
"""Email sent to address with tag is delivered"""
|
||||
mail_address = TEST_ADDRESS.replace("@", "+sometag@")
|
||||
msg, subject = new_message(TEST_ADDRESS, mail_address)
|
||||
s = smtplib.SMTP(TEST_DOMAIN, 587)
|
||||
s.starttls()
|
||||
s.login(TEST_ADDRESS, TEST_PASSWORD)
|
||||
s.sendmail(TEST_ADDRESS, [mail_address], msg)
|
||||
s.quit()
|
||||
assert check_imap_received(subject)
|
||||
|
||||
|
||||
def test_smtps_requires_auth():
|
||||
"""SMTPS with no authentication is rejected"""
|
||||
import smtplib
|
||||
s = smtplib.SMTP(TEST_DOMAIN, 587)
|
||||
s.starttls()
|
||||
|
||||
#FIXME why does this work without login?
|
||||
|
||||
with pytest.raises(smtplib.SMTPRecipientsRefused):
|
||||
s.sendmail(TEST_ADDRESS, [TEST_ADDRESS], 'Test')
|
||||
|
||||
s.quit()
|
||||
|
||||
|
||||
def test_smtp():
|
||||
"""Email sent from an MTA is delivered"""
|
||||
import smtplib
|
||||
msg, subject = new_message(TEST_SENDER, TEST_ADDRESS)
|
||||
s = smtplib.SMTP(TEST_DOMAIN, 25)
|
||||
s.sendmail(TEST_SENDER, [TEST_ADDRESS], msg)
|
||||
s.quit()
|
||||
assert check_imap_received(subject)
|
||||
|
||||
|
||||
def test_smtp_tls():
|
||||
"""Email sent from an MTA via SMTP+TLS is delivered"""
|
||||
msg, subject = new_message(TEST_SENDER, TEST_ADDRESS)
|
||||
s = smtplib.SMTP(TEST_DOMAIN, 25)
|
||||
s.starttls()
|
||||
s.sendmail(TEST_SENDER, [TEST_ADDRESS], msg)
|
||||
s.quit()
|
||||
assert check_imap_received(subject)
|
||||
|
||||
|
||||
def test_smtp_headers():
|
||||
"""Email sent from an MTA via SMTP+TLS has TLS headers"""
|
||||
# Send a message to root
|
||||
msg, subject = new_message(TEST_SENDER, TEST_ADDRESS)
|
||||
s = smtplib.SMTP(TEST_DOMAIN, 25)
|
||||
s.starttls()
|
||||
s.sendmail(TEST_SENDER, [TEST_ADDRESS], msg)
|
||||
s.quit()
|
||||
|
||||
sleep(3)
|
||||
|
||||
# Get the message
|
||||
m = imaplib.IMAP4_SSL(TEST_DOMAIN, 993)
|
||||
m.login(TEST_ADDRESS, TEST_PASSWORD)
|
||||
m.select()
|
||||
_, res = m.search(None, '(SUBJECT \"{}\")'.format(subject))
|
||||
_, data = m.fetch(res[0], '(RFC822)')
|
||||
|
||||
assert 'ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)' in data[0][1]
|
||||
|
||||
# Clean up
|
||||
m.store(res[0].strip(), '+FLAGS', '\\Deleted')
|
||||
m.expunge()
|
||||
m.close()
|
||||
m.logout()
|
||||
|
||||
|
||||
def test_pop3s():
|
||||
"""Connects with POP3S and asserts the existance of an email"""
|
||||
msg, subject = new_message(TEST_ADDRESS, TEST_ADDRESS)
|
||||
s = smtplib.SMTP(TEST_DOMAIN, 587)
|
||||
s.starttls()
|
||||
s.login(TEST_ADDRESS, TEST_PASSWORD)
|
||||
s.sendmail(TEST_ADDRESS, [TEST_ADDRESS], msg)
|
||||
s.quit()
|
||||
assert_pop3_received(subject)
|
13
test/test_ssh.py
Normal file
13
test/test_ssh.py
Normal file
@ -0,0 +1,13 @@
|
||||
import pytest
|
||||
|
||||
from settings import *
|
||||
|
||||
|
||||
def test_ssh_banner():
|
||||
"""SSH is responding with its banner"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((TEST_SERVER, TEST_PORT))
|
||||
data = s.recv(1024)
|
||||
s.close()
|
||||
|
||||
assert data.startswith("SSH-2.0-OpenSSH")
|
60
test/test_web.py
Normal file
60
test/test_web.py
Normal file
@ -0,0 +1,60 @@
|
||||
from time import sleep
|
||||
import requests
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from settings import *
|
||||
|
||||
|
||||
def test_web_hosting_http():
|
||||
"""web hosting is redirecting to https"""
|
||||
url = 'http://' + TEST_DOMAIN
|
||||
r = requests.get(url, verify=False)
|
||||
|
||||
# We should be redirected to https
|
||||
assert r.history[0].status_code == 301
|
||||
assert r.url == url.replace("http", "https") + "/"
|
||||
|
||||
assert r.status_code == 200
|
||||
assert "this is a mail-in-a-box" in r.content
|
||||
|
||||
|
||||
def test_admin_http():
|
||||
"""Admin page is redirecting to https"""
|
||||
url = 'http://' + TEST_DOMAIN + "/admin"
|
||||
r = requests.get(url, verify=False)
|
||||
|
||||
# We should be redirected to https
|
||||
assert r.history[0].status_code == 301
|
||||
assert r.url == url.replace("http", "https")
|
||||
|
||||
assert r.status_code == 200
|
||||
assert "Log in here for your Mail-in-a-Box control panel" in r.content
|
||||
|
||||
|
||||
def test_webmail_http():
|
||||
"""Webmail is redirecting to https and displaying login page"""
|
||||
url = 'http://' + TEST_DOMAIN + "/mail"
|
||||
r = requests.get(url, verify=False)
|
||||
|
||||
# We should be redirected to https
|
||||
assert r.history[0].status_code == 301
|
||||
assert r.url == url.replace("http", "https") + "/"
|
||||
|
||||
# 200 - We should be at the login page
|
||||
assert r.status_code == 200
|
||||
assert 'Welcome to ' + TEST_DOMAIN + ' Webmail' in r.content
|
||||
|
||||
|
||||
def test_owncloud_http():
|
||||
"""ownCloud is redirecting to https and displaying login page"""
|
||||
url = 'http://' + TEST_DOMAIN + '/cloud'
|
||||
r = requests.get(url, verify=False)
|
||||
|
||||
# We should be redirected to https
|
||||
assert r.history[0].status_code == 301
|
||||
assert r.url == url.replace("http", "https") + "/index.php/login"
|
||||
|
||||
# 200 - We should be at the login page
|
||||
assert r.status_code == 200
|
||||
assert 'ownCloud' in r.content
|
Loading…
Reference in New Issue
Block a user