diff --git a/chatmaild/src/chatmaild/dictproxy.py b/chatmaild/src/chatmaild/dictproxy.py index dd4f9345..759b0173 100644 --- a/chatmaild/src/chatmaild/dictproxy.py +++ b/chatmaild/src/chatmaild/dictproxy.py @@ -21,19 +21,32 @@ def encrypt_password(password: str): return "{SHA512-CRYPT}" + passhash -def check_password(password) -> bool: - """Check password policy""" - if len(password) < 10: +def is_allowed_to_create(user, cleartext_password) -> bool: + """Return True if user and password are admissable.""" + if os.path.exists(NOCREATE_FILE): + logging.warning(f"blocked account creation because {NOCREATE_FILE!r} exists.") return False + + if len(cleartext_password) < 10: + logging.warning("Password needs to be at least 10 characters long") + return False + + parts = user.split("@") + if len(parts) != 2: + logging.warning(f"user {user!r} is not a proper e-mail address") + return False + localpart, domain = parts + + if domain == "nine.testrun.org": + # nine.testrun.org policy, username has to be exactly nine chars + if len(localpart) != 9: + logging.warning(f"localpart {localpart!r} has not exactly nine chars") + return False + return True def create_user(db, user, encrypted_password): - if os.path.exists(NOCREATE_FILE): - logging.warning( - f"Didn't create account: {NOCREATE_FILE} exists. Delete the file to enable account creation." - ) - return with db.write_transaction() as conn: conn.create_user(user, encrypted_password) return dict( @@ -57,13 +70,13 @@ def lookup_userdb(db, user): return get_user_data(db, user) -def lookup_passdb(db, user, password): +def lookup_passdb(db, user, cleartext_password): userdata = get_user_data(db, user) if not userdata: - if not check_password(password): - logging.warning("Attempt to create an account with a weak password.") + if not is_allowed_to_create(user, cleartext_password): return - return create_user(db, user, encrypt_password(password)) + encrypted_password = encrypt_password(cleartext_password) + userdata = create_user(db=db, user=user, encrypted_password=encrypted_password) userdata["password"] = userdata["password"].strip() return userdata @@ -87,7 +100,7 @@ def handle_dovecot_request(msg, db, mail_domain): reply_command = "N" elif type == "passdb": if user.endswith(f"@{mail_domain}"): - res = lookup_passdb(db, user, password=args[0]) + res = lookup_passdb(db, user, cleartext_password=args[0]) if res: reply_command = "O" else: diff --git a/tests/chatmaild/test_dictproxy.py b/tests/chatmaild/test_dictproxy.py index 77150ce1..0eec33cb 100644 --- a/tests/chatmaild/test_dictproxy.py +++ b/tests/chatmaild/test_dictproxy.py @@ -1,9 +1,10 @@ import os +import json import pytest import chatmaild.dictproxy -from chatmaild.dictproxy import get_user_data, lookup_passdb +from chatmaild.dictproxy import get_user_data, lookup_passdb, handle_dovecot_request from chatmaild.database import Database, DBError @@ -14,13 +15,13 @@ def db(tmpdir): return Database(db_path) + def test_basic(db): - chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate" - if os.path.exists(chatmaild.dictproxy.NOCREATE_FILE): - os.remove(chatmaild.dictproxy.NOCREATE_FILE) lookup_passdb(db, "link2xt@c1.testrun.org", "Pieg9aeToe3eghuthe5u") data = get_user_data(db, "link2xt@c1.testrun.org") assert data + data2 = lookup_passdb(db, "link2xt@c1.testrun.org", "Pieg9aeToe3eghuthe5u") + assert data == data2 def test_dont_overwrite_password_on_wrong_login(db): @@ -32,14 +33,12 @@ def test_dont_overwrite_password_on_wrong_login(db): assert res["password"] == res2["password"] -def test_nocreate_file(db): - chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate" - with open(chatmaild.dictproxy.NOCREATE_FILE, "w+") as f: - f.write("") - assert os.path.exists(chatmaild.dictproxy.NOCREATE_FILE) +def test_nocreate_file(db, monkeypatch, tmpdir): + p = tmpdir.join("nocreate") + p.write("") + monkeypatch.setattr(chatmaild.dictproxy, "NOCREATE_FILE", str(p)) lookup_passdb(db, "newuser1@something.org", "zequ0Aimuchoodaechik") assert not get_user_data(db, "newuser1@something.org") - os.remove(chatmaild.dictproxy.NOCREATE_FILE) def test_db_version(db): @@ -51,3 +50,15 @@ def test_too_high_db_version(db): conn.execute("PRAGMA user_version=%s;" % (999,)) with pytest.raises(DBError): db.ensure_tables() + + +def test_handle_dovecot_request(db): + msg = ('Lshared/passdb/laksjdlaksjdlaksjdlk12j3l1k2j3123/' + 'some42@c3.testrun.org\tsome42@c3.testrun.org') + res = handle_dovecot_request(msg, db, "c3.testrun.org") + assert res + assert res[0] == "O" and res.endswith("\n") + userdata = json.loads(res[1:].strip()) + assert userdata["home"] == "/home/vmail/some42@c3.testrun.org" + assert userdata["uid"] == userdata["gid"] == "vmail" + assert userdata["password"].startswith("{SHA512-CRYPT}") diff --git a/tests/conftest.py b/tests/conftest.py index b97536d7..8d8f0227 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -195,8 +195,8 @@ def gencreds(maildomain): num = next(count) alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890" user = "".join(random.choices(alphanumeric, k=10)) - user = f"ac{num}_{user}" - password = "".join(random.choices(alphanumeric, k=10)) + user = f"ac{num}_{user}"[:9] + password = "".join(random.choices(alphanumeric, k=12)) yield f"{user}@{domain}", f"{password}" return lambda domain=None: next(gen(domain))