2022-11-01 11:25:21 +00:00
import datetime
2024-10-09 13:55:01 +00:00
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
2024-03-15 18:58:06 +00:00
from canaille . app import models
2023-03-16 17:39:28 +00:00
2024-04-07 18:12:13 +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 ]
2023-07-24 16:07:35 +00:00
assert " readonly " in res . form [ " groups " ] . attrs
assert " readonly " in res . form [ " user_name " ] . attrs
2023-03-16 17:39:28 +00:00
2023-02-05 17:57:18 +00:00
res . form [ " user_name " ] = " toto "
2023-07-30 21:08:17 +00:00
res = res . form . submit ( name = " action " , value = " edit-settings " )
2023-07-24 16:07:35 +00:00
assert res . flashes == [ ( " error " , " Profile edition failed. " ) ]
2024-04-14 20:51:58 +00:00
backend . reload ( logged_user )
2023-03-16 17:39:28 +00:00
2023-11-15 17:20:13 +00:00
assert logged_user . user_name == " user "
2023-03-16 17:39:28 +00:00
2024-04-14 20:51:58 +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 ]
2024-04-07 18:12:13 +00:00
assert backend . check_user_password ( logged_user , " correct horse battery staple " ) [ 0 ]
2023-03-16 17:39:28 +00:00
2023-11-15 17:20:13 +00:00
logged_user . user_name = " user "
2024-04-14 18:31:43 +00:00
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 ) :
2024-04-08 12:37:59 +00:00
""" Tests that one can remove a group from a user. """
2024-04-08 12:15:28 +00:00
foo_group . members = [ user , logged_admin ]
2024-04-14 18:31:43 +00:00
backend . save ( foo_group )
2024-04-14 20:51:58 +00:00
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. " ) ]
2024-04-14 20:51:58 +00:00
backend . reload ( user )
2024-04-08 12:15:28 +00:00
assert foo_group not in user . groups
2024-04-14 20:51:58 +00:00
backend . reload ( foo_group )
backend . reload ( logged_admin )
2024-04-08 12:15:28 +00:00
assert foo_group . members == [ logged_admin ]
2024-04-08 12:37:59 +00:00
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. "
)
2024-04-14 20:51:58 +00:00
backend . reload ( user )
2024-04-08 12:37:59 +00:00
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 ) :
2024-11-14 15:10:43 +00:00
current_app . config [ " CANAILLE " ] [ " ENABLE_PASSWORD_COMPROMISSION_CHECK " ] = True
2024-11-05 15:59:03 +00:00
""" 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 \n CAA6D483CC3887DCE9D1B8EB91408F1EA7A:3 \r \n AD6438836DBE526AA231ABDE2D0EEF74D42:3 \r \n 8289894DDB6317178960AB5AE98B81BBF97:1 \r \n 5FF0B6F9EAC40D5CA7B4DAA7B64F0E6F4AA:2 \r \n "
api_get . return_value = Response
2024-11-05 15:59:03 +00:00
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 )
2024-11-13 15:21:52 +00:00
with_different_values (
2024-11-19 13:49:36 +00:00
" password " ,
2024-11-13 15:21:52 +00:00
" This password appears on public compromission databases and is not secure. " ,
)
with_different_values (
2024-11-19 13:49:36 +00:00
" azertyuiop123 " ,
2024-11-13 15:21:52 +00:00
" This password appears on public compromission databases and is not secure. " ,
)
2024-11-05 15:59:03 +00:00
with_different_values ( " a " * 1000 , ' data-percent= " 25 " ' )
with_different_values ( " i ' m a little pea " , ' data-percent= " 100 " ' )
2024-11-07 14:51:21 +00:00
@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
2024-11-07 14:51:21 +00:00
) :
2024-11-14 15:10:43 +00:00
current_app . config [ " CANAILLE " ] [ " ENABLE_PASSWORD_COMPROMISSION_CHECK " ] = True
2024-11-07 14:51:21 +00:00
api_get . side_effect = mock . Mock ( side_effect = Exception ( ) )
2024-11-12 08:18:55 +00:00
current_app . config [ " CANAILLE " ] [ " ACL " ] [ " ADMIN " ] [ " FILTER " ] = { " groups " : " admins " }
2024-11-07 14:51:21 +00:00
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
2024-11-07 14:51:21 +00:00
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 ]
2024-11-12 08:18:55 +00:00
@mock.patch ( " requests.api.get " )
2024-11-13 15:21:52 +00:00
def test_compromised_password_validator_with_failure_of_api_request_and_success_mail_to_admin_from_settings_form (
2024-11-20 13:32:25 +00:00
api_get , testclient , backend , user , logged_user , caplog , smtpd
2024-11-12 08:18:55 +00:00
) :
2024-11-14 15:10:43 +00:00
current_app . config [ " CANAILLE " ] [ " ENABLE_PASSWORD_COMPROMISSION_CHECK " ] = True
2024-11-12 08:18:55 +00:00
api_get . side_effect = mock . Mock ( side_effect = Exception ( ) )
2024-11-13 15:21:52 +00:00
res = testclient . get ( " /profile/user/settings " , status = 200 )
2024-11-12 08:18:55 +00:00
2024-11-13 15:21:52 +00:00
res . form . user = user
res . form [ " password1 " ] = " 123456789 "
res . form [ " password2 " ] = " 123456789 "
2024-11-12 15:50:51 +00:00
2024-11-13 15:21:52 +00:00
res = res . form . submit ( name = " action " , value = " edit-settings " )
2024-11-12 15:50:51 +00:00
2024-11-19 08:11:02 +00:00
assert (
" canaille " ,
logging . ERROR ,
" Password compromise investigation failed on HIBP API. " ,
) in caplog . record_tuples
2024-11-13 15:21:52 +00:00
assert (
" error " ,
" Password compromise investigation failed. Please contact the administrators. " ,
) in res . flashes
assert (
2024-11-20 13:32:25 +00:00
" info " ,
2024-11-13 15:21:52 +00:00
" We have informed your administrator about the failure of the password compromise investigation. " ,
) in res . flashes
assert ( " success " , " Profile updated successfully. " ) in res . flashes
2024-11-20 13:32:25 +00:00
assert len ( smtpd . messages ) == 1
2024-11-12 08:18:55 +00:00
@mock.patch ( " requests.api.get " )
2024-11-13 15:21:52 +00:00
def test_compromised_password_validator_with_failure_of_api_request_and_fail_to_send_mail_to_admin_from_settings_form (
2024-11-20 13:32:25 +00:00
api_get , testclient , backend , user , logged_user , caplog , smtpd
2024-11-12 08:18:55 +00:00
) :
2024-11-14 15:10:43 +00:00
current_app . config [ " CANAILLE " ] [ " ENABLE_PASSWORD_COMPROMISSION_CHECK " ] = True
2024-11-12 08:18:55 +00:00
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
2024-11-12 08:18:55 +00:00
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-20 13:32:25 +00:00
assert len ( smtpd . messages ) == 0
2024-11-12 08:18:55 +00:00
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 ,
2024-04-07 18:12:13 +00:00
backend ,
2023-03-16 17:39:28 +00:00
) :
res = testclient . get ( " /profile/user/settings " , status = 200 )
2023-12-18 17:06:03 +00:00
testclient . app . config [ " CANAILLE " ] [ " ACL " ] [ " DEFAULT " ] [ " READ " ] = [ ]
2023-03-16 17:39:28 +00:00
2023-07-30 21:08:17 +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 ( )
2024-04-14 20:51:58 +00:00
backend . reload ( logged_user )
2023-03-16 17:39:28 +00:00
2023-11-15 17:20:13 +00:00
assert logged_user . user_name == " user "
2024-04-07 18:12:13 +00:00
assert backend . check_user_password ( logged_user , " correct horse battery staple " ) [ 0 ]
2023-03-16 17:39:28 +00:00
2023-11-15 17:20:13 +00:00
logged_user . user_name = " user "
2024-04-14 18:31:43 +00:00
backend . save ( logged_user )
2023-03-16 17:39:28 +00:00
2024-10-09 13:55:01 +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 )
2024-11-05 14:44:25 +00:00
res . form [ " password1 " ] = " i ' m a little pea "
res . form [ " password2 " ] = " i ' m a little pea "
2023-03-16 17:39:28 +00:00
2023-07-30 21:08:17 +00:00
res = res . form . submit ( name = " action " , value = " edit-settings " ) . follow ( )
2023-03-16 17:39:28 +00:00
2024-04-14 20:51:58 +00:00
backend . reload ( logged_user )
2024-11-05 14:44:25 +00:00
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 )
2024-11-05 14:44:25 +00:00
res . form [ " password1 " ] = " i ' m a little chickpea "
res . form [ " password2 " ] = " i ' m a little chickpea "
2023-03-16 17:39:28 +00:00
2023-07-30 21:08:17 +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
2024-10-09 13:55:01 +00:00
assert (
" canaille " ,
2024-10-21 09:17:55 +00:00
logging . SECURITY ,
" Changed password in settings for user from unknown IP " ,
2024-10-09 13:55:01 +00:00
) in caplog . record_tuples
2023-03-16 17:39:28 +00:00
res = res . follow ( )
2024-04-14 20:51:58 +00:00
backend . reload ( logged_user )
2024-11-05 14:44:25 +00:00
assert backend . check_user_password ( logged_user , " i ' m a little chickpea " ) [ 0 ]
2023-03-16 17:39:28 +00:00
2024-04-07 18:12:13 +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 )
2024-11-05 14:44:25 +00:00
res . form [ " password1 " ] = " i ' m a little pea "
res . form [ " password2 " ] = " i ' m a little chickpea "
2023-03-16 17:39:28 +00:00
2023-07-30 21:08:17 +00:00
res = res . form . submit ( name = " action " , value = " edit-settings " , status = 200 )
2023-03-16 17:39:28 +00:00
2024-04-14 20:51:58 +00:00
backend . reload ( logged_user )
2024-04-07 18:12:13 +00:00
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 )
2024-11-05 14:44:25 +00:00
res . form [ " password1 " ] = " i ' m a little pea "
2023-03-16 17:39:28 +00:00
res . form [ " password2 " ] = " "
2023-07-30 21:08:17 +00:00
res = res . form . submit ( name = " action " , value = " edit-settings " , status = 200 )
2023-03-16 17:39:28 +00:00
2024-04-14 20:51:58 +00:00
backend . reload ( logged_user )
2024-04-07 18:12:13 +00:00
assert backend . check_user_password ( logged_user , " 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 " ,
2023-10-02 19:58:46 +00:00
emails = [ " john@doe.com " ] ,
2023-03-16 17:39:28 +00:00
)
2024-04-14 18:31:43 +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
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
2024-04-14 20:51:58 +00:00
backend . reload ( u )
2023-11-22 13:47:48 +00:00
u . password = " correct horse battery staple "
2024-04-14 18:31:43 +00:00
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 " )
2024-04-14 18:37:52 +00:00
backend . delete ( u )
2023-03-16 17:39:28 +00:00
@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 " ,
2023-10-02 19:58:46 +00:00
emails = [ " john@doe.com " ] ,
2023-03-16 17:39:28 +00:00
)
2024-04-14 18:31:43 +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
2024-04-14 18:37:52 +00:00
backend . delete ( u )
2023-03-16 17:39:28 +00:00
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 )
2024-04-12 10:12:08 +00:00
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
)
2024-04-14 18:31:43 +00:00
backend . save ( user )
2024-04-12 10:12:08 +00:00
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 )
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 " ,
2023-10-02 19:58:46 +00:00
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
)
2024-04-14 18:31:43 +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
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
2024-04-14 18:37:52 +00:00
backend . delete ( u )
2023-03-16 17:39:28 +00:00
@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 " ,
2023-10-02 19:58:46 +00:00
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
)
2024-04-14 18:31:43 +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
2024-04-14 18:37:52 +00:00
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 ) :
2024-03-15 18:55:12 +00:00
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 ,
2024-04-14 20:51:58 +00:00
backend ,
2023-03-16 17:39:28 +00:00
) :
2023-12-18 17:06:03 +00:00
testclient . app . config [ " CANAILLE " ] [ " ACL " ] [ " DEFAULT " ] [ " PERMISSIONS " ] = [ ]
2024-04-14 20:51:58 +00:00
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
2023-12-18 17:06:03 +00:00
testclient . app . config [ " CANAILLE " ] [ " ACL " ] [ " DEFAULT " ] [ " PERMISSIONS " ] = [ " edit_self " ]
2024-04-14 20:51:58 +00:00
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 " )
2023-07-06 16:43:37 +00:00
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 " )
2024-04-14 15:30:59 +00:00
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 " )
2024-04-14 15:30:59 +00:00
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
2023-07-06 16:43:37 +00:00
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 " )
2023-07-30 21:08:17 +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-05-26 15:44:15 +00:00
res = res . follow ( )
2024-04-14 15:30:59 +00:00
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 " )
2023-07-30 21:08:17 +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-05-26 15:44:15 +00:00
res = res . follow ( )
2024-04-14 15:30:59 +00:00
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
2024-04-14 18:31:43 +00:00
backend . save ( user )
2023-05-26 15:44:15 +00:00
res = testclient . get ( " /profile/user/settings " , status = 200 )
res . form [ " lock_date " ] = " "
2023-07-30 21:08:17 +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-05-26 15:44:15 +00:00
res = res . follow ( )
2024-04-14 20:51:58 +00:00
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 " )
2023-07-30 21:08:17 +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-05-26 15:44:15 +00:00
res = res . follow ( )
2024-04-14 15:30:59 +00:00
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
2023-11-22 10:30:30 +00:00
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. " )