canaille-globuzma/tests/core/test_profile_settings.py

734 lines
24 KiB
Python
Raw Normal View History

2022-11-01 11:25:21 +00:00
import datetime
import logging
2023-03-16 17:39:28 +00:00
from unittest import mock
2024-10-28 21:17:47 +00:00
from flask import current_app
2023-08-13 20:08:28 +00:00
from flask import g
2023-03-16 17:39:28 +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, backend):
2023-03-16 17:39:28 +00:00
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 "readonly" in res.form["groups"].attrs
assert "readonly" in res.form["user_name"].attrs
2023-03-16 17:39:28 +00:00
res.form["user_name"] = "toto"
res = res.form.submit(name="action", value="edit-settings")
assert res.flashes == [("error", "Profile edition failed.")]
backend.reload(logged_user)
2023-03-16 17:39:28 +00:00
assert logged_user.user_name == "user"
2023-03-16 17:39:28 +00:00
backend.reload(foo_group)
backend.reload(bar_group)
2023-03-16 17:39:28 +00:00
assert logged_user.groups == [foo_group]
assert foo_group.members == [logged_user]
assert bar_group.members == [admin]
assert backend.check_user_password(logged_user, "correct horse battery staple")[0]
2023-03-16 17:39:28 +00:00
logged_user.user_name = "user"
backend.save(logged_user)
2023-03-16 17:39:28 +00:00
2024-04-08 12:15:28 +00:00
def test_group_removal(testclient, logged_admin, user, foo_group, backend):
"""Tests that one can remove a group from a user."""
2024-04-08 12:15:28 +00:00
foo_group.members = [user, logged_admin]
backend.save(foo_group)
backend.reload(user)
2024-04-08 12:15:28 +00:00
assert foo_group in user.groups
res = testclient.get("/profile/user/settings", status=200)
res.form["groups"] = []
res = res.form.submit(name="action", value="edit-settings")
assert res.flashes == [("success", "Profile updated successfully.")]
backend.reload(user)
2024-04-08 12:15:28 +00:00
assert foo_group not in user.groups
backend.reload(foo_group)
backend.reload(logged_admin)
2024-04-08 12:15:28 +00:00
assert foo_group.members == [logged_admin]
def test_empty_group_removal(testclient, logged_admin, user, foo_group, backend):
"""Tests that one cannot remove a group from a user, when was the last
person in the group.
This is because LDAP groups cannot be empty because
groupOfNames.member is a MUST attribute.
https://www.rfc-editor.org/rfc/rfc2256.html#section-7.10
"""
assert foo_group in user.groups
res = testclient.get("/profile/user/settings", status=200)
res.form["groups"] = []
res = res.form.submit(name="action", value="edit-settings")
assert res.flashes == [("error", "Profile edition failed.")]
res.mustcontain(
"The group 'foo' cannot be removed, because it must have at least one user left."
)
backend.reload(user)
assert foo_group in user.groups
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.")
2024-10-28 21:17:47 +00:00
def test_profile_settings_minimum_password_length_validation(testclient, logged_user):
"""Tests minimum length of password defined in configuration."""
def with_different_values(password, length):
current_app.config["CANAILLE"]["MIN_PASSWORD_LENGTH"] = length
res = testclient.get("/profile/user/settings")
res = testclient.post(
"/profile/user/settings",
{
"csrf_token": res.form["csrf_token"].value,
"password1": password,
},
headers={
"HX-Request": "true",
"HX-Trigger-Name": "password1",
},
)
res.mustcontain(f"Field must be at least {length} characters long.")
with_different_values("short", 8)
with_different_values("aa", 3)
with_different_values("1234567890123456789", 20)
def test_profile_settings_too_long_password(testclient, logged_user):
"""Tests maximum length of password."""
def with_different_values(password, length, message):
current_app.config["CANAILLE"]["MAX_PASSWORD_LENGTH"] = length
res = testclient.get("/profile/user/settings")
res = testclient.post(
"/profile/user/settings",
{
"csrf_token": res.form["csrf_token"].value,
"password1": password,
},
headers={
"HX-Request": "true",
"HX-Trigger-Name": "password1",
},
)
res.mustcontain(message)
with_different_values(
"a" * 1001, 1000, "Field cannot be longer than 1000 characters."
)
with_different_values("a1!A" * 250, 1000, 'data-percent="25"')
with_different_values("a" * 501, 500, "Field cannot be longer than 500 characters.")
with_different_values("a1!A" * 125, 500, 'data-percent="25"')
with_different_values("a" * 4097, 0, "Field cannot be longer than 4096 characters.")
with_different_values(
"a" * 4097, None, "Field cannot be longer than 4096 characters."
)
with_different_values(
"a" * 4097, 5000, "Field cannot be longer than 4096 characters."
)
2024-11-19 13:49:36 +00:00
@mock.patch("requests.api.get")
def test_profile_settings_compromised_password(api_get, testclient, logged_user):
current_app.config["CANAILLE"]["ENABLE_PASSWORD_COMPROMISSION_CHECK"] = True
"""Tests if password is compromised."""
2024-11-19 13:49:36 +00:00
# This content simulates a result from the hibp api containing the suffixes of the following password hashes: 'password', '987654321', 'correct horse battery staple', 'zxcvbn123', 'azertyuiop123'
class Response:
content = b"1E4C9B93F3F0682250B6CF8331B7EE68FD8:3\r\nCAA6D483CC3887DCE9D1B8EB91408F1EA7A:3\r\nAD6438836DBE526AA231ABDE2D0EEF74D42:3\r\n8289894DDB6317178960AB5AE98B81BBF97:1\r\n5FF0B6F9EAC40D5CA7B4DAA7B64F0E6F4AA:2\r\n"
api_get.return_value = Response
def with_different_values(password, message):
res = testclient.get("/profile/user/settings")
res = testclient.post(
"/profile/user/settings",
{
"csrf_token": res.form["csrf_token"].value,
"password1": password,
},
headers={
"HX-Request": "true",
"HX-Trigger-Name": "password1",
},
)
res.mustcontain(message)
with_different_values(
2024-11-19 13:49:36 +00:00
"password",
"This password appears on public compromission databases and is not secure.",
)
with_different_values(
2024-11-19 13:49:36 +00:00
"azertyuiop123",
"This password appears on public compromission databases and is not secure.",
)
with_different_values("a" * 1000, 'data-percent="25"')
with_different_values("i'm a little pea", 'data-percent="100"')
@mock.patch("requests.api.get")
def test_profile_settings_compromised_password_request_api_failed_but_password_updated(
2024-11-19 08:11:02 +00:00
api_get, testclient, logged_user, backend, caplog
):
current_app.config["CANAILLE"]["ENABLE_PASSWORD_COMPROMISSION_CHECK"] = True
api_get.side_effect = mock.Mock(side_effect=Exception())
current_app.config["CANAILLE"]["ACL"]["ADMIN"]["FILTER"] = {"groups": "admins"}
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "123456789"
res.form["password2"] = "123456789"
res = res.form.submit(name="action", value="edit-settings")
2024-11-19 08:11:02 +00:00
assert (
"canaille",
logging.ERROR,
"Password compromise investigation failed on HIBP API.",
) in caplog.record_tuples
assert (
"error",
"Password compromise investigation failed. Please contact the administrators.",
) in res.flashes
assert ("success", "Profile updated successfully.") in res.flashes
backend.reload(logged_user)
assert logged_user.user_name == "user"
assert backend.check_user_password(logged_user, "123456789")[0]
@mock.patch("requests.api.get")
def test_compromised_password_validator_with_failure_of_api_request_and_success_mail_to_admin_from_settings_form(
api_get, testclient, backend, user, logged_user, caplog
):
current_app.config["CANAILLE"]["ENABLE_PASSWORD_COMPROMISSION_CHECK"] = True
api_get.side_effect = mock.Mock(side_effect=Exception())
res = testclient.get("/profile/user/settings", status=200)
res.form.user = user
res.form["password1"] = "123456789"
res.form["password2"] = "123456789"
res = res.form.submit(name="action", value="edit-settings")
2024-11-19 08:11:02 +00:00
assert (
"canaille",
logging.ERROR,
"Password compromise investigation failed on HIBP API.",
) in caplog.record_tuples
assert (
"error",
"Password compromise investigation failed. Please contact the administrators.",
) in res.flashes
assert (
"success",
"We have informed your administrator about the failure of the password compromise investigation.",
) in res.flashes
assert ("success", "Profile updated successfully.") in res.flashes
@mock.patch("requests.api.get")
def test_compromised_password_validator_with_failure_of_api_request_and_fail_to_send_mail_to_admin_from_settings_form(
api_get, testclient, backend, user, logged_user, caplog
):
current_app.config["CANAILLE"]["ENABLE_PASSWORD_COMPROMISSION_CHECK"] = True
api_get.side_effect = mock.Mock(side_effect=Exception())
current_app.config["CANAILLE"]["SMTP"]["TLS"] = False
res = testclient.get("/profile/user/settings", status=200)
res.form.user = user
res.form["password1"] = "123456789"
res.form["password2"] = "123456789"
res = res.form.submit(name="action", value="edit-settings")
2024-11-19 08:11:02 +00:00
assert (
"canaille",
logging.ERROR,
"Password compromise investigation failed on HIBP API.",
) in caplog.record_tuples
assert (
"error",
"Password compromise investigation failed. Please contact the administrators.",
) in res.flashes
assert (
"error",
"An error occurred while communicating the incident to the administrators. "
"Please update your password as soon as possible. "
"If this still happens, please contact the administrators.",
) in res.flashes
assert ("success", "Profile updated successfully.") in res.flashes
2024-11-19 13:49:36 +00:00
@mock.patch("requests.api.get")
def test_compromised_password_validator_with_failure_of_api_request_without_smtp_or_without_admin_email_from_settings_form(
api_get, testclient, backend, user, logged_user, caplog
):
def without_smtp_or_without_admin_email(smtp, mail):
current_app.config["CANAILLE"]["ENABLE_PASSWORD_COMPROMISSION_CHECK"] = True
api_get.side_effect = mock.Mock(side_effect=Exception())
current_app.config["CANAILLE"]["SMTP"] = smtp
current_app.config["CANAILLE"]["ADMIN_EMAIL"] = mail
res = testclient.get("/profile/user/settings", status=200)
res.form.user = user
res.form["password1"] = "123456789"
res.form["password2"] = "123456789"
res = res.form.submit(name="action", value="edit-settings")
assert (
"canaille",
logging.ERROR,
"Password compromise investigation failed on HIBP API.",
) in caplog.record_tuples
assert (
"error",
"Password compromise investigation failed. Please contact the administrators.",
) not in res.flashes
without_smtp_or_without_admin_email(
None, current_app.config["CANAILLE"]["ADMIN_EMAIL"]
)
without_smtp_or_without_admin_email(current_app.config["CANAILLE"]["SMTP"], None)
2023-03-16 17:39:28 +00:00
def test_edition_without_groups(
testclient,
logged_user,
admin,
backend,
2023-03-16 17:39:28 +00:00
):
res = testclient.get("/profile/user/settings", status=200)
testclient.app.config["CANAILLE"]["ACL"]["DEFAULT"]["READ"] = []
2023-03-16 17:39:28 +00:00
res = res.form.submit(name="action", value="edit-settings")
2023-05-30 07:44:11 +00:00
assert res.flashes == [("success", "Profile updated successfully.")]
2023-03-16 17:39:28 +00:00
res = res.follow()
backend.reload(logged_user)
2023-03-16 17:39:28 +00:00
assert logged_user.user_name == "user"
assert backend.check_user_password(logged_user, "correct horse battery staple")[0]
2023-03-16 17:39:28 +00:00
logged_user.user_name = "user"
backend.save(logged_user)
2023-03-16 17:39:28 +00:00
def test_password_change(testclient, logged_user, backend, caplog):
2023-03-16 17:39:28 +00:00
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "i'm a little pea"
res.form["password2"] = "i'm a little pea"
2023-03-16 17:39:28 +00:00
res = res.form.submit(name="action", value="edit-settings").follow()
2023-03-16 17:39:28 +00:00
backend.reload(logged_user)
assert backend.check_user_password(logged_user, "i'm a little pea")[0]
2023-03-16 17:39:28 +00:00
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "i'm a little chickpea"
res.form["password2"] = "i'm a little chickpea"
2023-03-16 17:39:28 +00:00
res = res.form.submit(name="action", value="edit-settings")
2023-05-30 07:44:11 +00:00
assert ("success", "Profile updated successfully.") in res.flashes
assert (
"canaille",
logging.SECURITY,
"Changed password in settings for user from unknown IP",
) in caplog.record_tuples
2023-03-16 17:39:28 +00:00
res = res.follow()
backend.reload(logged_user)
assert backend.check_user_password(logged_user, "i'm a little chickpea")[0]
2023-03-16 17:39:28 +00:00
def test_password_change_fail(testclient, logged_user, backend):
2023-03-16 17:39:28 +00:00
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "i'm a little pea"
res.form["password2"] = "i'm a little chickpea"
2023-03-16 17:39:28 +00:00
res = res.form.submit(name="action", value="edit-settings", status=200)
2023-03-16 17:39:28 +00:00
backend.reload(logged_user)
assert backend.check_user_password(logged_user, "correct horse battery staple")[0]
2023-03-16 17:39:28 +00:00
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "i'm a little pea"
2023-03-16 17:39:28 +00:00
res.form["password2"] = ""
res = res.form.submit(name="action", value="edit-settings", status=200)
2023-03-16 17:39:28 +00:00
backend.reload(logged_user)
assert backend.check_user_password(logged_user, "correct horse battery staple")[0]
2023-03-16 17:39:28 +00:00
def test_password_initialization_mail(smtpd, testclient, backend, logged_admin):
u = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails=["john@doe.com"],
2023-03-16 17:39:28 +00:00
)
backend.save(u)
2023-03-16 17:39:28 +00:00
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",
2023-06-22 16:12:54 +00:00
"A password initialization link has been sent at the user email address. "
2023-03-16 17:39:28 +00:00
"It should be received within a few minutes.",
) in res.flashes
assert len(smtpd.messages) == 1
assert smtpd.messages[0]["X-RcptTo"] == "john@doe.com"
2023-03-16 17:39:28 +00:00
backend.reload(u)
u.password = "correct horse battery staple"
backend.save(u)
2023-03-16 17:39:28 +00:00
res = testclient.get("/profile/temp/settings", status=200)
res.mustcontain(no="This user does not have a password yet")
backend.delete(u)
2023-03-16 17:39:28 +00:00
@mock.patch("smtplib.SMTP")
def test_password_initialization_mail_send_fail(
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"))
u = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails=["john@doe.com"],
2023-03-16 17:39:28 +00:00
)
backend.save(u)
2023-03-16 17:39:28 +00:00
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",
2023-06-22 16:12:54 +00:00
"A password initialization link has been sent at the user email address. "
2023-03-16 17:39:28 +00:00
"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
backend.delete(u)
2023-03-16 17:39:28 +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
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
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
def test_impersonate_invalid_user(testclient, backend, logged_admin):
2023-03-16 17:39:28 +00:00
testclient.get("/impersonate/invalid", status=404)
def test_impersonate_locked_user(testclient, backend, logged_admin, user):
res = testclient.get("/profile/user/settings")
res.mustcontain("Impersonate")
user.lock_date = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
days=1
)
backend.save(user)
assert user.locked
res = testclient.get("/profile/user/settings")
res.mustcontain(no="Impersonate")
res = testclient.get("/impersonate/user", status=403)
res.mustcontain("Locked users cannot be impersonated.")
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)
def test_password_reset_email(smtpd, testclient, backend, logged_admin):
u = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails=["john@doe.com"],
2023-04-10 19:42:14 +00:00
password="correct horse battery staple",
2023-03-16 17:39:28 +00:00
)
backend.save(u)
2023-03-16 17:39:28 +00:00
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",
2023-06-22 16:12:54 +00:00
"A password reset link has been sent at the user email address. "
2023-03-16 17:39:28 +00:00
"It should be received within a few minutes.",
) in res.flashes
assert len(smtpd.messages) == 1
assert smtpd.messages[0]["X-RcptTo"] == "john@doe.com"
2023-03-16 17:39:28 +00:00
backend.delete(u)
2023-03-16 17:39:28 +00:00
@mock.patch("smtplib.SMTP")
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"))
u = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails=["john@doe.com"],
2023-11-22 13:49:51 +00:00
password="correct horse battery staple",
2023-03-16 17:39:28 +00:00
)
backend.save(u)
2023-03-16 17:39:28 +00:00
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",
2023-06-22 16:12:54 +00:00
"A password reset link has been sent at the user email address. "
2023-03-16 17:39:28 +00:00
"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
backend.delete(u)
2023-03-16 17:39:28 +00:00
2023-03-22 07:52:00 +00:00
def test_admin_bad_request(testclient, logged_admin):
res = testclient.get("/profile/admin/settings")
testclient.post(
"/profile/admin/settings",
{"action": "foobar", "csrf_token": res.form["csrf_token"].value},
status=400,
)
2023-03-16 17:39:28 +00:00
testclient.get("/profile/foobar/settings", status=404)
def test_edition_permission(
testclient,
logged_user,
admin,
backend,
2023-03-16 17:39:28 +00:00
):
testclient.app.config["CANAILLE"]["ACL"]["DEFAULT"]["PERMISSIONS"] = []
backend.reload(logged_user)
2023-06-28 15:56:49 +00:00
testclient.get("/profile/user/settings", status=404)
2023-03-16 17:39:28 +00:00
testclient.app.config["CANAILLE"]["ACL"]["DEFAULT"]["PERMISSIONS"] = ["edit_self"]
backend.reload(g.user)
2023-03-16 17:39:28 +00:00
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="confirm-lock")
2022-11-01 11:25:21 +00:00
res = res.form.submit(name="action", value="lock")
user = backend.get(models.User, id=user.id)
2022-11-01 11:25:21 +00:00
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 = backend.get(models.User, id=user.id)
2022-11-01 11:25:21 +00:00
assert not user.lock_date
assert not user.locked
res.mustcontain("The account has been unlocked")
2022-11-01 11:25:21 +00:00
res.mustcontain("Lock the account")
res.mustcontain(no="Unlock")
2023-05-26 15:44:15 +00:00
def test_past_lock_date(
2022-11-01 11:25:21 +00:00
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
2023-05-26 15:44:15 +00:00
) - datetime.timedelta(days=30)
2022-11-01 11:25:21 +00:00
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M")
res = res.form.submit(name="action", value="edit-settings")
2023-05-30 07:44:11 +00:00
assert res.flashes == [("success", "Profile updated successfully.")]
2023-05-26 15:44:15 +00:00
res = res.follow()
user = backend.get(models.User, id=user.id)
2022-11-01 11:25:21 +00:00
assert user.lock_date == expiration_datetime
2023-05-26 15:44:15 +00:00
assert user.locked
def test_future_lock_date(
testclient,
backend,
logged_admin,
user,
):
res = testclient.get("/profile/user/settings", status=200)
assert not user.lock_date
2022-11-01 11:25:21 +00:00
assert not user.locked
expiration_datetime = datetime.datetime.now(datetime.timezone.utc).replace(
second=0, microsecond=0
2023-05-26 15:44:15 +00:00
) + datetime.timedelta(days=30)
2022-11-01 11:25:21 +00:00
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M")
res = res.form.submit(name="action", value="edit-settings")
2023-05-30 07:44:11 +00:00
assert res.flashes == [("success", "Profile updated successfully.")]
2023-05-26 15:44:15 +00:00
res = res.follow()
user = backend.get(models.User, id=user.id)
2022-11-01 11:25:21 +00:00
assert user.lock_date == expiration_datetime
2023-05-26 15:44:15 +00:00
assert not user.locked
assert res.form["lock_date"].value == expiration_datetime.strftime("%Y-%m-%d %H:%M")
2022-11-01 11:25:21 +00:00
2023-05-26 15:44:15 +00:00
def test_empty_lock_date(
testclient,
backend,
logged_admin,
user,
):
expiration_datetime = datetime.datetime.now(datetime.timezone.utc).replace(
second=0, microsecond=0
) + datetime.timedelta(days=30)
user.lock_date = expiration_datetime
backend.save(user)
2023-05-26 15:44:15 +00:00
res = testclient.get("/profile/user/settings", status=200)
res.form["lock_date"] = ""
res = res.form.submit(name="action", value="edit-settings")
2023-05-30 07:44:11 +00:00
assert res.flashes == [("success", "Profile updated successfully.")]
2023-05-26 15:44:15 +00:00
res = res.follow()
backend.reload(user)
2022-11-01 11:25:21 +00:00
assert not user.lock_date
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-settings")
2023-05-30 07:44:11 +00:00
assert res.flashes == [("success", "Profile updated successfully.")]
2023-05-26 15:44:15 +00:00
res = res.follow()
user = backend.get(models.User, id=user.id)
2022-11-01 11:25:21 +00:00
assert user.lock_date == expiration_datetime
assert not user.locked
def test_edition_invalid_group(testclient, logged_admin, user, foo_group):
res = testclient.get("/profile/user/settings", status=200)
res.form["groups"].force_value("invalid")
res = res.form.submit(name="action", value="edit-settings")
assert res.flashes == [("error", "Profile edition failed.")]
res.mustcontain("Invalid choice(s): one or more data inputs could not be coerced.")