1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2025-04-01 23:57:05 +00:00

add a quota test

This commit is contained in:
downtownallday 2024-09-07 10:49:46 -04:00
parent 8aa76dc5de
commit 2040d4b193
3 changed files with 204 additions and 42 deletions

View File

@ -34,11 +34,11 @@ mgmt_start() {
MGMT_ADMIN_PW="$(generate_password)"
delete_user "$MGMT_ADMIN_EMAIL"
record "[Creating a new account with admin rights for management tests]"
create_user "$MGMT_ADMIN_EMAIL" "$MGMT_ADMIN_PW" "admin"
MGMT_ADMIN_DN="$ATTR_DN"
record "Created: $MGMT_ADMIN_EMAIL at $MGMT_ADMIN_DN"
record "Created: $MGMT_ADMIN_EMAIL at $MGMT_ADMIN_DN"
}
mgmt_end() {
@ -191,7 +191,7 @@ mgmt_assert_privileges_add() {
mgmt_get_totp_token() {
local secret="$1"
local mru_token="$2"
TOTP_TOKEN="" # this is set to the acquired token on success
# the user would normally give the secret to an authenticator app
@ -202,7 +202,7 @@ mgmt_get_totp_token() {
record "[Get the current token for the secret '$secret']"
local count=0
while [ -z "$TOTP_TOKEN" -a $count -lt 10 ]; do
TOTP_TOKEN="$(totp_current_token "$secret" 2>>"$TEST_OF")"
if [ $? -ne 0 ]; then
@ -218,13 +218,13 @@ mgmt_get_totp_token() {
record "Success: token is '$TOTP_TOKEN'"
return 0
fi
let count+=1
done
record "Failed: timeout !"
TOTP_TOKEN=""
return 1
return 1
}
mgmt_mfa_status() {
@ -246,7 +246,7 @@ mgmt_totp_enable() {
# returns 1 if a REST error occured. $REST_ERROR has the message
# returns 2 if some other error occured
#
local user="$1"
local pw="$2"
local label="$3" # optional
@ -258,7 +258,7 @@ mgmt_totp_enable() {
if ! mgmt_mfa_status "$user" "$pw"; then
return 1
fi
TOTP_SECRET="$(/usr/bin/jq -r ".new_mfa.totp.secret" <<<"$REST_OUTPUT")"
if [ $? -ne 0 ]; then
record "Unable to obtain setup totp secret - is 'jq' installed?"
@ -271,11 +271,11 @@ mgmt_totp_enable() {
else
record "Found TOTP secret '$TOTP_SECRET'"
fi
if ! mgmt_get_totp_token "$TOTP_SECRET"; then
return 2
fi
# 2. enable TOTP
record "Enabling TOTP using the secret and token"
if ! mgmt_rest_as_user "POST" "/admin/mfa/totp/enable" "$user" "$pw" "secret=$TOTP_SECRET" "token=$TOTP_TOKEN" "label=$label"; then
@ -284,7 +284,7 @@ mgmt_totp_enable() {
else
record "Success: POST /mfa/totp/enable: '$REST_OUTPUT'"
fi
return 0
}
@ -318,7 +318,7 @@ mgmt_mfa_disable() {
local user="$1"
local pw="$2"
local mfa_id="$3"
record "[Disable MFA for $user]"
if [ "$mfa_id" == "all" ]; then
mfa_id=""
@ -327,7 +327,7 @@ mgmt_mfa_disable() {
if ! mgmt_mfa_status "$user" "$pw"; then
return 1
fi
mfa_id="$(/usr/bin/jq -r ".enabled_mfa[0].id" <<<"$REST_OUTPUT")"
if [ $? -ne 0 ]; then
record "Unable to use /usr/bin/jq - is it installed?"
@ -338,9 +338,9 @@ mgmt_mfa_disable() {
return 3
fi
fi
if ! mgmt_rest_as_user "POST" "/admin/mfa/disable" "$user" "$pw" "mfa-id=$mfa_id"
then
REST_ERROR="Failed: POST /admin/mfa/disable: $REST_ERROR"
@ -387,7 +387,7 @@ mgmt_assert_admin_login() {
if [ $code -ne 0 ]; then
test_failure "Unable to run jq ($code) on /admin/login json"
return 1
elif [ "$status" == "null" ]; then
test_failure "No 'status' in /admin/login json"
return 1
@ -395,8 +395,39 @@ mgmt_assert_admin_login() {
elif [ "$status" != "$expected_status" ]; then
test_failure "Expected a login status of '$expected_status', but got '$status'"
return 1
fi
fi
return 0
}
mgmt_get_user_quota() {
local user="$1"
record "[get user $user quota]"
mgmt_rest GET "/admin/mail/users/quota?email=$user"
local rc=$?
# REST_OUTPUT contains json, eg:
# { "email": "alice@somedomain.com", "quota": "5000" }
if [ $rc -eq 0 ]; then
# output to stdout the quota value
QUOTA="$(/usr/bin/jq -r ".quota" <<<"$REST_OUTPUT" 2>>$TEST_OF)"
if [ $? -ne 0 ]; then
record "could not obtain quota member from json using jq"
rc=1
fi
else
QUOTA="error"
fi
return $rc
}
mgmt_set_user_quota() {
local user="$1"
local quota="$2"
record "[set user $user quota to $quota]"
mgmt_rest POST "/admin/mail/users/quota" "email=$user" "quota=$quota"
local rc=$?
return $rc
}

View File

@ -8,7 +8,7 @@
##### details.
#####
#
#
# User management tests
_test_mixed_case() {
@ -29,16 +29,16 @@ _test_mixed_case() {
test_failure "Creation of a user with the same email address, but different case, succeeded."
test_failure "${REST_ERROR}"
fi
# create an alias group with alice in it
mgmt_assert_create_alias_group "${aliases[0]}" "${alices[1]}"
fi
# create local user bob
mgmt_assert_create_user "${bobs[0]}" "$bob_pw"
assert_check_logs
# send mail from bob to alice
#
@ -60,7 +60,7 @@ _test_mixed_case() {
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP ${alices[3]} "$alice_pw" 2>&1)"
assert_python_success $? "$output"
assert_check_logs
# send mail from alice as the alias to bob, ensure bob got it
#
record "[Mailing to bob as alias from alice]"
@ -87,7 +87,7 @@ test_mixed_case_users() {
# send mail from that user as the alias to the other user
test_start "mixed-case-users"
local alices=(alice@mgmt.somedomain.com
aLICE@mgmt.somedomain.com
aLiCe@mgmt.somedomain.com
@ -102,7 +102,7 @@ test_mixed_case_users() {
ALICE@mgmt.anotherdomain.com)
_test_mixed_case "${alices[*]}" "${bobs[*]}" "${aliases[*]}"
test_end
}
@ -115,7 +115,7 @@ test_mixed_case_domains() {
# send mail from that user as the alias to the other user
test_start "mixed-case-domains"
local alices=(alice@mgmt.somedomain.com
alice@MGMT.somedomain.com
alice@mgmt.SOMEDOMAIN.com
@ -128,9 +128,9 @@ test_mixed_case_domains() {
local aliases=(alice@MGMT.anotherdomain.com
alice@mgmt.ANOTHERDOMAIN.com
alice@Mgmt.AnotherDomain.Com)
_test_mixed_case "${alices[*]}" "${bobs[*]}" "${aliases[*]}"
test_end
}
@ -178,7 +178,7 @@ test_intl_domains() {
[ ! -z "$ATTR_DN" ] && record_search "$ATTR_DN"
else
record_search "$ATTR_DN"
# required aliases are automatically created and should
# have both mail addresses (idna and utf8)
get_attribute "$LDAP_ALIASES_BASE" "(mail=abuse@$intl_person_idna_domain)" "mail"
@ -191,7 +191,7 @@ test_intl_domains() {
test_failure "Require alias abuse@$intl_person_idna_domain expected to contain both idna and utf8 mail addresses"
record_search "$ATTR_DN"
fi
# ensure user is removed as is expected by the remaining tests
mgmt_delete_user "$intl_person_idna"
fi
@ -204,7 +204,7 @@ test_intl_domains() {
test_failure "No required alias should not exist for the $intl_person_domain domain"
record_search "$ATTR_DN"
fi
# create local users bob and mary
mgmt_assert_create_user "$bob" "$bob_pw"
mgmt_assert_create_user "$mary" "$mary_pw"
@ -212,7 +212,7 @@ test_intl_domains() {
# create intl alias with local user bob and intl_person in it
if mgmt_assert_create_alias_group "$alias" "$bob" "$intl_person"; then
# examine LDAP server to verify IDNA-encodings
# 1. the mail attribute for the alias should have both the
# idna and utf8 addresses
get_attribute "$LDAP_ALIASES_BASE" "(mail=$alias)" "mail"
@ -238,7 +238,7 @@ test_intl_domains() {
# re-create intl alias with local user bob only
mgmt_assert_create_alias_group "$alias" "$bob"
assert_check_logs
if ! have_test_failures; then
@ -300,18 +300,18 @@ test_totp() {
record "Expect a login failure..."
mgmt_assert_admin_login "$alice" "$alice_pw" "missing-totp-token"
fi
# logging into /admin/me with a password and a token should
# succeed, and an api_key generated
local api_key
if ! have_test_failures; then
if ! have_test_failures; then
record "Try using a password and a token to get the user api key, we may have to wait 30 seconds to get a new token..."
local old_totp_token="$TOTP_TOKEN"
if ! mgmt_get_totp_token "$TOTP_SECRET" "$TOTP_TOKEN"; then
test_failure "Could not obtain a new TOTP token"
else
# we have a new token, try logging in ...
# the token must be placed in the header "x-auth-token"
@ -331,7 +331,7 @@ test_totp() {
fi
# we should be able to login using the user's api key
if ! have_test_failures; then
if ! have_test_failures; then
record "[Use the session key to enum users]"
if ! mgmt_rest_as_user "GET" "/admin/mail/users?format=json" "$alice" "$api_key"; then
test_failure "Unable to use the session key to issue a rest call: $REST_ERROR"
@ -342,7 +342,7 @@ test_totp() {
# disable totp on the account - login should work with just the password
# and the ldap entry should not have the 'totpUser' objectClass
if ! have_test_failures; then
if ! have_test_failures; then
if mgmt_assert_mfa_disable "$alice" "$api_key"; then
mgmt_assert_admin_login "$alice" "$alice_pw" "ok"
fi
@ -354,19 +354,130 @@ test_totp() {
else
check_logs
fi
mgmt_assert_delete_user "$alice"
test_end
}
test_mailbox_quotas() {
test_start "mailbox-quotas"
# create standard user alice
local alice="alice@somedomain.com"
create_user "$alice" "alice"
# quota should be unlimited for newly added users
if ! mgmt_get_user_quota "$alice"; then
test_failure "Unable to get $alice's quota: $REST_ERROR"
elif [ "$QUOTA" != "0" -a "$QUOTA" != "unlimited" ]; then
test_failure "A newly created user should have unlimited quota"
fi
# get alice's current total number of messages. should be 0 unless
# the account was "archived"
local count_messages="$(doveadm -f json quota get -u "$alice" | jq -r '.[] | select(.type=="MESSAGE") | .value')"
record "$alice currently has $count_messages messages"
# set alice's quota to a small number
local quota_value="5K"
if ! mgmt_set_user_quota "$alice" "$quota_value"
then
test_failure "Unable to set $alice's quota: $REST_ERROR"
else
# read back the quota - make sure it's what we set
if ! mgmt_get_user_quota "$alice" || [ "$QUOTA" != "$quota_value" ]
then
test_failure "Setting quota failed - expected quota does not match current quota: $REST_OUTPUT $REST_ERROR QUOTA=$QUOTA"
else
record_search "(mail=$alice)"
fi
fi
if ! have_test_failures; then
# send messages large enough to exceed the quota
local output
local subjects=()
local msgidx=0
local body="$(python3 -c 'for i in range(0,int(512/4)): print("abc\n", end="")')"
local quota_exceeded="no"
while ! have_test_failures && [ $msgidx -lt 10 ]; do
record ""
record "[send msg $msgidx]"
local subj="msg$msgidx - $(generate_password)"
output="$($PYMAIL -smtp-debug -body-from-stdin -no-delete -subj "$subj" $PRIVATE_IP $alice alice <<<"$body" 2>&1)"
if ! assert_python_success $? "$output"; then
break
fi
# You'd expect that the send would fail when the quota is
# exceeded, but it doesn't. Postfix accepts it into it's
# queue, then bounces the message back to sender with
# delivery status notification (DSN) of 5.2.2 when it
# processes the queue.
#
# The debugging messages (turned on by the -smtp-debug
# argument) hold the internal postfix message id, so
# extract that, then grep the logs to see if the message
# was bounced due to 5.2.2.
local postid="$(awk '/^data: .* queued as/ { match($0," as "); print substr($0,RSTART+4,10); exit }' <<<"$output" 2>>$TEST_OF)"
record "Extracted POSTID=$postid"
if [ ! -z "$postid" ]; then
/usr/sbin/postqueue -f >>"$TEST_OF" 2>&1
flush_logs
record "[dovecot and postfix logs for msg $msgidx]"
record "logs: $(grep "$postid" /var/log/mail.log)"
if grep "$postid" /var/log/mail.log | grep "status=bounced" | grep -Fq "5.2.2"; then
# success - message was rejected
quota_exceeded="yes"
break
fi
fi
subjects+=( "$subj" )
let msgidx+=1
# doveadm quota get -u "$alice" >>"$TEST_OF" 2>&1
done
if ! have_test_failures && [ "$quota_exceeded" = "no" ]; then
test_failure "Quota restriction was not enforced by dovecot after sending $msgidx messages"
fi
# cleanup: delete the messages
msgidx=0
for subj in "${subjects[@]}"; do
record "[delete msg $msgidx]"
record "subj=$subj"
$PYMAIL -no-send -timeout 2 -subj "$subj" $PRIVATE_IP $alice alice >>$TEST_OF 2>&1
let msgidx+=1
done
# verify cleanup worked
local cur_count_messages="$(doveadm -f json quota get -u "$alice" | jq -r '.[] | select(.type=="MESSAGE") | .value')"
if [ $count_messages -ne $cur_count_messages ]; then
test_failure "Cleanup failed: test account $alice started with $count_messages but ended up with $cur_count_messages"
fi
fi
# cleanup: delete the test user
delete_user "$alice"
test_end
}
suite_start "management-users" mgmt_start
test_totp
test_mixed_case_domains
test_mixed_case_users
test_intl_domains
test_mailbox_quotas
suite_end mgmt_end

View File

@ -28,6 +28,8 @@ def usage():
print(" -no-send: don't send, just delete")
print(" -no-delete: don't delete, just send")
print(" -timeout <seconds>: how long to wait for message")
print(" -body-from-stdin: read the message body from stdin")
print(" -smtp-debug: output debugging messages")
print("");
sys.exit(1)
@ -48,6 +50,8 @@ delete_msg=True # login to imap and delete message
wait_timeout=30 # abandon timeout wiating for message delivery
wait_cycle_sleep=5 # delay between delivery checks
subject="Mail-in-a-Box Automated Test Message " + uuid.uuid4().hex # message subject
body_from_stdin=False
smtp_debug=False
# process command line
argi=1
@ -81,6 +85,12 @@ while argi<len(sys.argv):
elif arg=="-timeout" and arg_remaining>1:
wait_timeout=int(sys.argv[argi+1])
argi+=2
elif arg=="-body-from-stdin":
body_from_stdin = True
argi+=1
elif arg=="-smtp-debug":
smtp_debug = True
argi+=1
else:
usage()
@ -100,14 +110,20 @@ headerfrom = if_unset(headerfrom, emailfrom)
emailto = if_unset(emailto, login)
emailto_pw = if_unset(emailto_pw, pw)
if body_from_stdin:
body=sys.stdin.readlines()
else:
body=['This is a test message. It should be automatically deleted by the test script.']
msg = """From: {headerfrom}
To: {emailto}
Subject: {subject}
This is a test message. It should be automatically deleted by the test script.""".format(
{body}""".format(
headerfrom=headerfrom,
emailto=emailto,
subject=subject,
body=''.join(body)
)
def imap_login(host, login, pw):
@ -162,7 +178,8 @@ def smtp_login(host, login, pw, port):
server.starttls()
else:
server = smtplib.SMTP_SSL(host)
#server.set_debuglevel(1)
if smtp_debug:
server.set_debuglevel(1)
# Verify that the EHLO name matches the server's reverse DNS.
ipaddr = socket.gethostbyname(host) # IPv4 only!
@ -191,7 +208,10 @@ def smtp_login(host, login, pw, port):
if send_msg:
# Attempt to send a mail.
server = smtp_login(host, login, pw, port)
server.sendmail(emailfrom, [emailto], msg)
# sendmail: "If this method does not raise an exception, it returns a dictionary, with one entry for each recipient that was refused. Each entry contains a tuple of the SMTP error code and the accompanying error message sent by the server."
errors = server.sendmail(emailfrom, [emailto], msg)
#print(errors)
#if errors: raise ValueError(errors)
server.quit()
print("SMTP submission is OK.")