2022-11-01 11:25:21 +00:00
|
|
|
import datetime
|
2023-03-16 17:39:28 +00:00
|
|
|
from unittest import mock
|
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
from canaille.app import models
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_edition(
|
|
|
|
testclient,
|
|
|
|
logged_user,
|
|
|
|
admin,
|
|
|
|
foo_group,
|
|
|
|
bar_group,
|
|
|
|
):
|
|
|
|
res = testclient.get("/profile/user/settings", status=200)
|
|
|
|
assert set(res.form["groups"].options) == {
|
|
|
|
(foo_group.id, True, "foo"),
|
|
|
|
(bar_group.id, False, "bar"),
|
|
|
|
}
|
|
|
|
assert logged_user.groups == [foo_group]
|
|
|
|
assert foo_group.members == [logged_user]
|
|
|
|
assert bar_group.members == [admin]
|
|
|
|
assert res.form["groups"].attrs["readonly"]
|
2023-02-05 17:57:18 +00:00
|
|
|
assert res.form["user_name"].attrs["readonly"]
|
2023-03-16 17:39:28 +00:00
|
|
|
|
2023-02-05 17:57:18 +00:00
|
|
|
res.form["user_name"] = "toto"
|
2023-03-16 17:39:28 +00:00
|
|
|
res = res.form.submit(name="action", value="edit")
|
|
|
|
assert res.flashes == [("success", "Profile updated successfuly.")]
|
|
|
|
res = res.follow()
|
|
|
|
|
2023-04-08 19:34:09 +00:00
|
|
|
logged_user.reload()
|
2023-03-16 17:39:28 +00:00
|
|
|
|
2023-02-05 17:57:18 +00:00
|
|
|
assert logged_user.user_name == ["user"]
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
foo_group.reload()
|
|
|
|
bar_group.reload()
|
|
|
|
assert logged_user.groups == [foo_group]
|
|
|
|
assert foo_group.members == [logged_user]
|
|
|
|
assert bar_group.members == [admin]
|
|
|
|
|
2022-11-01 11:25:21 +00:00
|
|
|
assert logged_user.check_password("correct horse battery staple")[0]
|
2023-03-16 17:39:28 +00:00
|
|
|
|
2023-02-05 17:57:18 +00:00
|
|
|
logged_user.user_name = ["user"]
|
2023-03-16 17:39:28 +00:00
|
|
|
logged_user.save()
|
|
|
|
|
|
|
|
|
2023-03-30 21:14:39 +00:00
|
|
|
def test_profile_settings_edition_dynamic_validation(testclient, logged_admin):
|
2023-05-25 11:37:58 +00:00
|
|
|
res = testclient.get("/profile/admin/settings")
|
2023-03-30 21:14:39 +00:00
|
|
|
res = testclient.post(
|
2023-05-25 11:37:58 +00:00
|
|
|
"/profile/admin/settings",
|
2023-03-30 21:14:39 +00:00
|
|
|
{
|
|
|
|
"csrf_token": res.form["csrf_token"].value,
|
|
|
|
"password1": "short",
|
|
|
|
},
|
|
|
|
headers={
|
|
|
|
"HX-Request": "true",
|
|
|
|
"HX-Trigger-Name": "password1",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
res.mustcontain("Field must be at least 8 characters long.")
|
|
|
|
|
|
|
|
|
2023-03-16 17:39:28 +00:00
|
|
|
def test_edition_without_groups(
|
|
|
|
testclient,
|
|
|
|
logged_user,
|
|
|
|
admin,
|
|
|
|
):
|
|
|
|
res = testclient.get("/profile/user/settings", status=200)
|
|
|
|
testclient.app.config["ACL"]["DEFAULT"]["READ"] = []
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="edit")
|
|
|
|
assert res.flashes == [("success", "Profile updated successfuly.")]
|
|
|
|
res = res.follow()
|
|
|
|
|
2023-04-08 19:34:09 +00:00
|
|
|
logged_user.reload()
|
2023-03-16 17:39:28 +00:00
|
|
|
|
2023-02-05 17:57:18 +00:00
|
|
|
assert logged_user.user_name == ["user"]
|
2022-11-01 11:25:21 +00:00
|
|
|
assert logged_user.check_password("correct horse battery staple")[0]
|
2023-03-16 17:39:28 +00:00
|
|
|
|
2023-02-05 17:57:18 +00:00
|
|
|
logged_user.user_name = ["user"]
|
2023-03-16 17:39:28 +00:00
|
|
|
logged_user.save()
|
|
|
|
|
|
|
|
|
|
|
|
def test_password_change(testclient, logged_user):
|
|
|
|
res = testclient.get("/profile/user/settings", status=200)
|
|
|
|
|
|
|
|
res.form["password1"] = "new_password"
|
|
|
|
res.form["password2"] = "new_password"
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="edit").follow()
|
|
|
|
|
2023-05-17 14:23:54 +00:00
|
|
|
logged_user.reload()
|
2022-11-01 11:25:21 +00:00
|
|
|
assert logged_user.check_password("new_password")[0]
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
res = testclient.get("/profile/user/settings", status=200)
|
|
|
|
|
|
|
|
res.form["password1"] = "correct horse battery staple"
|
|
|
|
res.form["password2"] = "correct horse battery staple"
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="edit")
|
|
|
|
assert ("success", "Profile updated successfuly.") in res.flashes
|
|
|
|
res = res.follow()
|
|
|
|
|
2023-05-17 14:23:54 +00:00
|
|
|
logged_user.reload()
|
2022-11-01 11:25:21 +00:00
|
|
|
assert logged_user.check_password("correct horse battery staple")[0]
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_password_change_fail(testclient, logged_user):
|
|
|
|
res = testclient.get("/profile/user/settings", status=200)
|
|
|
|
|
|
|
|
res.form["password1"] = "new_password"
|
|
|
|
res.form["password2"] = "other_password"
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="edit", status=200)
|
|
|
|
|
2023-05-17 14:23:54 +00:00
|
|
|
logged_user.reload()
|
2022-11-01 11:25:21 +00:00
|
|
|
assert logged_user.check_password("correct horse battery staple")[0]
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
res = testclient.get("/profile/user/settings", status=200)
|
|
|
|
|
|
|
|
res.form["password1"] = "new_password"
|
|
|
|
res.form["password2"] = ""
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="edit", status=200)
|
|
|
|
|
2023-05-17 14:23:54 +00:00
|
|
|
logged_user.reload()
|
2022-11-01 11:25:21 +00:00
|
|
|
assert logged_user.check_password("correct horse battery staple")[0]
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_password_initialization_mail(smtpd, testclient, backend, logged_admin):
|
2023-04-09 09:37:04 +00:00
|
|
|
u = models.User(
|
2023-02-05 17:57:18 +00:00
|
|
|
formatted_name="Temp User",
|
|
|
|
family_name="Temp",
|
|
|
|
user_name="temp",
|
|
|
|
email="john@doe.com",
|
2023-03-16 17:39:28 +00:00
|
|
|
)
|
|
|
|
u.save()
|
|
|
|
|
|
|
|
res = testclient.get("/profile/temp/settings", status=200)
|
|
|
|
res.mustcontain("This user does not have a password yet")
|
|
|
|
res.mustcontain("Send")
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="password-initialization-mail")
|
|
|
|
assert (
|
|
|
|
"success",
|
|
|
|
"A password initialization link has been sent at the user email address. "
|
|
|
|
"It should be received within a few minutes.",
|
|
|
|
) in res.flashes
|
|
|
|
assert len(smtpd.messages) == 1
|
2023-05-05 08:53:48 +00:00
|
|
|
assert smtpd.messages[0]["X-RcptTo"] == "john@doe.com"
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
u.reload()
|
2023-04-10 19:42:14 +00:00
|
|
|
u.password = ["correct horse battery staple"]
|
2023-03-16 17:39:28 +00:00
|
|
|
u.save()
|
|
|
|
|
|
|
|
res = testclient.get("/profile/temp/settings", status=200)
|
|
|
|
res.mustcontain(no="This user does not have a password yet")
|
|
|
|
|
|
|
|
u.delete()
|
|
|
|
|
|
|
|
|
|
|
|
@mock.patch("smtplib.SMTP")
|
|
|
|
def test_password_initialization_mail_send_fail(
|
2023-05-20 15:17:46 +00:00
|
|
|
SMTP, smtpd, testclient, backend, logged_admin
|
2023-03-16 17:39:28 +00:00
|
|
|
):
|
|
|
|
SMTP.side_effect = mock.Mock(side_effect=OSError("unit test mail error"))
|
2023-04-09 09:37:04 +00:00
|
|
|
u = models.User(
|
2023-02-05 17:57:18 +00:00
|
|
|
formatted_name="Temp User",
|
|
|
|
family_name="Temp",
|
|
|
|
user_name="temp",
|
|
|
|
email="john@doe.com",
|
2023-03-16 17:39:28 +00:00
|
|
|
)
|
|
|
|
u.save()
|
|
|
|
|
|
|
|
res = testclient.get("/profile/temp/settings", status=200)
|
|
|
|
res.mustcontain("This user does not have a password yet")
|
|
|
|
res.mustcontain("Send")
|
|
|
|
|
|
|
|
res = res.form.submit(
|
|
|
|
name="action", value="password-initialization-mail", expect_errors=True
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"success",
|
|
|
|
"A password initialization link has been sent at the user email address. "
|
|
|
|
"It should be received within a few minutes.",
|
|
|
|
) not in res.flashes
|
|
|
|
assert ("error", "Could not send the password initialization email") in res.flashes
|
|
|
|
assert len(smtpd.messages) == 0
|
|
|
|
|
|
|
|
u.delete()
|
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_password_initialization_invalid_user(smtpd, testclient, backend, logged_admin):
|
2023-03-16 17:39:28 +00:00
|
|
|
assert len(smtpd.messages) == 0
|
2023-03-28 18:30:29 +00:00
|
|
|
res = testclient.get("/profile/admin/settings")
|
2023-03-16 17:39:28 +00:00
|
|
|
testclient.post(
|
|
|
|
"/profile/invalid/settings",
|
2023-03-28 18:30:29 +00:00
|
|
|
{
|
|
|
|
"action": "password-initialization-mail",
|
|
|
|
"csrf_token": res.form["csrf_token"].value,
|
|
|
|
},
|
2023-03-16 17:39:28 +00:00
|
|
|
status=404,
|
|
|
|
)
|
|
|
|
assert len(smtpd.messages) == 0
|
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_password_reset_invalid_user(smtpd, testclient, backend, logged_admin):
|
2023-03-16 17:39:28 +00:00
|
|
|
assert len(smtpd.messages) == 0
|
2023-03-28 18:30:29 +00:00
|
|
|
res = testclient.get("/profile/admin/settings")
|
2023-03-16 17:39:28 +00:00
|
|
|
testclient.post(
|
2023-03-28 18:30:29 +00:00
|
|
|
"/profile/invalid/settings",
|
|
|
|
{"action": "password-reset-mail", "csrf_token": res.form["csrf_token"].value},
|
|
|
|
status=404,
|
2023-03-16 17:39:28 +00:00
|
|
|
)
|
|
|
|
assert len(smtpd.messages) == 0
|
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_delete_invalid_user(testclient, backend, logged_admin):
|
2023-03-28 18:30:29 +00:00
|
|
|
res = testclient.get("/profile/admin/settings")
|
|
|
|
testclient.post(
|
|
|
|
"/profile/invalid/settings",
|
|
|
|
{"action": "delete", "csrf_token": res.form["csrf_token"].value},
|
|
|
|
status=404,
|
|
|
|
)
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_impersonate_invalid_user(testclient, backend, logged_admin):
|
2023-03-16 17:39:28 +00:00
|
|
|
testclient.get("/impersonate/invalid", status=404)
|
|
|
|
|
|
|
|
|
2023-03-28 18:30:29 +00:00
|
|
|
def test_invalid_form_request(testclient, logged_admin):
|
|
|
|
res = testclient.get("/profile/admin/settings")
|
|
|
|
res = res.form.submit(name="action", value="invalid-action", status=400)
|
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_password_reset_email(smtpd, testclient, backend, logged_admin):
|
2023-04-09 09:37:04 +00:00
|
|
|
u = models.User(
|
2023-02-05 17:57:18 +00:00
|
|
|
formatted_name="Temp User",
|
|
|
|
family_name="Temp",
|
|
|
|
user_name="temp",
|
|
|
|
email="john@doe.com",
|
2023-04-10 19:42:14 +00:00
|
|
|
password="correct horse battery staple",
|
2023-03-16 17:39:28 +00:00
|
|
|
)
|
|
|
|
u.save()
|
|
|
|
|
|
|
|
res = testclient.get("/profile/temp/settings", status=200)
|
|
|
|
res.mustcontain("If the user has forgotten his password")
|
|
|
|
res.mustcontain("Send")
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="password-reset-mail")
|
|
|
|
assert (
|
|
|
|
"success",
|
|
|
|
"A password reset link has been sent at the user email address. "
|
|
|
|
"It should be received within a few minutes.",
|
|
|
|
) in res.flashes
|
|
|
|
assert len(smtpd.messages) == 1
|
2023-05-05 08:53:48 +00:00
|
|
|
assert smtpd.messages[0]["X-RcptTo"] == "john@doe.com"
|
2023-03-16 17:39:28 +00:00
|
|
|
|
|
|
|
u.delete()
|
|
|
|
|
|
|
|
|
|
|
|
@mock.patch("smtplib.SMTP")
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_password_reset_email_failed(SMTP, smtpd, testclient, backend, logged_admin):
|
2023-03-16 17:39:28 +00:00
|
|
|
SMTP.side_effect = mock.Mock(side_effect=OSError("unit test mail error"))
|
2023-04-09 09:37:04 +00:00
|
|
|
u = models.User(
|
2023-02-05 17:57:18 +00:00
|
|
|
formatted_name="Temp User",
|
|
|
|
family_name="Temp",
|
|
|
|
user_name="temp",
|
|
|
|
email="john@doe.com",
|
2023-04-10 19:42:14 +00:00
|
|
|
password=["correct horse battery staple"],
|
2023-03-16 17:39:28 +00:00
|
|
|
)
|
|
|
|
u.save()
|
|
|
|
|
|
|
|
res = testclient.get("/profile/temp/settings", status=200)
|
|
|
|
res.mustcontain("If the user has forgotten his password")
|
|
|
|
res.mustcontain("Send")
|
|
|
|
|
|
|
|
res = res.form.submit(
|
|
|
|
name="action", value="password-reset-mail", expect_errors=True
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"success",
|
|
|
|
"A password reset link has been sent at the user email address. "
|
|
|
|
"It should be received within a few minutes.",
|
|
|
|
) not in res.flashes
|
|
|
|
assert ("error", "Could not send the password reset email") in res.flashes
|
|
|
|
assert len(smtpd.messages) == 0
|
|
|
|
|
|
|
|
u.delete()
|
|
|
|
|
|
|
|
|
2023-03-22 07:52:00 +00:00
|
|
|
def test_admin_bad_request(testclient, logged_admin):
|
2023-03-16 17:39:28 +00:00
|
|
|
testclient.post("/profile/admin/settings", {"action": "foobar"}, status=400)
|
|
|
|
testclient.get("/profile/foobar/settings", status=404)
|
|
|
|
|
|
|
|
|
|
|
|
def test_edition_permission(
|
|
|
|
testclient,
|
|
|
|
logged_user,
|
|
|
|
admin,
|
|
|
|
):
|
|
|
|
testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = []
|
|
|
|
testclient.get("/profile/user/settings", status=403)
|
|
|
|
|
|
|
|
testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = ["edit_self"]
|
|
|
|
testclient.get("/profile/user/settings", status=200)
|
2022-11-01 11:25:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_account_locking(
|
|
|
|
testclient,
|
|
|
|
backend,
|
|
|
|
logged_admin,
|
|
|
|
user,
|
|
|
|
):
|
|
|
|
res = testclient.get("/profile/user/settings")
|
|
|
|
assert not user.lock_date
|
|
|
|
assert not user.locked
|
|
|
|
res.mustcontain("Lock the account")
|
|
|
|
res.mustcontain(no="Unlock")
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="lock")
|
|
|
|
user = models.User.get(id=user.id)
|
|
|
|
assert user.lock_date <= datetime.datetime.now(datetime.timezone.utc)
|
|
|
|
assert user.locked
|
|
|
|
res.mustcontain("The account has been locked")
|
|
|
|
res.mustcontain(no="Lock the account")
|
|
|
|
res.mustcontain("Unlock")
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="unlock")
|
|
|
|
user = models.User.get(id=user.id)
|
|
|
|
assert not user.lock_date
|
|
|
|
assert not user.locked
|
|
|
|
assert "The account has been unlocked"
|
|
|
|
res.mustcontain("Lock the account")
|
|
|
|
res.mustcontain(no="Unlock")
|
|
|
|
|
|
|
|
|
|
|
|
def test_lock_date(
|
|
|
|
testclient,
|
|
|
|
backend,
|
|
|
|
logged_admin,
|
|
|
|
user,
|
|
|
|
):
|
|
|
|
res = testclient.get("/profile/user/settings", status=200)
|
|
|
|
assert not user.lock_date
|
|
|
|
assert not user.locked
|
|
|
|
|
|
|
|
expiration_datetime = datetime.datetime.now(datetime.timezone.utc).replace(
|
|
|
|
second=0, microsecond=0
|
|
|
|
) + datetime.timedelta(days=30)
|
|
|
|
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M")
|
|
|
|
res = res.form.submit(name="action", value="edit").follow()
|
|
|
|
user = models.User.get(id=user.id)
|
|
|
|
assert user.lock_date == expiration_datetime
|
|
|
|
assert not user.locked
|
|
|
|
assert res.form["lock_date"].value == expiration_datetime.strftime("%Y-%m-%d %H:%M")
|
|
|
|
|
|
|
|
expiration_datetime = datetime.datetime.now(datetime.timezone.utc).replace(
|
|
|
|
second=0, microsecond=0
|
|
|
|
) - datetime.timedelta(days=30)
|
|
|
|
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M")
|
|
|
|
res = res.form.submit(name="action", value="edit").follow()
|
|
|
|
user = models.User.get(id=user.id)
|
|
|
|
assert user.lock_date == expiration_datetime
|
|
|
|
assert user.locked
|
|
|
|
|
|
|
|
res = res.form.submit(name="action", value="unlock")
|
|
|
|
user = models.User.get(id=user.id)
|
|
|
|
assert not user.lock_date
|
|
|
|
assert not user.locked
|
|
|
|
|
|
|
|
|
|
|
|
def test_account_limit_values(
|
|
|
|
testclient,
|
|
|
|
backend,
|
|
|
|
logged_admin,
|
|
|
|
user,
|
|
|
|
):
|
|
|
|
res = testclient.get("/profile/user/settings", status=200)
|
|
|
|
assert not user.lock_date
|
|
|
|
assert not user.locked
|
|
|
|
|
|
|
|
expiration_datetime = datetime.datetime.max.replace(
|
|
|
|
microsecond=0, tzinfo=datetime.timezone.utc
|
|
|
|
)
|
|
|
|
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
res = res.form.submit(name="action", value="edit").follow()
|
|
|
|
user = models.User.get(id=user.id)
|
|
|
|
assert user.lock_date == expiration_datetime
|
|
|
|
assert not user.locked
|