2024-12-05 10:42:51 +00:00
|
|
|
import datetime
|
|
|
|
import logging
|
|
|
|
|
|
|
|
import time_machine
|
|
|
|
|
|
|
|
from canaille.backends.ldap.backend import LDAPBackend
|
|
|
|
from canaille.core.models import PASSWORD_MIN_DELAY
|
|
|
|
|
|
|
|
|
|
|
|
def test_intruder_lockout_fail_second_attempt_then_succeed_in_third(
|
|
|
|
testclient, user, caplog
|
|
|
|
):
|
|
|
|
testclient.app.config["CANAILLE"]["ENABLE_INTRUDER_LOCKOUT"] = True
|
|
|
|
|
|
|
|
with testclient.session_transaction() as session:
|
|
|
|
assert not session.get("user_id")
|
|
|
|
|
2024-12-19 11:16:39 +00:00
|
|
|
# add 500 milliseconds to account for LDAP time
|
2024-12-05 10:42:51 +00:00
|
|
|
with time_machine.travel(
|
|
|
|
datetime.datetime.now(datetime.timezone.utc)
|
2024-12-19 11:16:39 +00:00
|
|
|
+ datetime.timedelta(milliseconds=500),
|
2024-12-05 10:42:51 +00:00
|
|
|
tick=False,
|
|
|
|
) as traveller:
|
|
|
|
res = testclient.get("/login", status=200)
|
|
|
|
|
|
|
|
res.form["login"] = "user"
|
|
|
|
res = res.form.submit(status=302).follow()
|
|
|
|
|
|
|
|
res.form["password"] = "incorrect horse"
|
|
|
|
res = res.form.submit(status=200)
|
|
|
|
|
|
|
|
assert ("error", "Login failed, please check your information") in res.flashes
|
|
|
|
assert (
|
|
|
|
"canaille",
|
|
|
|
logging.SECURITY,
|
|
|
|
"Failed login attempt for user from unknown IP",
|
|
|
|
) in caplog.record_tuples
|
|
|
|
|
|
|
|
res.form["password"] = "correct horse battery staple"
|
|
|
|
res = res.form.submit(status=200)
|
|
|
|
|
|
|
|
assert (
|
|
|
|
"error",
|
|
|
|
f"Too much attempts. Please wait for {PASSWORD_MIN_DELAY} seconds before trying to login again.",
|
|
|
|
) in res.flashes
|
|
|
|
assert (
|
|
|
|
"canaille",
|
|
|
|
logging.SECURITY,
|
|
|
|
"Failed login attempt for user from unknown IP",
|
|
|
|
) in caplog.record_tuples
|
|
|
|
|
|
|
|
traveller.shift(datetime.timedelta(seconds=PASSWORD_MIN_DELAY))
|
|
|
|
|
|
|
|
res.form["password"] = "correct horse battery staple"
|
|
|
|
res = res.form.submit(status=302)
|
|
|
|
|
|
|
|
assert (
|
|
|
|
"success",
|
|
|
|
"Connection successful. Welcome John (johnny) Doe",
|
|
|
|
) in res.flashes
|
|
|
|
assert (
|
|
|
|
"canaille",
|
|
|
|
logging.SECURITY,
|
|
|
|
"Succeed login attempt for user from unknown IP",
|
|
|
|
) in caplog.record_tuples
|
|
|
|
|
|
|
|
|
|
|
|
def test_intruder_lockout_two_consecutive_fails(testclient, backend, user, caplog):
|
|
|
|
testclient.app.config["CANAILLE"]["ENABLE_INTRUDER_LOCKOUT"] = True
|
|
|
|
|
|
|
|
# add 100 milliseconds to account for LDAP time
|
|
|
|
with time_machine.travel(
|
|
|
|
datetime.datetime.now(datetime.timezone.utc)
|
|
|
|
+ datetime.timedelta(milliseconds=100),
|
|
|
|
tick=False,
|
|
|
|
) as traveller:
|
|
|
|
res = testclient.get("/login", status=200)
|
|
|
|
|
|
|
|
res.form["login"] = "user"
|
|
|
|
res = res.form.submit().follow()
|
|
|
|
|
|
|
|
res.form["password"] = "incorrect horse"
|
|
|
|
res = res.form.submit(status=200)
|
|
|
|
|
|
|
|
assert ("error", "Login failed, please check your information") in res.flashes
|
|
|
|
assert (
|
|
|
|
"canaille",
|
|
|
|
logging.SECURITY,
|
|
|
|
"Failed login attempt for user from unknown IP",
|
|
|
|
) in caplog.record_tuples
|
|
|
|
|
|
|
|
res.form["password"] = "correct horse battery staple"
|
|
|
|
res = res.form.submit(status=200)
|
|
|
|
|
|
|
|
assert (
|
|
|
|
"error",
|
|
|
|
f"Too much attempts. Please wait for {PASSWORD_MIN_DELAY} seconds before trying to login again.",
|
|
|
|
) in res.flashes
|
|
|
|
assert (
|
|
|
|
"canaille",
|
|
|
|
logging.SECURITY,
|
|
|
|
"Failed login attempt for user from unknown IP",
|
|
|
|
) in caplog.record_tuples
|
|
|
|
|
|
|
|
traveller.shift(datetime.timedelta(seconds=PASSWORD_MIN_DELAY))
|
|
|
|
ldap_shift = (
|
|
|
|
PASSWORD_MIN_DELAY if isinstance(backend, LDAPBackend) else 0
|
|
|
|
) # LDAP doesn't travel in time!
|
|
|
|
|
|
|
|
res.form["password"] = "incorrect horse"
|
|
|
|
res = res.form.submit(status=200)
|
|
|
|
|
|
|
|
assert ("error", "Login failed, please check your information") in res.flashes
|
|
|
|
assert (
|
|
|
|
"canaille",
|
|
|
|
logging.SECURITY,
|
|
|
|
"Failed login attempt for user from unknown IP",
|
|
|
|
) in caplog.record_tuples
|
|
|
|
|
|
|
|
res.form["password"] = "correct horse battery staple"
|
|
|
|
res = res.form.submit(status=200)
|
|
|
|
|
|
|
|
assert (
|
|
|
|
"error",
|
|
|
|
f"Too much attempts. Please wait for {PASSWORD_MIN_DELAY*2 - ldap_shift} seconds before trying to login again.",
|
|
|
|
) in res.flashes
|
|
|
|
assert (
|
|
|
|
"canaille",
|
|
|
|
logging.SECURITY,
|
|
|
|
"Failed login attempt for user from unknown IP",
|
|
|
|
) in caplog.record_tuples
|
|
|
|
|
|
|
|
traveller.shift(datetime.timedelta(seconds=PASSWORD_MIN_DELAY * 2))
|
|
|
|
|
|
|
|
res.form["password"] = "correct horse battery staple"
|
|
|
|
res = res.form.submit(status=302)
|
|
|
|
|
|
|
|
assert (
|
|
|
|
"success",
|
|
|
|
"Connection successful. Welcome John (johnny) Doe",
|
|
|
|
) in res.flashes
|
|
|
|
assert (
|
|
|
|
"canaille",
|
|
|
|
logging.SECURITY,
|
|
|
|
"Succeed login attempt for user from unknown IP",
|
|
|
|
) in caplog.record_tuples
|