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:
parent
8aa76dc5de
commit
2040d4b193
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user