mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-11-11 02:41:33 +00:00
Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a2ffca169 | ||
|
|
1a5095c0f4 | ||
|
|
2418da031f | ||
|
|
9825c8d290 | ||
|
|
dc880d861c | ||
|
|
a03fd8aaad | ||
|
|
e3a90718c2 | ||
|
|
54a85ba439 | ||
|
|
4ced82d554 | ||
|
|
f83e02b740 | ||
|
|
04c9413ed9 | ||
|
|
5e98d8e279 | ||
|
|
4caecf77e0 | ||
|
|
37d304d075 | ||
|
|
25086a9b3f | ||
|
|
7a3f1cbd41 | ||
|
|
616717bc0d | ||
|
|
6fd271ee64 | ||
|
|
7459cc046a | ||
|
|
58dad73eee | ||
|
|
b21acd4787 | ||
|
|
4ab954dcd1 | ||
|
|
eefc17274e | ||
|
|
4878840bdd | ||
|
|
869bcb3c42 | ||
|
|
0bcde13500 | ||
|
|
867b9dddab | ||
|
|
a4326e6642 | ||
|
|
c5e89a9e03 | ||
|
|
45d31b3279 | ||
|
|
24780ea5fb | ||
|
|
38b031e10c | ||
|
|
e2fb833ceb | ||
|
|
e27cb83326 | ||
|
|
f810cecd3a | ||
|
|
cfad223824 | ||
|
|
6d20463acb | ||
|
|
1a42c19050 | ||
|
|
b9f8eb4c31 | ||
|
|
ce4150f921 | ||
|
|
7cc8cac705 | ||
|
|
3fd1d017cf | ||
|
|
8597954f91 | ||
|
|
2b4ffe8808 | ||
|
|
1911847c3b | ||
|
|
497872080b | ||
|
|
a0dc3447f8 | ||
|
|
e0640949a2 | ||
|
|
bf744413bd | ||
|
|
262de81da1 | ||
|
|
5f13bb2046 | ||
|
|
0a0686ef19 | ||
|
|
24b91f6089 | ||
|
|
5705a48724 | ||
|
|
2f76230daf | ||
|
|
498434ee3d | ||
|
|
f662af4c50 | ||
|
|
8d4895a54d | ||
|
|
81d6b1f547 | ||
|
|
8db3cae826 | ||
|
|
d93f32072e | ||
|
|
7b658fa65c | ||
|
|
9f934af3f2 |
No files matched your search
@@ -3,6 +3,11 @@
|
|||||||
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||||
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||||
|
|
||||||
|
# Global IPv6 address that is no unique local address (fc00::/7, e.g. Docker bridges)
|
||||||
|
_has_public_ipv6() {
|
||||||
|
ip -6 -o addr show scope global 2>/dev/null | awk '{print $4}' | grep -qviE '^f[cd]'
|
||||||
|
}
|
||||||
|
|
||||||
# 1) Check if the host supports IPv6
|
# 1) Check if the host supports IPv6
|
||||||
get_ipv6_support() {
|
get_ipv6_support() {
|
||||||
# ---- helper: probe external IPv6 connectivity without DNS ----
|
# ---- helper: probe external IPv6 connectivity without DNS ----
|
||||||
@@ -37,6 +42,9 @@ get_ipv6_support() {
|
|||||||
if _probe_ipv6_connectivity; then
|
if _probe_ipv6_connectivity; then
|
||||||
DETECTED_IPV6=true
|
DETECTED_IPV6=true
|
||||||
echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
|
echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
|
||||||
|
elif _has_public_ipv6; then
|
||||||
|
DETECTED_IPV6=true
|
||||||
|
echo -e "${YELLOW}External IPv6 connectivity test failed, but the host has a public IPv6 address – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
|
||||||
else
|
else
|
||||||
DETECTED_IPV6=false
|
DETECTED_IPV6=false
|
||||||
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||||
@@ -192,9 +200,31 @@ EOF
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_set_enable_ipv6() {
|
||||||
|
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
|
||||||
|
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||||
|
sed -i "s/^ENABLE_IPV6=.*/ENABLE_IPV6=$1/" "$MAILCOW_CONF"
|
||||||
|
else
|
||||||
|
echo "ENABLE_IPV6=$1" >> "$MAILCOW_CONF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
export IPV6_BOOL=$1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_warn_ipv6_disabled() {
|
||||||
|
_has_public_ipv6 || return 0
|
||||||
|
ip -6 route show default 2>/dev/null | grep -qE '^default' || return 0
|
||||||
|
grep -Eq '"userland-proxy"[[:space:]]*:[[:space:]]*false' /etc/docker/daemon.json 2>/dev/null && return 0
|
||||||
|
echo -e "${LIGHT_RED}WARNING: ENABLE_IPV6=false, but this host has a public IPv6 address.${NC}"
|
||||||
|
echo -e "${YELLOW}Docker forwards IPv6 connections to mailcow with an internal source address, which Postfix trusts. This can make your server an open relay over IPv6.${NC}"
|
||||||
|
echo -e "${YELLOW}Set ENABLE_IPV6=true in mailcow.conf, or set \"userland-proxy\": false in /etc/docker/daemon.json and restart Docker.${NC}"
|
||||||
|
echo -e "${YELLOW}See https://docs.mailcow.email/post_installation/firststeps-disable_ipv6/${NC}"
|
||||||
|
sleep 5
|
||||||
|
}
|
||||||
|
|
||||||
# 3) Main wrapper for generate_config.sh and update.sh
|
# 3) Main wrapper for generate_config.sh and update.sh
|
||||||
configure_ipv6() {
|
configure_ipv6() {
|
||||||
# detect manual override if mailcow.conf is present
|
|
||||||
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||||
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
|
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
|
||||||
elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
|
elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
|
||||||
@@ -203,19 +233,23 @@ configure_ipv6() {
|
|||||||
MANUAL_SETTING=""
|
MANUAL_SETTING=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "${MANUAL_SETTING,,}" == "false" ]]; then
|
||||||
|
_set_enable_ipv6 false
|
||||||
|
echo "ENABLE_IPV6=false is set, skipping Docker IPv6 configuration."
|
||||||
|
_warn_ipv6_disabled
|
||||||
|
echo "IPv6 configuration complete: ENABLE_IPV6=false"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
get_ipv6_support
|
get_ipv6_support
|
||||||
|
|
||||||
# if user manually set it, check for mismatch
|
if [[ "$DETECTED_IPV6" != "true" && "${MANUAL_SETTING,,}" == "true" ]] && _has_public_ipv6; then
|
||||||
|
echo -e "${YELLOW}Keeping ENABLE_IPV6=true, the host has a public IPv6 address.${NC}"
|
||||||
|
DETECTED_IPV6=true
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$DETECTED_IPV6" != "true" ]]; then
|
if [[ "$DETECTED_IPV6" != "true" ]]; then
|
||||||
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
|
_set_enable_ipv6 false
|
||||||
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
|
||||||
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
|
|
||||||
else
|
|
||||||
echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
export IPV6_BOOL=false
|
|
||||||
fi
|
|
||||||
echo "Skipping Docker IPv6 configuration because host does not support IPv6."
|
echo "Skipping Docker IPv6 configuration because host does not support IPv6."
|
||||||
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
|
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
|
||||||
echo "IPv6 configuration complete: ENABLE_IPV6=false"
|
echo "IPv6 configuration complete: ENABLE_IPV6=false"
|
||||||
@@ -224,16 +258,7 @@ configure_ipv6() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
docker_daemon_edit
|
docker_daemon_edit
|
||||||
|
_set_enable_ipv6 true
|
||||||
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
|
|
||||||
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
|
||||||
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
|
|
||||||
else
|
|
||||||
echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
export IPV6_BOOL=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "IPv6 configuration complete: ENABLE_IPV6=true"
|
echo "IPv6 configuration complete: ENABLE_IPV6=true"
|
||||||
}
|
}
|
||||||
@@ -586,8 +586,8 @@ class DockerApi:
|
|||||||
if not user or id in ('user=', 'group='):
|
if not user or id in ('user=', 'group='):
|
||||||
return Response(content=json.dumps({ 'type': 'danger', 'msg': 'user and id required' }, indent=4), media_type="application/json")
|
return Response(content=json.dumps({ 'type': 'danger', 'msg': 'user and id required' }, indent=4), media_type="application/json")
|
||||||
action = 'doveadm acl set -u "$ACL_USER" -- "$folder" "$ACL_ID" $ACL_RIGHTS' if rights else 'doveadm acl delete -u "$ACL_USER" -- "$folder" "$ACL_ID"'
|
action = 'doveadm acl set -u "$ACL_USER" -- "$folder" "$ACL_ID" $ACL_RIGHTS' if rights else 'doveadm acl delete -u "$ACL_USER" -- "$folder" "$ACL_ID"'
|
||||||
script = ('set -o pipefail; doveadm mailbox list -u "$ACL_USER" | while IFS= read -r folder; do '
|
script = ('set -o pipefail; doveadm -f tab mailbox status -u "$ACL_USER" guid "*" | tail -n +2 | cut -f1 | while IFS= read -r folder; do '
|
||||||
'case "$folder" in Shared/*) continue;; esac; ' + action + ' || exit 1; done && doveadm acl recalc -u "$ACL_USER"')
|
'case "$folder" in Shared|Shared/*) continue;; esac; ' + action + ' || exit 1; done && doveadm acl recalc -u "$ACL_USER"')
|
||||||
acl_return = container.exec_run(["/bin/bash", "-c", script],
|
acl_return = container.exec_run(["/bin/bash", "-c", script],
|
||||||
environment={ 'ACL_USER': user, 'ACL_ID': id, 'ACL_RIGHTS': ' '.join(rights) })
|
environment={ 'ACL_USER': user, 'ACL_ID': id, 'ACL_RIGHTS': ' '.join(rights) })
|
||||||
return self.exec_run_handler('generic', acl_return)
|
return self.exec_run_handler('generic', acl_return)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ catch_non_zero() {
|
|||||||
${CMD} > /dev/null
|
${CMD} > /dev/null
|
||||||
EC=$?
|
EC=$?
|
||||||
if [ ${EC} -ne 0 ]; then
|
if [ ${EC} -ne 0 ]; then
|
||||||
echo "Command ${CMD} failed to execute, exit code was ${EC}"
|
echo "Command ${CMD//${REDISPASS}/***} failed to execute, exit code was ${EC}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
source /source_env.sh
|
source /source_env.sh
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
|||||||
|
|
||||||
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||||
|
|
||||||
RUN apk add --no-cache nginx \
|
RUN apk upgrade --no-cache && \
|
||||||
|
apk add --no-cache nginx \
|
||||||
python3 \
|
python3 \
|
||||||
py3-pip && \
|
py3-pip && \
|
||||||
pip install --upgrade pip && \
|
pip install --upgrade pip && \
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ WORKDIR /src
|
|||||||
ENV CGO_ENABLED=0 \
|
ENV CGO_ENABLED=0 \
|
||||||
GO111MODULE=on \
|
GO111MODULE=on \
|
||||||
NOOPT=1 \
|
NOOPT=1 \
|
||||||
VERSION=1.14.0
|
VERSION=1.14.5
|
||||||
|
|
||||||
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
||||||
cd /src/postfix-tlspol && \
|
cd /src/postfix-tlspol && \
|
||||||
|
|||||||
@@ -277,9 +277,34 @@ if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"DBL" {
|
"DBL" {
|
||||||
# override the defaults for DBL defined in modules.d/rbl.conf
|
# full copy of the DBL rule from modules.d/rbl.conf, pointed at DQS:
|
||||||
|
# the override include replaces the stock rule instead of merging into it
|
||||||
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||||
disable_monitoring = true;
|
disable_monitoring = true;
|
||||||
|
checks = ["emails", "dkim", "helo", "rdns", "replyto", "urls", "content_urls"];
|
||||||
|
no_ip = true;
|
||||||
|
ignore_defaults = true;
|
||||||
|
exclude_users = false;
|
||||||
|
emails_domainonly = true;
|
||||||
|
selector = {
|
||||||
|
from_smtp = "from('smtp'):domain";
|
||||||
|
from_mime = "from('mime'):domain";
|
||||||
|
mid = "header(Message-Id).regexp('@([^\.]+\.[^>]+)').last";
|
||||||
|
}
|
||||||
|
returncodes = {
|
||||||
|
DBL_SPAM = "127.0.1.2";
|
||||||
|
DBL_PHISH = "127.0.1.4";
|
||||||
|
DBL_MALWARE = "127.0.1.5";
|
||||||
|
DBL_BOTNET = "127.0.1.6";
|
||||||
|
DBL_ABUSE = "127.0.1.102";
|
||||||
|
DBL_ABUSE_REDIR = "127.0.1.103";
|
||||||
|
DBL_ABUSE_PHISH = "127.0.1.104";
|
||||||
|
DBL_ABUSE_MALWARE = "127.0.1.105";
|
||||||
|
DBL_ABUSE_BOTNET = "127.0.1.106";
|
||||||
|
DBL_PROHIBIT = "127.0.1.255";
|
||||||
|
DBL_BLOCKED_OPENRESOLVER = "127.255.255.254";
|
||||||
|
DBL_BLOCKED = "127.255.255.255";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"ZRD" {
|
"ZRD" {
|
||||||
ignore_defaults = true;
|
ignore_defaults = true;
|
||||||
|
|||||||
@@ -116,8 +116,8 @@ if ($result === false){
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
if ($result) {
|
if ($result) {
|
||||||
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $client_ip);
|
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $service . " from IP " . $client_ip);
|
||||||
set_sasl_log($post['username'], $client_ip, $post['service']);
|
set_sasl_log($post['username'], $client_ip, $service);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,11 +94,11 @@ location / {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /qhandler {
|
location /qhandler {
|
||||||
rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2;
|
rewrite ^/qhandler/([^/]+)/(.+?)/?$ /qhandler.php?action=$1&hash=$2;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /edit {
|
location /edit {
|
||||||
rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2;
|
rewrite ^/edit/([^/]+)/(.+?)/?$ /edit.php?$1=$2;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/v1/(.*)$ {
|
location ~ ^/api/v1/(.*)$ {
|
||||||
@@ -187,6 +187,12 @@ location /sogo-auth-verify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location ^~ /Microsoft-Server-ActiveSync {
|
location ^~ /Microsoft-Server-ActiveSync {
|
||||||
|
# Outlook for iOS/Android (Microsoft's sync service) sends EAS commands without credentials
|
||||||
|
# and only authenticates after a 401 Basic challenge. SOGo answers those with 403, so challenge here.
|
||||||
|
if ($http_authorization = "") {
|
||||||
|
add_header WWW-Authenticate 'Basic realm="SOGo"' always;
|
||||||
|
return 401;
|
||||||
|
}
|
||||||
auth_request /sogo-auth-verify;
|
auth_request /sogo-auth-verify;
|
||||||
auth_request_set $user $upstream_http_x_user;
|
auth_request_set $user $upstream_http_x_user;
|
||||||
auth_request_set $auth $upstream_http_x_auth;
|
auth_request_set $auth $upstream_http_x_auth;
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
MX_IMPLICIT {
|
|
||||||
expression = "MX_GOOD & MX_MISSING";
|
|
||||||
score = -0.01;
|
|
||||||
}
|
|
||||||
VIRUS_FOUND {
|
VIRUS_FOUND {
|
||||||
expression = "CLAM_VIRUS & !MAILCOW_WHITE";
|
expression = "CLAM_VIRUS & !MAILCOW_WHITE";
|
||||||
score = 2000.0;
|
score = 2000.0;
|
||||||
|
|||||||
@@ -23,24 +23,6 @@ symbols {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group "MX" {
|
|
||||||
"MX_INVALID" {
|
|
||||||
score = 0.5;
|
|
||||||
description = "No connectable MX";
|
|
||||||
one_shot = true;
|
|
||||||
}
|
|
||||||
"MX_MISSING" {
|
|
||||||
score = 2.0;
|
|
||||||
description = "No MX record";
|
|
||||||
one_shot = true;
|
|
||||||
}
|
|
||||||
"MX_GOOD" {
|
|
||||||
score = -0.01;
|
|
||||||
description = "MX was ok";
|
|
||||||
one_shot = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group "reputation" {
|
group "reputation" {
|
||||||
symbols = {
|
symbols = {
|
||||||
"IP_REPUTATION_HAM" {
|
"IP_REPUTATION_HAM" {
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ RCPT_MAILCOW_DOMAIN {
|
|||||||
symbols_set = ["RCPT_MAILCOW_DOMAIN"];
|
symbols_set = ["RCPT_MAILCOW_DOMAIN"];
|
||||||
}
|
}
|
||||||
|
|
||||||
WHITELISTED_FWD_HOST {
|
|
||||||
type = "ip";
|
|
||||||
map = "redis://WHITELISTED_FWD_HOST";
|
|
||||||
symbols_set = ["WHITELISTED_FWD_HOST"];
|
|
||||||
}
|
|
||||||
|
|
||||||
BULK_HEADER {
|
BULK_HEADER {
|
||||||
type = "content";
|
type = "content";
|
||||||
map = "${LOCAL_CONFDIR}/custom/bulk_header.map";
|
map = "${LOCAL_CONFDIR}/custom/bulk_header.map";
|
||||||
@@ -151,20 +145,6 @@ BAD_LANG {
|
|||||||
score = 5.0;
|
score = 5.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
BAZAAR_ABUSE_CH {
|
|
||||||
type = "selector";
|
|
||||||
selector = "attachments(hex,md5)";
|
|
||||||
map = "https://bazaar.abuse.ch/export/txt/md5/recent/";
|
|
||||||
score = 10.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
URLHAUS_ABUSE_CH {
|
|
||||||
type = "selector";
|
|
||||||
selector = "urls";
|
|
||||||
map = "https://urlhaus.abuse.ch/downloads/text_online/";
|
|
||||||
score = 10.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SMTP_LIMITED_ACCESS {
|
SMTP_LIMITED_ACCESS {
|
||||||
type = "user";
|
type = "user";
|
||||||
map = "redis://SMTP_LIMITED_ACCESS";
|
map = "redis://SMTP_LIMITED_ACCESS";
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
timeout = 8.0;
|
timeout = 8.0;
|
||||||
symbol_bad_mx = "MX_INVALID";
|
symbol_bad_mx = "MX_INVALID";
|
||||||
symbol_no_mx = "MX_MISSING";
|
|
||||||
symbol_good_mx = "MX_GOOD";
|
symbol_good_mx = "MX_GOOD";
|
||||||
expire = 86400;
|
expire = 86400;
|
||||||
key_prefix = "rmx";
|
key_prefix = "rmx";
|
||||||
|
|||||||
@@ -9,3 +9,5 @@ disable_monitoring = true;
|
|||||||
# instead of silently accepting the message without further processing.
|
# instead of silently accepting the message without further processing.
|
||||||
soft_reject_on_timeout = true;
|
soft_reject_on_timeout = true;
|
||||||
local_addrs = /etc/rspamd/custom/mailcow_networks.map;
|
local_addrs = /etc/rspamd/custom/mailcow_networks.map;
|
||||||
|
# Must not be lower than message_size_limit in postfix main.cf
|
||||||
|
max_message = 134217728;
|
||||||
@@ -362,6 +362,61 @@ rspamd_config:register_symbol({
|
|||||||
priority = 19
|
priority = 19
|
||||||
})
|
})
|
||||||
|
|
||||||
|
rspamd_config:register_symbol({
|
||||||
|
name = 'WHITELISTED_FWD_HOST',
|
||||||
|
type = 'prefilter',
|
||||||
|
score = 0.0,
|
||||||
|
callback = function(task)
|
||||||
|
local rspamd_logger = require "rspamd_logger"
|
||||||
|
local redis_params = rspamd_parse_redis_server('fwd_host')
|
||||||
|
local ip = task:get_from_ip()
|
||||||
|
|
||||||
|
if ip == nil or not ip:is_valid() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local from_ip_string = tostring(ip)
|
||||||
|
local ip_check_table = {'WHITELISTED_FWD_HOST', from_ip_string}
|
||||||
|
|
||||||
|
local maxbits = 128
|
||||||
|
local minbits = 7
|
||||||
|
if ip:get_version() == 4 then
|
||||||
|
maxbits = 32
|
||||||
|
minbits = 8
|
||||||
|
end
|
||||||
|
for i=maxbits,minbits,-1 do
|
||||||
|
table.insert(ip_check_table, ip:apply_mask(i):to_string() .. "/" .. i)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fwd_host_cb(err, data)
|
||||||
|
if err or type(data) ~= 'table' then
|
||||||
|
rspamd_logger.infox(rspamd_config, "fwd_host query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for _,v in pairs(data) do
|
||||||
|
if type(v) == 'string' then
|
||||||
|
task:insert_result('WHITELISTED_FWD_HOST', 1.0, from_ip_string)
|
||||||
|
task:set_flag('pass_all')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local redis_ret = rspamd_redis_make_request(task,
|
||||||
|
redis_params, -- connect params
|
||||||
|
'WHITELISTED_FWD_HOST', -- hash key
|
||||||
|
false, -- is write
|
||||||
|
fwd_host_cb, --callback
|
||||||
|
'HMGET', -- command
|
||||||
|
ip_check_table -- arguments
|
||||||
|
)
|
||||||
|
if not redis_ret then
|
||||||
|
rspamd_logger.infox(rspamd_config, "cannot check fwd_host redis map")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
priority = 18
|
||||||
|
})
|
||||||
|
|
||||||
rspamd_config:register_symbol({
|
rspamd_config:register_symbol({
|
||||||
name = 'TLS_HEADER',
|
name = 'TLS_HEADER',
|
||||||
type = 'postfilter',
|
type = 'postfilter',
|
||||||
@@ -390,9 +445,8 @@ rspamd_config:register_symbol({
|
|||||||
local rspamd_logger = require "rspamd_logger"
|
local rspamd_logger = require "rspamd_logger"
|
||||||
local redis_params = rspamd_parse_redis_server('taghandler')
|
local redis_params = rspamd_parse_redis_server('taghandler')
|
||||||
local rspamd_http = require "rspamd_http"
|
local rspamd_http = require "rspamd_http"
|
||||||
local rcpts = task:get_recipients('smtp')
|
local rcpts = task:get_recipients({'smtp', 'orig'})
|
||||||
local lua_util = require "lua_util"
|
local lua_util = require "lua_util"
|
||||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
|
||||||
|
|
||||||
local function remove_moo_tag()
|
local function remove_moo_tag()
|
||||||
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
||||||
@@ -417,8 +471,9 @@ rspamd_config:register_symbol({
|
|||||||
|
|
||||||
-- Check if recipient has a tag (contains '+')
|
-- Check if recipient has a tag (contains '+')
|
||||||
local tag = nil
|
local tag = nil
|
||||||
if tagged_rcpt ~= nil then
|
local base_user, tag_part = rcpt_user:match('^([^%+]+)%+(.+)$')
|
||||||
tag = tagged_rcpt
|
if base_user and tag_part then
|
||||||
|
tag = tag_part
|
||||||
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
|
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -498,8 +553,7 @@ rspamd_config:register_symbol({
|
|||||||
else
|
else
|
||||||
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
|
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
|
||||||
local sbj = task:get_header('Subject') or ''
|
local sbj = task:get_header('Subject') or ''
|
||||||
local tag_value = tag[1] and tag[1].options and tag[1].options[1] or ''
|
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag_value .. '] ' .. sbj)) .. '?='
|
|
||||||
task:set_milter_reply({
|
task:set_milter_reply({
|
||||||
remove_headers = {
|
remove_headers = {
|
||||||
['Subject'] = 1,
|
['Subject'] = 1,
|
||||||
@@ -907,6 +961,9 @@ rspamd_config:register_symbol({
|
|||||||
|
|
||||||
if not seen_cte and rewrite.need_rewrite_ct then
|
if not seen_cte and rewrite.need_rewrite_ct then
|
||||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', new_cte)
|
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', new_cte)
|
||||||
|
task:set_milter_reply({
|
||||||
|
add_headers = {['Content-Transfer-Encoding'] = new_cte}
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
-- End of headers
|
-- End of headers
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ foreach ($rcpts as $rcpt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip if domain is excluded
|
// Skip if domain is excluded
|
||||||
if (in_array($parsed_rcpt['domain'], $exclude_domains)) {
|
if (is_array($exclude_domains) && in_array($parsed_rcpt['domain'], $exclude_domains)) {
|
||||||
error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL);
|
error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,3 +14,19 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* mailcow: inline code in descriptions in the text colour instead of bold purple */
|
||||||
|
.swagger-ui .markdown code,
|
||||||
|
.swagger-ui .renderedMarkdown code {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 0.9em;
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
padding: 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark-mode .swagger-ui .markdown code,
|
||||||
|
html.dark-mode .swagger-ui .renderedMarkdown code {
|
||||||
|
color: inherit;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
@@ -1,79 +1,6 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
|
||||||
<title>Swagger UI: OAuth2 Redirect</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script src="oauth2-redirect.js"></script>
|
||||||
'use strict';
|
|
||||||
function run () {
|
|
||||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
|
||||||
var sentState = oauth2.state;
|
|
||||||
var redirectUrl = oauth2.redirectUrl;
|
|
||||||
var isValid, qp, arr;
|
|
||||||
|
|
||||||
if (/code|token|error/.test(window.location.hash)) {
|
|
||||||
qp = window.location.hash.substring(1).replace('?', '&');
|
|
||||||
} else {
|
|
||||||
qp = location.search.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
arr = qp.split("&");
|
|
||||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
|
||||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
|
||||||
function (key, value) {
|
|
||||||
return key === "" ? value : decodeURIComponent(value);
|
|
||||||
}
|
|
||||||
) : {};
|
|
||||||
|
|
||||||
isValid = qp.state === sentState;
|
|
||||||
|
|
||||||
if ((
|
|
||||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
|
||||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
|
||||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
|
||||||
) && !oauth2.auth.code) {
|
|
||||||
if (!isValid) {
|
|
||||||
oauth2.errCb({
|
|
||||||
authId: oauth2.auth.name,
|
|
||||||
source: "auth",
|
|
||||||
level: "warning",
|
|
||||||
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qp.code) {
|
|
||||||
delete oauth2.state;
|
|
||||||
oauth2.auth.code = qp.code;
|
|
||||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
|
||||||
} else {
|
|
||||||
let oauthErrorMsg;
|
|
||||||
if (qp.error) {
|
|
||||||
oauthErrorMsg = "["+qp.error+"]: " +
|
|
||||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
|
||||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
oauth2.errCb({
|
|
||||||
authId: oauth2.auth.name,
|
|
||||||
source: "auth",
|
|
||||||
level: "error",
|
|
||||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
|
||||||
}
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.readyState !== 'loading') {
|
|
||||||
run();
|
|
||||||
} else {
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
run();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"use strict";function run(){var e,r,t,a=window.opener.swaggerUIRedirectOauth2,o=a.state,n=a.redirectUrl;if((t=(r=/code|token|error/.test(window.location.hash)?window.location.hash.substring(1).replace("?","&"):location.search.substring(1)).split("&")).forEach((function(e,r,t){t[r]='"'+e.replace("=",'":"')+'"'})),e=(r=r?JSON.parse("{"+t.join()+"}",(function(e,r){return""===e?r:decodeURIComponent(r)})):{}).state===o,"accessCode"!==a.auth.schema.get("flow")&&"authorizationCode"!==a.auth.schema.get("flow")&&"authorization_code"!==a.auth.schema.get("flow")||a.auth.code)a.callback({auth:a.auth,token:r,isValid:e,redirectUrl:n});else if(e||a.errCb({authId:a.auth.name,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),r.code)delete a.state,a.auth.code=r.code,a.callback({auth:a.auth,redirectUrl:n});else{let e;r.error&&(e="["+r.error+"]: "+(r.error_description?r.error_description+". ":"no accessCode received from the server. ")+(r.error_uri?"More info: "+r.error_uri:"")),a.errCb({authId:a.auth.name,source:"auth",level:"error",message:e||"[Authorization failed]: no accessCode received from the server"})}window.close()}"loading"!==document.readyState?run():document.addEventListener("DOMContentLoaded",(function(){run()}));
|
||||||
+13895
-7429
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,2 @@
|
|||||||
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
|
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
|
||||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(()=>(()=>{var e={17967:(e,t)=>{"use strict";t.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,o=/&(newline|tab);/gi,s=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i=/^.+(:|:)/gim,a=[".","/"];t.N=function(e){var t,l=(t=e||"",t.replace(r,(function(e,t){return String.fromCharCode(t)}))).replace(o,"").replace(s,"").trim();if(!l)return"about:blank";if(function(e){return a.indexOf(e[0])>-1}(l))return l;var c=l.match(i);if(!c)return l;var u=c[0];return n.test(u)?"about:blank":l}},53795:(e,t,n)=>{"use strict";n.d(t,{Z:()=>P});var r=n(23101),o=n.n(r),s=n(61125),i=n.n(s),a=n(11882),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294),h=n(43393);function f(e){return f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},f(e)}function d(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function m(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function g(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function y(e){return y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},y(e)}function v(e,t){return v=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},v(e,t)}function b(e,t){return!t||"object"!=typeof t&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}var w={};function E(e,t,n){return function(e){return null==e}(e)?n:function(e){return null!==e&&"object"===f(e)&&"function"==typeof e.get&&"function"==typeof e.has}(e)?e.has(t)?e.get(t):n:hasOwnProperty.call(e,t)?e[t]:n}function x(e,t,n){for(var r=0;r!==t.length;)if((e=E(e,t[r++],w))===w)return n;return e}function S(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=function(e,t){return function(n){if("string"==typeof n)return(0,h.is)(t[n],e[n]);if(Array.isArray(n))return(0,h.is)(x(t,n),x(e,n));throw new TypeError("Invalid key: expected Array or string: "+n)}}(t,n),o=e||Object.keys(function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?g(n,!0).forEach((function(t){m(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):g(n).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},n,{},t));return o.every(r)}const _=function(e){function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),b(this,y(t).apply(this,arguments))}var n,r,o;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&v(e,t)}(t,e),n=t,r=[{key:"shouldComponentUpdate",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return!S(this.updateOnProps,this.props,e,"updateOnProps")||!S(this.updateOnStates,this.state,t,"updateOnStates")}}],r&&d(n.prototype,r),o&&d(n,o),t}(p.Component);var j=n(23930),O=n.n(j),k=n(45697),A=n.n(k);const C=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class P extends _{constructor(){super(...arguments),i()(this,"getModelName",(e=>-1!==l()(e).call(e,"#/definitions/")?C(e.replace(/^.*#\/definitions\//,"")):-1!==l()(e).call(e,"#/components/schemas/")?C(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),i()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:r,schema:s,required:i,name:a,isRef:l,specPath:c,displayName:u,includeReadOnly:h,includeWriteOnly:f}=this.props;const d=e("ObjectModel"),m=e("ArrayModel"),g=e("PrimitiveModel");let y="object",v=s&&s.get("$$ref");if(!a&&v&&(a=this.getModelName(v)),!s&&v&&(s=this.getRefSchema(a)),!s)return p.createElement("span",{className:"model model-title"},p.createElement("span",{className:"model-title__text"},u||a),p.createElement("img",{src:n(2517),height:"20px",width:"20px"}));const b=r.isOAS3()&&s.get("deprecated");switch(l=void 0!==l?l:!!v,y=s&&s.get("type")||y,y){case"object":return p.createElement(d,o()({className:"object"},this.props,{specPath:c,getConfigs:t,scLine truncated
|
!function webpackUniversalModuleDefinition(s,o){"object"==typeof exports&&"object"==typeof module?module.exports=o():"function"==typeof define&&define.amd?define([],o):"object"==typeof exports?exports.SwaggerUIBundle=o():s.SwaggerUIBundle=o()}(this,(()=>(()=>{var s={67526(s,o){"use strict";o.byteLength=function byteLength(s){var o=getLens(s),i=o[0],a=o[1];return 3*(i+a)/4-a},o.toByteArray=function toByteArray(s){var o,i,_=getLens(s),w=_[0],x=_[1],C=new u(function _byteLength(s,o,i){return 3*(o+i)/4-i}(0,w,x)),j=0,L=x>0?w-4:w;for(i=0;i<L;i+=4)o=a[s.charCodeAt(i)]<<18|a[s.charCodeAt(i+1)]<<12|a[s.charCodeAt(i+2)]<<6|a[s.charCodeAt(i+3)],C[j++]=o>>16&255,C[j++]=o>>8&255,C[j++]=255&o;2===x&&(o=a[s.charCodeAt(i)]<<2|a[s.charCodeAt(i+1)]>>4,C[j++]=255&o);1===x&&(o=a[s.charCodeAt(i)]<<10|a[s.charCodeAt(i+1)]<<4|a[s.charCodeAt(i+2)]>>2,C[j++]=o>>8&255,C[j++]=255&o);return C},o.fromByteArray=function fromByteArray(s){for(var o,a=s.length,u=a%3,_=[],w=16383,x=0,C=a-u;x<C;x+=w)_.push(encodeChunk(s,x,x+w>C?C:x+w));1===u?(o=s[a-1],_.push(i[o>>2]+i[o<<4&63]+"==")):2===u&&(o=(s[a-2]<<8)+s[a-1],_.push(i[o>>10]+i[o>>4&63]+i[o<<2&63]+"="));return _.join("")};for(var i=[],a=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",w=0;w<64;++w)i[w]=_[w],a[_.charCodeAt(w)]=w;function getLens(s){var o=s.length;if(o%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=s.indexOf("=");return-1===i&&(i=o),[i,i===o?0:4-i%4]}function encodeChunk(s,o,a){for(var u,_,w=[],x=o;x<a;x+=3)u=(s[x]<<16&16711680)+(s[x+1]<<8&65280)+(255&s[x+2]),w.push(i[(_=u)>>18&63]+i[_>>12&63]+i[_>>6&63]+i[63&_]);return w.join("")}a["-".charCodeAt(0)]=62,a["_".charCodeAt(0)]=63},48287(s,o,i){"use strict";const a=i(67526),u=i(251),_="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;o.Buffer=Buffer,o.SlowBuffer=function SlowBuffer(s){+s!=s&&(s=0);return Buffer.alloc(+s)},o.INSPECT_MAX_BYTES=50;const w=2147483647;function createBuffer(s){if(s>w)throw new RangeError('The value "'+s+'" is invalid for option "size"');const o=new Uint8Array(s);return Object.setPrototypeOf(o,Buffer.prototype),o}function Buffer(s,o,i){if("number"==typeof s){if("string"==typeof o)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(s)}return from(s,o,i)}function from(s,o,i){if("string"==typeof s)return function fromString(s,o){"string"==typeof o&&""!==o||(o="utf8");if(!Buffer.isEncoding(o))throw new TypeError("Unknown encoding: "+o);const i=0|byteLength(s,o);let a=createBuffer(i);const u=a.write(s,o);u!==i&&(a=a.slice(0,u));return a}(s,o);if(ArrayBuffer.isView(s))return function fromArrayView(s){if(isInstance(s,Uint8Array)){const o=new Uint8Array(s);return fromArrayBuffer(o.buffer,o.byteOffset,o.byteLength)}return fromArrayLike(s)}(s);if(null==s)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s);if(isInstance(s,ArrayBuffer)||s&&isInstance(s.buffer,ArrayBuffer))return fromArrayBuffer(s,o,i);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(s,SharedArrayBuffer)||s&&isInstance(s.buffer,SharedArrayBuffer)))return fromArrayBuffer(s,o,i);if("number"==typeof s)throw new TypeError('The "value" argument must not be of type number. Received type number');const a=s.valueOf&&s.valueOf();if(null!=a&&a!==s)return Buffer.from(a,o,i);const u=function fromObject(s){if(Buffer.isBuffer(s)){const o=0|checked(s.length),i=createBuffer(o);return 0===i.length||s.copy(i,0,0,o),i}if(void 0!==s.length)return"number"!=typeof s.length||numberIsNaN(s.length)?createBuffer(0):fromArrayLike(s);if("Buffer"===s.type&&Array.isArray(s.data))return fromArrayLike(s.data)}(s);if(u)return u;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof s[Symbol.toPrimitive])return Buffer.from(s[Symbol.toPrimitive]("string"),o,i);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s)}function assertSize(s){if("number"!=typeof s)throw new TypeError('"size" argument must be of type number');if(s<0)throw new RangeError('The value "'+s+'" is invalid for option "size"')}function allocUnsafe(s){return assertSize(s),createBuffer(s<0?0:0|checked(s))}function fromArrayLike(s){const o=s.length<0?0:0|checked(s.length),i=createBuffer(o);for(let a=0;a<o;a+=1)i[a]=255&s[a];return i}function fromArrayBuffer(s,o,i){if(o<0||s.byteLength<o)throw new RangeError('"offset" is outside of buffer bounds');if(s.byteLength<o+(i||0))throw new RangeError('"length" is outside of buffer bounds');let a;return a=void 0===o&&void 0===i?new Uint8Array(s):void 0===i?new Uint8Array(s,o):new Uint8Array(s,o,i),Object.setPrototypeOf(a,Buffer.prototype),a}function checked(s){if(s>=w)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+w.toString(16)+"
Line truncated
|
||||||
//# sourceMappingURL=swagger-ui-bundle.js.map
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*!
|
||||||
|
Copyright (c) 2018 Jed Watson.
|
||||||
|
Licensed under the MIT License (MIT), see
|
||||||
|
http://jedwatson.github.io/classnames
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @description Recursive object extending
|
||||||
|
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
|
||||||
|
* @license MIT
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* The buffer module from node.js, for the browser.
|
||||||
|
*
|
||||||
|
* @author Feross Aboukhadijeh <https://feross.org>
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* https://github.com/Starcounter-Jack/JSON-Patch
|
||||||
|
* (c) 2017-2021 Joachim Wester
|
||||||
|
* MIT license
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* https://github.com/Starcounter-Jack/JSON-Patch
|
||||||
|
* (c) 2017-2022 Joachim Wester
|
||||||
|
* MIT licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* repeat-string <https://github.com/jonschlinkert/repeat-string>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2015, Jon Schlinkert.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! @license DOMPurify 3.4.17 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.17/LICENSE */
|
||||||
|
|
||||||
|
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
|
|
||||||
|
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
||||||
|
|
||||||
|
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-present, Lee Byron and other contributors.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react-dom.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* scheduler.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* use-sync-external-store-with-selector.production.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"swagger-ui-bundle.js","mappings":"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 truncated
|
{"version":3,"file":"swagger-ui-bundle.js","mappings":"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 truncated
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
/*! For license information please see swagger-ui-es-bundle-core.js.LICENSE.txt */
|
/*! For license information please see swagger-ui-es-bundle-core.js.LICENSE.txt */
|
||||||
import*as e from"react-immutable-pure-component";import*as t from"remarkable/linkify";import*as a from"dompurify";import*as n from"zenscroll";import*as r from"lodash/reduce";import*as s from"@babel/runtime-corejs3/core-js-stable/string/raw";import*as o from"@babel/runtime-corejs3/core-js-stable/number/epsilon";import*as l from"@babel/runtime-corejs3/core-js-stable/instance/repeat";import*as i from"@babel/runtime-corejs3/core-js-stable/instance/fill";import*as c from"lodash/zipObject";import*as u from"@babel/runtime-corejs3/core-js-stable/promise";import*as d from"@babel/runtime-corejs3/core-js-stable/date/now";import*as p from"lodash/isString";import*as m from"lodash/debounce";import*as f from"lodash/set";import*as h from"lodash/fp/assocPath";import*as g from"swagger-client/es/resolver/strategies/generic";import*as y from"swagger-client/es/resolver/strategies/openapi-2";import*as v from"swagger-client/es/resolver/strategies/openapi-3-0";import*as E from"swagger-client/es/resolver/strategies/openapi-3-1-apidom";import*as S from"swagger-client/es/resolver";import*as w from"swagger-client/es/execute";import*as b from"swagger-client/es/http";import*as x from"swagger-client/es/subtree-resolver";import*as _ from"react-dom";import*as C from"react-redux";import*as j from"lodash/omit";import*as O from"react-syntax-highlighter/dist/esm/light";import*as N from"react-syntax-highlighter/dist/esm/languages/hljs/javascript";import*as k from"react-syntax-highlighter/dist/esm/languages/hljs/json";import*as A from"react-syntax-highlighter/dist/esm/languages/hljs/xml";import*as I from"react-syntax-highlighter/dist/esm/languages/hljs/bash";import*as R from"react-syntax-highlighter/dist/esm/languages/hljs/yaml";import*as P from"react-syntax-highlighter/dist/esm/languages/hljs/http";import*as T from"react-syntax-highlighter/dist/esm/languages/hljs/powershell";import*as M from"react-syntax-highlighter/dist/esm/styles/hljs/agate";import*as D from"react-syntax-highlighter/dist/esm/styles/hljs/arta";import*as L from"react-syntax-highlighter/dist/esm/styles/hljs/monokai";import*as q from"react-syntax-highlighter/dist/esm/styles/hljs/nord";import*as B from"react-syntax-highlighter/dist/esm/styles/hljs/obsidian";import*as U from"react-syntax-highlighter/dist/esm/styles/hljs/tomorrow-night";import*as J from"@babel/runtime-corejs3/core-js-stable/instance/starts-with";import*as $ from"@braintree/sanitize-url";import*as K from"lodash/camelCase";import*as V from"lodash/upperFirst";import*as F from"lodash/find";import*as z from"lodash/eq";import*as W from"css.escape";import*as H from"@babel/runtime-corejs3/core-js-stable/instance/find-index";import*as G from"@babel/runtime-corejs3/core-js-stable/array/from";import*as Z from"@babel/runtime-corejs3/core-js-stable/array/is-array";import*as Y from"@babel/runtime-corejs3/core-js-stable/instance/bind";import*as X from"@babel/runtime-corejs3/core-js-stable/instance/concat";import*as Q from"@babel/runtime-corejs3/core-js-stable/instance/entries";import*as ee from"@babel/runtime-corejs3/core-js-stable/instance/every";import*as te from"@babel/runtime-corejs3/core-js-stable/instance/filter";import*as ae from"@babel/runtime-corejs3/core-js-stable/instance/find";import*as ne from"@babel/runtime-corejs3/core-js-stable/instance/for-each";import*as re from"@babel/runtime-corejs3/core-js-stable/instance/includes";import*as se from"@babel/runtime-corejs3/core-js-stable/instance/index-of";import*as oe from"@babel/runtime-corejs3/core-js-stable/instance/keys";import*as le from"@babel/runtime-corejs3/core-js-stable/instance/map";import*as ie from"@babel/runtime-corejs3/core-js-stable/instance/reduce";import*as ce from"@babel/runtime-corejs3/core-js-stable/instance/slice";import*as ue from"@babel/runtime-corejs3/core-js-stable/instance/some";import*as de from"@babel/runtime-corejs3/core-js-stable/instance/sort";import*as pe from"@babel/runtime-corejs3/core-js-stable/instance/trim";import*as me from"@babel/runtime-corejs3/core-js-stable/json/stringify";import*as fe from"@babel/runtime-corejs3/core-js-stable/map";import*as he from"@babel/runtime-corejs3/core-js-stable/number/is-integer";import*as ge from"@babel/runtime-corejs3/core-js-stable/object/assign";import*as ye from"@babel/runtime-corejs3/core-js-stable/object/entries";import*as ve from"@babel/runtime-corejs3/core-js-stable/object/from-entries";import*as Ee from"@babel/runtime-corejs3/core-js-stable/object/keys";import*as Se from"@babel/runtime-corejs3/core-js-stable/object/values";import*as we from"@babel/runtime-corejs3/core-js-stable/set";import*as be from"@babel/runtime-corejs3/core-js-stable/set-timeout";import*as xe from"@babel/runtime-corejs3/core-js-stable/url";import*as _e from"@babel/runtime-corejs3/core-js-stable/weak-map";import*as Ce from"@babel/runtime-corejs3/core-js-stable/weak-set";import*as je from"@babel/runtime-corejs3/helpers/classPrivateFieldGet";import*as Oe from"@babel/runtime-corejs3/helpers/defineProperty";import*as Ne from"@babelLine truncated
|
import*as e from"base64-js";import*as t from"ieee754";import*as r from"to-buffer";import{Component as n,PureComponent as a,createContext as s,createElement as o,default as i,forwardRef as l,useCallback as c,useContext as u,useEffect as p,useLayoutEffect as m,useMemo as d,useRef as h,useState as f}from"react";import{applyMiddleware as g,bindActionCreators as y,compose as S,createStore as v}from"redux";import*as b from"immutable";import{serializeError as w}from"serialize-error";import{default as _}from"lodash/merge";import"lodash/camelCase";import"lodash/upperFirst";import{default as C}from"lodash/memoize";import{default as x}from"lodash/find";import{default as O}from"lodash/some";import{default as N}from"lodash/eq";import{default as A}from"lodash/isFunction";import{default as k}from"css.escape";import{default as I}from"url-parse";import{createSelector as j}from"reselect";import{default as T}from"prop-types";import{default as R}from"lodash/omit";import{JSON_SCHEMA as M,default as P}from"js-yaml";import{default as B}from"zenscroll";import{default as L}from"react-immutable-proptypes";import{default as q}from"lodash/reduce";import{default as U}from"lodash/get";import{default as D}from"@babel/runtime-corejs3/helpers/extends";import{default as J}from"classnames";import{CopyToClipboard as $}from"react-copy-to-clipboard";import{default as V}from"react-immutable-pure-component";import{useVirtualizer as F,useWindowVirtualizer as z}from"@tanstack/react-virtual";import{default as K}from"react-debounce-input";import{default as W}from"lodash/isPlainObject";import{default as H}from"randexp";import{default as G}from"lodash/isEmpty";import{default as X}from"lodash/constant";import{default as Y}from"lodash/isString";import{default as Q}from"lodash/debounce";import{default as Z}from"lodash/set";import{default as ee}from"lodash/fp/assocPath";import{default as te}from"swagger-client/es/resolver/strategies/generic";import{default as re}from"swagger-client/es/resolver/strategies/openapi-2";import{default as ne}from"swagger-client/es/resolver/strategies/openapi-3-0";import{default as ae}from"swagger-client/es/resolver/strategies/openapi-3-1-apidom";import{default as se}from"swagger-client/es/resolver/strategies/openapi-3-2-apidom";import{makeResolve as oe}from"swagger-client/es/resolver";import{buildRequest as ie,execute as le}from"swagger-client/es/execute";import{default as ce,makeHttp as ue,serializeRes as pe}from"swagger-client/es/http";import{makeResolveSubtree as me}from"swagger-client/es/subtree-resolver";import{opId as de}from"swagger-client/es/helpers";import{default as he}from"react-dom";import{Provider as fe,connect as ge}from"react-redux";import{default as ye}from"lodash/identity";import{default as Se}from"react-syntax-highlighter/dist/esm/light";import{default as Ee}from"react-syntax-highlighter/dist/esm/languages/hljs/javascript";import{default as ve}from"react-syntax-highlighter/dist/esm/languages/hljs/json";import{default as be}from"react-syntax-highlighter/dist/esm/languages/hljs/xml";import{default as we}from"react-syntax-highlighter/dist/esm/languages/hljs/bash";import{default as _e}from"react-syntax-highlighter/dist/esm/languages/hljs/yaml";import{default as Ce}from"react-syntax-highlighter/dist/esm/languages/hljs/http";import{default as xe}from"react-syntax-highlighter/dist/esm/languages/hljs/powershell";import{default as Oe}from"react-syntax-highlighter/dist/esm/styles/hljs/agate";import{default as Ne}from"react-syntax-highlighter/dist/esm/styles/hljs/arta";import{default as Ae}from"react-syntax-highlighter/dist/esm/styles/hljs/monokai";import{default as ke}from"react-syntax-highlighter/dist/esm/styles/hljs/nord";import{default as Ie}from"react-syntax-highlighter/dist/esm/styles/hljs/obsidian";import{default as je}from"react-syntax-highlighter/dist/esm/styles/hljs/tomorrow-night";import{default as Te}from"react-syntax-highlighter/dist/esm/styles/hljs/idea";import{default as Re}from"js-file-download";import{default as Me}from"lodash/zipObject";import{default as Pe}from"lodash/toString";import{default as Be}from"xml-but-prettier";import{default as Le}from"lodash/toLower";import{Remarkable as qe}from"remarkable";import{linkify as Ue}from"remarkable/linkify";import{default as De}from"dompurify";import{default as Je}from"lodash/escapeRegExp";import{default as $e}from"lodash/has";import{default as Ve}from"lodash/fp/set";var Fe={287:function(e,t,r){const n=r(987),a=r(362),s="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;t.Buffer=Buffer,t.SlowBuffer=function SlowBuffer(e){+e!=e&&(e=0);return Buffer.alloc(+e)},t.INSPECT_MAX_BYTES=50;const o=2147483647;function createBuffer(e){if(e>o)throw new RangeError('The value "'+e+'" is invalid for option "size"');const t=new Uint8Array(e);return Object.setPrototypeOf(t,Buffer.prototype),t}function Buffer(e,t,r){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. ReLine truncated
|
||||||
//# sourceMappingURL=swagger-ui-es-bundle-core.js.map
|
//# sourceMappingURL=swagger-ui-es-bundle-core.js.map
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*!
|
||||||
|
* @description Recursive object extending
|
||||||
|
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
|
||||||
|
* @license MIT
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* The buffer module from node.js, for the browser.
|
||||||
|
*
|
||||||
|
* @author Feross Aboukhadijeh <https://feross.org>
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"swagger-ui-es-bundle-core.js","mappings":"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 truncated
|
{"version":3,"file":"swagger-ui-es-bundle-core.js","mappings":";q0IAUA,MAAMA,EAAS,EAAQ,KACjBC,EAAU,EAAQ,KAClBC,EACe,mBAAXC,QAAkD,mBAAlBA,OAAY,IAChDA,OAAY,IAAE,8BACd,KAENC,EAAQC,OAASA,OACjBD,EAAQE,WAyTR,SAASA,WAAYC,IACdA,GAAUA,IACbA,EAAS,GAEX,OAAOF,OAAOG,OAAOD,EACvB,EA7TAH,EAAQK,kBAAoB,GAE5B,MAAMC,EAAe,WAwDrB,SAASC,aAAcJ,GACrB,GAAIA,EAASG,EACX,MAAM,IAAIE,WAAW,cAAgBL,EAAS,kCAGhD,MAAMM,EAAM,IAAIC,WAAWP,GAE3B,OADAQ,OAAOC,eAAeH,EAAKR,OAAOY,WAC3BJ,CACT,CAYA,SAASR,OAAQa,EAAKC,EAAkBZ,GAEtC,GAAmB,iBAARW,EAAkB,CAC3B,GAAgC,iBAArBC,EACT,MAAM,IAAIC,UACR,sEAGJ,OAAOC,YAAYH,EACrB,CACA,OAAOI,KAAKJ,EAAKC,EAAkBZ,EACrC,CAIA,SAASe,KAAMC,EAAOJ,EAAkBZ,GACtC,GAAqB,iBAAVgB,EACT,OAqHJ,SAASC,WAAYC,EAAQC,GACH,iBAAbA,GAAsC,KAAbA,IAClCA,EAAW,QAGb,IAAKrB,OAAOsB,WAAWD,GACrB,MAAM,IAAIN,UAAU,qBAAuBM,GAG7C,MAAMnB,EAAwC,EAA/BqB,WAAWH,EAAQC,GAClC,IAAIb,EAAMF,aAAaJ,GAEvB,MAAMsB,EAAShB,EAAIiB,MAAML,EAAQC,GAE7BG,IAAWtB,IAIbM,EAAMA,EAAIkB,MAAM,EAAGF,IAGrB,OAAOhB,CACT,CA3IWW,CAAWD,EAAOJ,GAG3B,GAAIa,YAAYC,OAAOV,GACrB,OAkJJ,SAASW,cAAeC,GACtB,GAAIC,WAAWD,EAAWrB,YAAa,CACrC,MAAMuB,EAAO,IAAIvB,WAAWqB,GAC5B,OAAOG,gBAAgBD,EAAKE,OAAQF,EAAKG,WAAYH,EAAKT,WAC5D,CACA,OAAOa,cAAcN,EACvB,CAxJWD,CAAcX,GAGvB,GAAa,MAATA,EACF,MAAM,IAAIH,UACR,yHACiDG,GAIrD,GAAIa,WAAWb,EAAOS,cACjBT,GAASa,WAAWb,EAAMgB,OAAQP,aACrC,OAAOM,gBAAgBf,EAAOJ,EAAkBZ,GAGlD,GAAiC,oBAAtBmC,oBACNN,WAAWb,EAAOmB,oBAClBnB,GAASa,WAAWb,EAAMgB,OAAQG,oBACrC,OAAOJ,gBAAgBf,EAAOJ,EAAkBZ,GAGlD,GAAqB,iBAAVgB,EACT,MAAM,IAAIH,UACR,yEAIJ,MAAMuB,EAAUpB,EAAMoB,SAAWpB,EAAMoB,UACvC,GAAe,MAAXA,GAAmBA,IAAYpB,EACjC,OAAOlB,OAAOiB,KAAKqB,EAASxB,EAAkBZ,GAGhD,MAAMqC,EAkJR,SAASC,WAAYC,GACnB,GAAIzC,OAAO0C,SAASD,GAAM,CACxB,MAAME,EAA4B,EAAtBC,QAAQH,EAAIvC,QAClBM,EAAMF,aAAaqC,GAEzB,OAAmB,IAAfnC,EAAIN,QAIRuC,EAAIT,KAAKxB,EAAK,EAAG,EAAGmC,GAHXnC,CAKX,CAEA,QAAmBqC,IAAfJ,EAAIvC,OACN,MAA0B,iBAAfuC,EAAIvC,QAAuB4C,YAAYL,EAAIvC,QAC7CI,aAAa,GAEf8B,cAAcK,GAGvB,GAAiB,WAAbA,EAAIM,MAAqBC,MAAMC,QAAQR,EAAIS,MAC7C,OAAOd,cAAcK,EAAIS,KAE7B,CAzKYV,CAAWtB,GACrB,GAAIqB,EAAG,OAAOA,EAEd,GAAsB,oBAAXzC,QAAgD,MAAtBA,OAAOqD,aACH,mBAA9BjC,EAAMpB,OAAOqD,aACtB,OAAOnD,OAAOiB,KAAKC,EAAMpB,OAAOqD,aAAa,UAAWrC,EAAkBZ,GAG5E,MAAM,IAAIa,UACR,yHACiDG,EAErD,CAmBA,SAASkC,WAAYC,GACnB,GAAoB,iBAATA,EACT,MAAM,IAAItC,UAAU,0CACf,GAAIsC,EAAO,EAChB,MAAM,IAAI9C,WAAW,cAAgB8C,EAAO,iCAEhD,CA0BA,SAASrC,YAAaqC,GAEpB,OADAD,WAAWC,GACJ/C,aAAa+C,EAAO,EAAI,EAAoB,EAAhBT,QAAQS,GAC7C,CAuCA,SAASjB,cAAekB,GACtB,MAAMpD,EAASoD,EAAMpD,OAAS,EAAI,EAA4B,EAAxB0C,QAAQU,EAAMpD,QAC9CM,EAAMF,aAAaJ,GACzB,IAAK,IAAIqD,EAAI,EAAGA,EAAIrD,EAAQqD,GAAK,EAC/B/C,EAAI+C,GAAgB,IAAXD,EAAMC,GAEjB,OAAO/C,CACT,CAUA,SAASyB,gBAAiBqB,EAAOnB,EAAYjC,GAC3C,GAAIiC,EAAa,GAAKmB,EAAM/B,WAAaY,EACvC,MAAM,IAAI5B,WAAW,wCAGvB,GAAI+C,EAAM/B,WAAaY,GAAcjC,GAAU,GAC7C,MAAM,IAAIK,WAAW,wCAGvB,IAAIC,EAYJ,OAVEA,OADiBqC,IAAfV,QAAuCU,IAAX3C,EACxB,IAAIO,WAAW6C,QACDT,IAAX3C,EACH,IAAIO,WAAW6C,EAAOnB,GAEtB,IAAI1B,WAAW6C,EAAOnB,EAAYjC,GAI1CQ,OAAOC,eAAeH,EAAKR,OAAOY,WAE3BJ,CACT,CA2BA,SAASoC,QAAS1C,GAGhB,GAAIA,GAAUG,EACZ,MAAM,IAAIE,WAAW,0DACaF,EAAamD,SAAS,IAAM,UAEhE,OAAgB,EAATtD,CACT,CAsGA,SAASqB,WAAYH,EAAQC,GAC3B,GAAIrB,OAAO0C,SAAStB,GAClB,OAAOA,EAAOlB,OAEhB,GAAIyB,YAAYC,OAAOR,IAAWW,WAAWX,EAAQO,aACnD,OAAOP,EAAOG,WAEhB,GAAsB,iBAAXH,EACT,MAAM,IAAIL,UACR,kGAC0BK,GAI9B,MAAMuB,EAAMvB,EAAOlB,OACbuD,EAAaC,UAAUxD,OAAS,IAAsB,IAAjBwD,UAAU,GACrD,IAAKD,GAAqB,IAARd,EAAW,OAAO,EAGpC,IAAIgB,GAAc,EAClB,OACE,OAAQtC,GACN,IAAK,QACL,IAAK,SACL,IAAK,SACH,OAAOsB,EACT,IAAK,OACL,IAAK,QACH,OAAOiB,YAAYxC,GAAQlB,OAC7B,IAAK,OACL,IAAK,QACL,IAAK,UACL,IAAK,WACH,OAAa,EAANyC,EACT,IAAK,MACH,OAAOA,IAAQ,EACjB,IAAK,SACH,OAAOkB,cAAczC,GAAQlB,OAC/B,QACE,GAAIyD,EACF,OAAOF,GAAa,EAAIG,YAAYxC,GAAQlB,OAE9CmB,GAAY,GAAKA,GAAUyC,cAC3BH,GAAc,EAGtB,CAGA,SAASI,aAAc1C,EAAU2C,EAAOC,GACtC,IAAIN,GAAc,EAclB,SALcd,IAAVmB,GAAuBA,EAAQ,KACjCA,EAAQ,GAINA,EAAQE,KAAKhE,OACf,MAAO,GAOT,SAJY2C,IAARoB,GAAqBA,EAAMC,KAAKhE,UAClC+D,EAAMC,KAAKhE,QAGT+D,GAAO,EACT,MAAO,GAOT,IAHAA,KAAS,KACTD,KAAW,GAGT,MAAO,GAKT,IAFK3C,IAAUA,EAAW,UAGxB,OAAQA,GACN,IAAK,MACH,OAAO8C,SAASD,KAAMF,EAAOC,GAE/B,IAAK,OACL,IAAK,QACH,OAAOG,UAAUF,KAAMF,EAAOC,GAEhC,IAAK,QACH,OAAOI,WAAWH,KAAMF,EAAOC,GAEjC,IAAK,SACL,IAAK,SACH,OAAOK,YAAYJ,KAAMF,EAAOC,GAElC,IAAK,SACH,OAAOM,YAAYL,KAAMF,EAAOC,GAElC,IAAK,OACL,IAAK,QACL,IAAK,UACL,IAAK,WACH,OAAOO,aAAaN,KAAMF,EAAOC,GAEnC,QACE,GAAIN,EAAa,MAAM,IAAI5C,UAAU,qBAAuBM,GAC5DA,GAAYA,EAAW,IAAIyC,cAC3BH,GAAc,EAGtB,CAUA,SAASc,KAAMlC,EAAGmC,EAAGC,GACnB,MAAMpB,EAAIhB,EAAEmC,GACZnC,EAAEmC,GAAKnC,EAAEoC,GACTpC,EAAEoC,GAAKpB,CACT,CA2IA,SAASqB,qBAAsB1C,EAAQ2C,EAAK1C,EAAYd,EAAUyD,GAEhE,GAAsB,IAAlB5C,EAAOhC,OAAc,OAAQ,EAmBjC,GAhB0B,iBAAfiC,GACTd,EAAWc,EACXA,EAAa,GACJA,EAAa,WACtBA,EAAa,WACJA,GAAc,aACvBA,GAAc,YAGZW,YADJX,GAAcA,KAGZA,EAAa2C,EAAM,EAAK5C,EAAOhC,OAAS,GAItCiC,EAAa,IAAGA,EAAaD,EAAOhC,OAASiC,GAC7CA,GAAcD,EAAOhC,OAAQ,CAC/B,GAAI4E,EAAK,OAAQ,EACZ3C,EAAaD,EAAOhC,OAAS,CACpC,MAAO,GAAIiC,EAAa,EAAG,CACzB,IAAI2C,EACC,OAAQ,EADJ3C,EAAa,CAExB,CAQA,GALmB,iBAAR0C,IACTA,EAAM7E,OAAOiB,KAAK4D,EAAKxD,IAIrBrB,OAAO0C,SAASmC,GAElB,OAAmB,IAAfA,EAAI3E,QACE,EAEH6E,aAAa7C,EAAQ2C,EAAK1C,EAAYd,EAAUyD,GAClD,GAAmB,iBAARD,EAEhB,OADAA,GAAY,IACgC,mBAAjCpE,WAAWLine truncated
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
/*! For license information please see swagger-ui-es-bundle.js.LICENSE.txt */
|
/*! For license information please see swagger-ui-es-bundle.js.LICENSE.txt */
|
||||||
(()=>{var e={17967:(e,t)=>{"use strict";t.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,o=/&(newline|tab);/gi,s=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i=/^.+(:|:)/gim,a=[".","/"];t.N=function(e){var t,l=(t=e||"",t.replace(r,(function(e,t){return String.fromCharCode(t)}))).replace(o,"").replace(s,"").trim();if(!l)return"about:blank";if(function(e){return a.indexOf(e[0])>-1}(l))return l;var c=l.match(i);if(!c)return l;var u=c[0];return n.test(u)?"about:blank":l}},53795:(e,t,n)=>{"use strict";n.d(t,{Z:()=>P});var r=n(23101),o=n.n(r),s=n(61125),i=n.n(s),a=n(11882),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294),h=n(43393);function f(e){return f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},f(e)}function d(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function m(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function g(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function y(e){return y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},y(e)}function v(e,t){return v=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},v(e,t)}function b(e,t){return!t||"object"!=typeof t&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}var w={};function E(e,t,n){return function(e){return null==e}(e)?n:function(e){return null!==e&&"object"===f(e)&&"function"==typeof e.get&&"function"==typeof e.has}(e)?e.has(t)?e.get(t):n:hasOwnProperty.call(e,t)?e[t]:n}function x(e,t,n){for(var r=0;r!==t.length;)if((e=E(e,t[r++],w))===w)return n;return e}function S(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=function(e,t){return function(n){if("string"==typeof n)return(0,h.is)(t[n],e[n]);if(Array.isArray(n))return(0,h.is)(x(t,n),x(e,n));throw new TypeError("Invalid key: expected Array or string: "+n)}}(t,n),o=e||Object.keys(function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?g(n,!0).forEach((function(t){m(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):g(n).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},n,{},t));return o.every(r)}const _=function(e){function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),b(this,y(t).apply(this,arguments))}var n,r,o;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&v(e,t)}(t,e),n=t,r=[{key:"shouldComponentUpdate",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return!S(this.updateOnProps,this.props,e,"updateOnProps")||!S(this.updateOnStates,this.state,t,"updateOnStates")}}],r&&d(n.prototype,r),o&&d(n,o),t}(p.Component);var j=n(23930),O=n.n(j),k=n(45697),A=n.n(k);const C=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class P extends _{constructor(){super(...arguments),i()(this,"getModelName",(e=>-1!==l()(e).call(e,"#/definitions/")?C(e.replace(/^.*#\/definitions\//,"")):-1!==l()(e).call(e,"#/components/schemas/")?C(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),i()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:r,schema:s,required:i,name:a,isRef:l,specPath:c,displayName:u,includeReadOnly:h,includeWriteOnly:f}=this.props;const d=e("ObjectModel"),m=e("ArrayModel"),g=e("PrimitiveModel");let y="object",v=s&&s.get("$$ref");if(!a&&v&&(a=this.getModelName(v)),!s&&v&&(s=this.getRefSchema(a)),!s)return p.createElement("span",{className:"model model-title"},p.createElement("span",{className:"model-title__text"},u||a),p.createElement("img",{src:n(2517),height:"20px",width:"20px"}));const b=r.isOAS3()&&s.get("deprecated");switch(l=void 0!==l?l:!!v,y=s&&s.get("type")||y,y){case"object":return p.createElement(d,o()({className:"object"},this.props,{specPath:c,getConfigs:t,schema:s,name:a,deprecated:b,isRef:l,includeReadOnly:h,includeWriteOnly:f}));case"array":return p.createElement(m,o()({className:"array"},this.props,{getConfigs:t,schema:s,name:a,deprecated:b,required:i,includeReadOnly:h,inLine truncated
|
(()=>{var s={67526(s,o){"use strict";o.byteLength=function byteLength(s){var o=getLens(s),i=o[0],a=o[1];return 3*(i+a)/4-a},o.toByteArray=function toByteArray(s){var o,i,_=getLens(s),w=_[0],x=_[1],C=new u(function _byteLength(s,o,i){return 3*(o+i)/4-i}(0,w,x)),j=0,L=x>0?w-4:w;for(i=0;i<L;i+=4)o=a[s.charCodeAt(i)]<<18|a[s.charCodeAt(i+1)]<<12|a[s.charCodeAt(i+2)]<<6|a[s.charCodeAt(i+3)],C[j++]=o>>16&255,C[j++]=o>>8&255,C[j++]=255&o;2===x&&(o=a[s.charCodeAt(i)]<<2|a[s.charCodeAt(i+1)]>>4,C[j++]=255&o);1===x&&(o=a[s.charCodeAt(i)]<<10|a[s.charCodeAt(i+1)]<<4|a[s.charCodeAt(i+2)]>>2,C[j++]=o>>8&255,C[j++]=255&o);return C},o.fromByteArray=function fromByteArray(s){for(var o,a=s.length,u=a%3,_=[],w=16383,x=0,C=a-u;x<C;x+=w)_.push(encodeChunk(s,x,x+w>C?C:x+w));1===u?(o=s[a-1],_.push(i[o>>2]+i[o<<4&63]+"==")):2===u&&(o=(s[a-2]<<8)+s[a-1],_.push(i[o>>10]+i[o>>4&63]+i[o<<2&63]+"="));return _.join("")};for(var i=[],a=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",w=0;w<64;++w)i[w]=_[w],a[_.charCodeAt(w)]=w;function getLens(s){var o=s.length;if(o%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=s.indexOf("=");return-1===i&&(i=o),[i,i===o?0:4-i%4]}function encodeChunk(s,o,a){for(var u,_,w=[],x=o;x<a;x+=3)u=(s[x]<<16&16711680)+(s[x+1]<<8&65280)+(255&s[x+2]),w.push(i[(_=u)>>18&63]+i[_>>12&63]+i[_>>6&63]+i[63&_]);return w.join("")}a["-".charCodeAt(0)]=62,a["_".charCodeAt(0)]=63},48287(s,o,i){"use strict";const a=i(67526),u=i(251),_="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;o.Buffer=Buffer,o.SlowBuffer=function SlowBuffer(s){+s!=s&&(s=0);return Buffer.alloc(+s)},o.INSPECT_MAX_BYTES=50;const w=2147483647;function createBuffer(s){if(s>w)throw new RangeError('The value "'+s+'" is invalid for option "size"');const o=new Uint8Array(s);return Object.setPrototypeOf(o,Buffer.prototype),o}function Buffer(s,o,i){if("number"==typeof s){if("string"==typeof o)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(s)}return from(s,o,i)}function from(s,o,i){if("string"==typeof s)return function fromString(s,o){"string"==typeof o&&""!==o||(o="utf8");if(!Buffer.isEncoding(o))throw new TypeError("Unknown encoding: "+o);const i=0|byteLength(s,o);let a=createBuffer(i);const u=a.write(s,o);u!==i&&(a=a.slice(0,u));return a}(s,o);if(ArrayBuffer.isView(s))return function fromArrayView(s){if(isInstance(s,Uint8Array)){const o=new Uint8Array(s);return fromArrayBuffer(o.buffer,o.byteOffset,o.byteLength)}return fromArrayLike(s)}(s);if(null==s)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s);if(isInstance(s,ArrayBuffer)||s&&isInstance(s.buffer,ArrayBuffer))return fromArrayBuffer(s,o,i);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(s,SharedArrayBuffer)||s&&isInstance(s.buffer,SharedArrayBuffer)))return fromArrayBuffer(s,o,i);if("number"==typeof s)throw new TypeError('The "value" argument must not be of type number. Received type number');const a=s.valueOf&&s.valueOf();if(null!=a&&a!==s)return Buffer.from(a,o,i);const u=function fromObject(s){if(Buffer.isBuffer(s)){const o=0|checked(s.length),i=createBuffer(o);return 0===i.length||s.copy(i,0,0,o),i}if(void 0!==s.length)return"number"!=typeof s.length||numberIsNaN(s.length)?createBuffer(0):fromArrayLike(s);if("Buffer"===s.type&&Array.isArray(s.data))return fromArrayLike(s.data)}(s);if(u)return u;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof s[Symbol.toPrimitive])return Buffer.from(s[Symbol.toPrimitive]("string"),o,i);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s)}function assertSize(s){if("number"!=typeof s)throw new TypeError('"size" argument must be of type number');if(s<0)throw new RangeError('The value "'+s+'" is invalid for option "size"')}function allocUnsafe(s){return assertSize(s),createBuffer(s<0?0:0|checked(s))}function fromArrayLike(s){const o=s.length<0?0:0|checked(s.length),i=createBuffer(o);for(let a=0;a<o;a+=1)i[a]=255&s[a];return i}function fromArrayBuffer(s,o,i){if(o<0||s.byteLength<o)throw new RangeError('"offset" is outside of buffer bounds');if(s.byteLength<o+(i||0))throw new RangeError('"length" is outside of buffer bounds');let a;return a=void 0===o&&void 0===i?new Uint8Array(s):void 0===i?new Uint8Array(s,o):new Uint8Array(s,o,i),Object.setPrototypeOf(a,Buffer.prototype),a}function checked(s){if(s>=w)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+w.toString(16)+" bytes");return 0|s}function byteLength(s,o){if(Buffer.isBuffer(s))return s.length;if(ArrayBuffer.isView(s)||isInstance(s,ArrayBuffer))return s.byteLength;if("string"!=typeof s)throw new TypeError('The "string" argument must be one of type string, Buffer,Line truncated
|
||||||
//# sourceMappingURL=swagger-ui-es-bundle.js.map
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*!
|
||||||
|
Copyright (c) 2018 Jed Watson.
|
||||||
|
Licensed under the MIT License (MIT), see
|
||||||
|
http://jedwatson.github.io/classnames
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @description Recursive object extending
|
||||||
|
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
|
||||||
|
* @license MIT
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* The buffer module from node.js, for the browser.
|
||||||
|
*
|
||||||
|
* @author Feross Aboukhadijeh <https://feross.org>
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* https://github.com/Starcounter-Jack/JSON-Patch
|
||||||
|
* (c) 2017-2021 Joachim Wester
|
||||||
|
* MIT license
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* https://github.com/Starcounter-Jack/JSON-Patch
|
||||||
|
* (c) 2017-2022 Joachim Wester
|
||||||
|
* MIT licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* repeat-string <https://github.com/jonschlinkert/repeat-string>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2015, Jon Schlinkert.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! @license DOMPurify 3.4.17 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.17/LICENSE */
|
||||||
|
|
||||||
|
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
|
|
||||||
|
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
||||||
|
|
||||||
|
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-present, Lee Byron and other contributors.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react-dom.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* scheduler.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* use-sync-external-store-with-selector.production.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"swagger-ui-es-bundle.js","mappings":"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 truncated
|
{"version":3,"file":"swagger-ui-es-bundle.js","mappings":"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 truncated
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
/*! For license information please see swagger-ui-standalone-preset.js.LICENSE.txt */
|
/*! For license information please see swagger-ui-standalone-preset.js.LICENSE.txt */
|
||||||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SwaggerUIStandalonePreset=e():t.SwaggerUIStandalonePreset=e()}(this,(()=>(()=>{var t={7967:(t,e)=>{"use strict";var r=/^([^\w]*)(javascript|data|vbscript)/im,n=/&#(\w+)(^\w|;)?/g,i=/&(newline|tab);/gi,o=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,u=/^.+(:|:)/gim,s=[".","/"]},9742:(t,e)=>{"use strict";e.byteLength=function(t){var e=s(t),r=e[0],n=e[1];return 3*(r+n)/4-n},e.toByteArray=function(t){var e,r,o=s(t),u=o[0],a=o[1],c=new i(function(t,e,r){return 3*(e+r)/4-r}(0,u,a)),f=0,l=a>0?u-4:u;for(r=0;r<l;r+=4)e=n[t.charCodeAt(r)]<<18|n[t.charCodeAt(r+1)]<<12|n[t.charCodeAt(r+2)]<<6|n[t.charCodeAt(r+3)],c[f++]=e>>16&255,c[f++]=e>>8&255,c[f++]=255&e;2===a&&(e=n[t.charCodeAt(r)]<<2|n[t.charCodeAt(r+1)]>>4,c[f++]=255&e);1===a&&(e=n[t.charCodeAt(r)]<<10|n[t.charCodeAt(r+1)]<<4|n[t.charCodeAt(r+2)]>>2,c[f++]=e>>8&255,c[f++]=255&e);return c},e.fromByteArray=function(t){for(var e,n=t.length,i=n%3,o=[],u=16383,s=0,c=n-i;s<c;s+=u)o.push(a(t,s,s+u>c?c:s+u));1===i?(e=t[n-1],o.push(r[e>>2]+r[e<<4&63]+"==")):2===i&&(e=(t[n-2]<<8)+t[n-1],o.push(r[e>>10]+r[e>>4&63]+r[e<<2&63]+"="));return o.join("")};for(var r=[],n=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",u=0;u<64;++u)r[u]=o[u],n[o.charCodeAt(u)]=u;function s(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");return-1===r&&(r=e),[r,r===e?0:4-r%4]}function a(t,e,n){for(var i,o,u=[],s=e;s<n;s+=3)i=(t[s]<<16&16711680)+(t[s+1]<<8&65280)+(255&t[s+2]),u.push(r[(o=i)>>18&63]+r[o>>12&63]+r[o>>6&63]+r[63&o]);return u.join("")}n["-".charCodeAt(0)]=62,n["_".charCodeAt(0)]=63},8764:(t,e,r)=>{"use strict";const n=r(9742),i=r(645),o="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;e.Buffer=a,e.SlowBuffer=function(t){+t!=t&&(t=0);return a.alloc(+t)},e.INSPECT_MAX_BYTES=50;const u=2147483647;function s(t){if(t>u)throw new RangeError('The value "'+t+'" is invalid for option "size"');const e=new Uint8Array(t);return Object.setPrototypeOf(e,a.prototype),e}function a(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new TypeError('The "string" argument must be of type string. Received type number');return l(t)}return c(t,e,r)}function c(t,e,r){if("string"==typeof t)return function(t,e){"string"==typeof e&&""!==e||(e="utf8");if(!a.isEncoding(e))throw new TypeError("Unknown encoding: "+e);const r=0|M(t,e);let n=s(r);const i=n.write(t,e);i!==r&&(n=n.slice(0,i));return n}(t,e);if(ArrayBuffer.isView(t))return function(t){if($(t,Uint8Array)){const e=new Uint8Array(t);return p(e.buffer,e.byteOffset,e.byteLength)}return h(t)}(t);if(null==t)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if($(t,ArrayBuffer)||t&&$(t.buffer,ArrayBuffer))return p(t,e,r);if("undefined"!=typeof SharedArrayBuffer&&($(t,SharedArrayBuffer)||t&&$(t.buffer,SharedArrayBuffer)))return p(t,e,r);if("number"==typeof t)throw new TypeError('The "value" argument must not be of type number. Received type number');const n=t.valueOf&&t.valueOf();if(null!=n&&n!==t)return a.from(n,e,r);const i=function(t){if(a.isBuffer(t)){const e=0|y(t.length),r=s(e);return 0===r.length||t.copy(r,0,0,e),r}if(void 0!==t.length)return"number"!=typeof t.length||X(t.length)?s(0):h(t);if("Buffer"===t.type&&Array.isArray(t.data))return h(t.data)}(t);if(i)return i;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof t[Symbol.toPrimitive])return a.from(t[Symbol.toPrimitive]("string"),e,r);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t)}function f(t){if("number"!=typeof t)throw new TypeError('"size" argument must be of type number');if(t<0)throw new RangeError('The value "'+t+'" is invalid for option "size"')}function l(t){return f(t),s(t<0?0:0|y(t))}function h(t){const e=t.length<0?0:0|y(t.length),r=s(e);for(let n=0;n<e;n+=1)r[n]=255&t[n];return r}function p(t,e,r){if(e<0||t.byteLength<e)throw new RangeError('"offset" is outside of buffer bounds');if(t.byteLength<e+(r||0))throw new RangeError('"length" is outside of buffer bounds');let n;return n=void 0===e&&void 0===r?new Uint8Array(t):void 0===r?new Uint8Array(t,e):new Uint8Array(t,e,r),Object.setPrototypeOf(n,a.prototype),n}function y(t){if(t>=u)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+u.toString(16)+" bytes");return 0|t}function M(t,e){if(a.isBuffer(t))return t.length;if(ArrayBuffer.isView(t)||$(t,ArrayBuffer))return t.byteLength;if("string"!=typeof t)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof t);const r=t.leLine truncated
|
!function webpackUniversalModuleDefinition(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SwaggerUIStandalonePreset=e():t.SwaggerUIStandalonePreset=e()}(this,(()=>(()=>{var t={7526(t,e){"use strict";e.byteLength=function byteLength(t){var e=getLens(t),r=e[0],n=e[1];return 3*(r+n)/4-n},e.toByteArray=function toByteArray(t){var e,r,i=getLens(t),a=i[0],s=i[1],u=new o(function _byteLength(t,e,r){return 3*(e+r)/4-r}(0,a,s)),c=0,l=s>0?a-4:a;for(r=0;r<l;r+=4)e=n[t.charCodeAt(r)]<<18|n[t.charCodeAt(r+1)]<<12|n[t.charCodeAt(r+2)]<<6|n[t.charCodeAt(r+3)],u[c++]=e>>16&255,u[c++]=e>>8&255,u[c++]=255&e;2===s&&(e=n[t.charCodeAt(r)]<<2|n[t.charCodeAt(r+1)]>>4,u[c++]=255&e);1===s&&(e=n[t.charCodeAt(r)]<<10|n[t.charCodeAt(r+1)]<<4|n[t.charCodeAt(r+2)]>>2,u[c++]=e>>8&255,u[c++]=255&e);return u},e.fromByteArray=function fromByteArray(t){for(var e,n=t.length,o=n%3,i=[],a=16383,s=0,u=n-o;s<u;s+=a)i.push(encodeChunk(t,s,s+a>u?u:s+a));1===o?(e=t[n-1],i.push(r[e>>2]+r[e<<4&63]+"==")):2===o&&(e=(t[n-2]<<8)+t[n-1],i.push(r[e>>10]+r[e>>4&63]+r[e<<2&63]+"="));return i.join("")};for(var r=[],n=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0;a<64;++a)r[a]=i[a],n[i.charCodeAt(a)]=a;function getLens(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");return-1===r&&(r=e),[r,r===e?0:4-r%4]}function encodeChunk(t,e,n){for(var o,i,a=[],s=e;s<n;s+=3)o=(t[s]<<16&16711680)+(t[s+1]<<8&65280)+(255&t[s+2]),a.push(r[(i=o)>>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return a.join("")}n["-".charCodeAt(0)]=62,n["_".charCodeAt(0)]=63},8287(t,e,r){"use strict";const n=r(7526),o=r(251),i="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;e.Buffer=Buffer,e.SlowBuffer=function SlowBuffer(t){+t!=t&&(t=0);return Buffer.alloc(+t)},e.INSPECT_MAX_BYTES=50;const a=2147483647;function createBuffer(t){if(t>a)throw new RangeError('The value "'+t+'" is invalid for option "size"');const e=new Uint8Array(t);return Object.setPrototypeOf(e,Buffer.prototype),e}function Buffer(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(t)}return from(t,e,r)}function from(t,e,r){if("string"==typeof t)return function fromString(t,e){"string"==typeof e&&""!==e||(e="utf8");if(!Buffer.isEncoding(e))throw new TypeError("Unknown encoding: "+e);const r=0|byteLength(t,e);let n=createBuffer(r);const o=n.write(t,e);o!==r&&(n=n.slice(0,o));return n}(t,e);if(ArrayBuffer.isView(t))return function fromArrayView(t){if(isInstance(t,Uint8Array)){const e=new Uint8Array(t);return fromArrayBuffer(e.buffer,e.byteOffset,e.byteLength)}return fromArrayLike(t)}(t);if(null==t)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if(isInstance(t,ArrayBuffer)||t&&isInstance(t.buffer,ArrayBuffer))return fromArrayBuffer(t,e,r);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(t,SharedArrayBuffer)||t&&isInstance(t.buffer,SharedArrayBuffer)))return fromArrayBuffer(t,e,r);if("number"==typeof t)throw new TypeError('The "value" argument must not be of type number. Received type number');const n=t.valueOf&&t.valueOf();if(null!=n&&n!==t)return Buffer.from(n,e,r);const o=function fromObject(t){if(Buffer.isBuffer(t)){const e=0|checked(t.length),r=createBuffer(e);return 0===r.length||t.copy(r,0,0,e),r}if(void 0!==t.length)return"number"!=typeof t.length||numberIsNaN(t.length)?createBuffer(0):fromArrayLike(t);if("Buffer"===t.type&&Array.isArray(t.data))return fromArrayLike(t.data)}(t);if(o)return o;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof t[Symbol.toPrimitive])return Buffer.from(t[Symbol.toPrimitive]("string"),e,r);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t)}function assertSize(t){if("number"!=typeof t)throw new TypeError('"size" argument must be of type number');if(t<0)throw new RangeError('The value "'+t+'" is invalid for option "size"')}function allocUnsafe(t){return assertSize(t),createBuffer(t<0?0:0|checked(t))}function fromArrayLike(t){const e=t.length<0?0:0|checked(t.length),r=createBuffer(e);for(let n=0;n<e;n+=1)r[n]=255&t[n];return r}function fromArrayBuffer(t,e,r){if(e<0||t.byteLength<e)throw new RangeError('"offset" is outside of buffer bounds');if(t.byteLength<e+(r||0))throw new RangeError('"length" is outside of buffer bounds');let n;return n=void 0===e&&void 0===r?new Uint8Array(t):void 0===r?new Uint8Array(t,e):new Uint8Array(t,e,r),Object.setPrototypeOf(n,Buffer.prototype),n}function checked(t){if(t>=a)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+Line truncated
|
||||||
//# sourceMappingURL=swagger-ui-standalone-preset.js.map
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*!
|
||||||
|
* The buffer module from node.js, for the browser.
|
||||||
|
*
|
||||||
|
* @author Feross Aboukhadijeh <https://feross.org>
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
|
|
||||||
|
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-present, Lee Byron and other contributors.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"swagger-ui-standalone-preset.js","mappings":"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 truncated
|
{"version":3,"file":"swagger-ui-standalone-preset.js","mappings":"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 truncated
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
.swagger-ui{color:#3b4151;font-family:sans-serif/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */}.swagger-ui html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{-webkit-text-decoration-skip:objects;background-color:transparent}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+ILine truncated
|
.swagger-ui{color:#3b4151;container-name:swagger-ui;container-type:inline-size;/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */font-family:sans-serif}.swagger-ui html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{background-color:transparent;-webkit-text-decoration-skip:objects}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpLine truncated
|
||||||
|
|
||||||
/*# sourceMappingURL=swagger-ui.css.map*/
|
/*# sourceMappingURL=swagger-ui.css.map*/
|
||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"swagger-ui.css","mappings":"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,CDrDb,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,qDC8BW,CD7BX,uDC8BY,CD7BZ,wDC8Ba,CD5Bb,6CCyCW,CDxCX,wCCyCM,CDxCN,8CCyCY,CDxCZ,2CCyCS,CDxCT,yCCyCO,CDxCP,wCCyCS,CDxCT,iDCyCe,CDxCf,2CCyCS,CDxCT,iDCyCe,CDxCf,8CCyCY,CDxCZ,6CCyCW,CDxCX,yCCyCO,CDxCP,+CCyCa,CDxCb,+CCyCa,CDxCb,0CCyCQ,CDxCR,gDCyCc,CDxCd,yCCyCO,CDxCP,8CCyCY,CDxCZ,yCCyCO,CDxCP,+CCyCa,CDxCb,kDCyCgB,CDxChB,gDCyCc,CDxCd,iDCyCe,CDxCf,kDCyCgB,CDxChB,+CCyCa,CDvCb,oDCVc,CDWd,6CE3DE,gCDmBmB,CClBnB,sCDmBgB,CClBhB,qCDmBgB,CClBhB,oCDmBgB,CClBhB,mCDmBgB,CClBhB,sCDmBqB,CClBrB,yCDmBmB,CClBnB,wBACI,yBACA,0BAEJ,qBACI,4BACA,6BAEJ,uBAEI,4BADA,wBACA,CAEJ,sBAEI,6BADA,yBACA,CAGN,mCACE,mCDNmB,CCOnB,yCDNgB,CCOhB,wCDNgB,CCOhB,uCDNgB,CCOhB,sCDNgB,CCOhB,yCDNqB,CCOrB,4CDNmB,CCOnB,2BACI,yBACA,0BAEJ,wBACI,4BACA,6BAEJ,0BAEI,4BADA,wBACA,CAEJ,yBAEI,6BADA,yBACA,EAIN,uDACE,kCDhCmB,CCiCnB,wCDhCgB,CCiChB,uCDhCgB,CCiChB,sCDhCgB,CCiChB,qCDhCgB,CCiChB,wCDhCqB,CCiCrB,2CDhCmB,CCiCnB,0BACI,yBACA,0BAEJ,uBACI,4BACA,6BAEJ,yBAEI,4BADA,wBACA,CAEJ,wBAEI,6BADA,yBACA,EAIN,mCACE,kCD1DmB,CC2DnB,wCD1DgB,CC2DhB,uCD1DgB,CC2DhB,sCD1DgB,CC2DhB,qCD1DgB,CC2DhB,wCD1DqB,CC2DrB,2CD1DmB,CC2DnB,0BACI,yBACA,0BAEJ,uBACI,4BACA,6BAEJ,yBAEI,4BADA,wBACA,CAEJ,wBAEI,6BADA,yBACA,ECrGN,2CACA,2CACA,yCACA,uCAEA,mCACE,8CACA,8CACA,4CACA,2CAGF,uDACE,6CACA,6CACA,2CACA,0CAGF,mCACE,6CACA,6CACA,2CACA,0CCvBF,+BH6BoB,CG5BpB,qCH6BiB,CG5BjB,oCH6BiB,CG5BjB,mCH6BiB,CG5BjB,kCH6BiB,CG5BjB,kCH6BiB,CG1BjB,oCHqBoB,CGpBpB,sCHoBoB,CGnBpB,uCHmBoB,CGlBpB,qCHkBoB,CGhBpB,mCACE,kCHekB,CGdlB,wCHee,CGdf,uCHee,CGdf,sCHee,CGdf,qCHee,CGdf,qCHee,CGdf,uCHSkB,CGRlB,yCHQkB,CGPlB,0CHOkB,CGNlB,wCHMkB,EGHpB,uDACE,iCHEkB,CGDlB,uCHEe,CGDf,sCHEe,CGDf,qCHEe,CGDf,oCHEe,CGDf,oCHEe,CGDf,sCHJkB,CGKlB,wCHLkB,CGMlB,yCHNkB,CGOlB,uCHPkB,EGUpB,mCACE,iCHXkB,CGYlB,uCHXe,CGYf,sCHXe,CGYf,qCHXe,CGYf,oCHXe,CGYf,oCHXe,CGYf,sCHjBkB,CGkBlB,wCHlBkB,CGmBlB,yCHnBkB,CGoBlB,uCHpBkB,EIxCpB,2DJ8Ce,CI7Cf,2DJ8Ce,CI7Cf,+DJ8Ce,CI7Cf,6DJ8Ce,CI7Cf,6DJ8Ce,CI5Cf,mCACE,8DJuCa,CItCb,8DJuCa,CItCb,kEJuCa,CItCb,gEJuCa,CItCb,gEJuCa,EIpCf,uDACE,6DJ+Ba,CI9Bb,6DJ+Ba,CI9Bb,iEJ+Ba,CI9Bb,+DJ+Ba,CI9Bb,+DJ+Ba,EI5Bf,mCACE,6DJuBa,CItBb,6DJuBa,CItBb,iEJuBa,CItBb,+DJuBa,CItBb,+DJuBa,EKxDf,iBACE,gBACA,kBACA,gBCkBF,yBACA,6BACA,+BACA,2BAEA,4BACA,gCACA,kCACA,8BAEA,4BACA,gCACA,kCACA,8BAEA,8BACA,kCACA,oCACA,gCAEA,8BACA,kCACA,oCACA,gCAGA,4BAGE,SACA,OAFA,QADA,KAGA,CAGF,mCACE,4BACA,8BACA,gCACA,kCACA,+BACA,iCACA,mCACA,qCACA,+BACA,iCACA,mCACA,qCACA,iCACA,qCACA,uCACA,mCACA,iCACA,qCACA,uCACA,mCACA,+BAGE,SACA,OAFA,QADA,KAGA,EAIJ,uDACE,2BACA,6BACA,+BACA,iLine truncated
|
{"version":3,"file":"swagger-ui.css","mappings":"AAAA,YCII,aCcU,CFfd,yBACE,2BACA,4EGEF,CFJE,sBEIF,CDWc,iCCCZ,0BACA,8BACA,kBAUF,QACE,iHAOF,aAME,gBAQF,aACE,eACA,4DAWF,aAGE,oBAOF,eACE,gBAQF,sBACE,SACA,iBACA,iBAQF,+BACE,cACA,eAWF,4BACE,qCACA,yBAQF,kBACE,CACA,yBACA,CADA,wCACA,CADA,gCACA,kCAOF,mBAEE,CAOF,kBAPE,CASA,kDAQF,+BAGE,cACA,iBAOF,iBACE,kBAOF,qBACE,WACA,mBAOF,aACE,iCAQF,aAEE,cACA,kBACA,wBACA,iBAGF,aACE,iBAGF,SACE,qCAUF,oBAEE,mCAOF,YACE,SACA,iBAOF,iBACE,4BAOF,eACE,mGAWF,sBAKE,eACA,iBACA,SACA,sCAQF,gBAEE,uCAQF,mBAEE,sGASF,yBAIE,yKAOF,iBAIE,UACA,6JAOF,6BAIE,sBAOF,0BACE,oBAUF,qBACE,cACA,cACA,eACA,UACA,mBACA,sBAQF,oBACE,wBACA,sBAOF,aACE,sDAQF,qBAEE,UACA,2GAOF,WAEE,2BAQF,4BACE,oBACA,8GAOF,uBAEE,0CAQF,yBACE,aACA,sCAWF,aAEE,qBAOF,iBACE,oBAUF,oBACE,CAQA,0CAUF,YACE,sBCnbF,kDACA,kDACA,+CCDA,yEACE,4BAGF,yEACE,iCAGF,kEACE,kCAGF,kEACE,2tBClBF,qBAkCE,2BCrBF,QACE,kBACA,iCAGF,sDACA,sDAEA,kDACA,sDAEA,oDACA,mDAEA,oDACA,mDAEA,qDACA,mDAEA,sDAKI,QACA,CAEA,WACA,CAHA,MACA,CALJ,iBACI,CACA,OACA,CAFA,KACA,CAGA,UACA,CACA,WACA,oCAGJ,6BACI,QACE,kBACA,oCAEF,yDACA,yDACA,qDACA,yDACA,uDACA,sDACA,uDACA,sDACA,wDACA,sDACA,yDAII,QACA,CAEA,WACA,CAHA,MACA,CALJ,iBACI,CACA,OACA,CAFA,KACA,CAGA,UACA,CACA,WACA,yDAIR,4BACI,QACE,kBACA,mCAEF,wDACA,wDACA,oDACA,wDACA,sDACA,qDACA,sDACA,qDACA,uDACA,qDACA,wDAII,QACA,CAEA,WACA,CAHA,MACA,CALJ,iBACI,CACA,OACA,CAFA,KACA,CAGA,UACA,CACA,WACA,qCAIR,4BACI,QACE,kBACA,mCAEF,wDACA,wDACA,oDACA,wDACA,sDACA,qDACA,sDACA,qDACA,uDACA,qDACA,wDAII,QACA,CAEA,WACA,CAHA,MACA,CALJ,iBACI,CACA,OACA,CAFA,KACA,CAGA,UACA,CACA,WACA,kBC3HR,kCCQE,qDACA,qEAEF,sBACE,wDACA,0FAGF,qBACE,uDACA,sEAGF,qBACE,uDACA,0DCVA,uBACA,CAFF,2BAEE,qBAIA,uBACA,CAFF,2BAEE,uBAIA,wBACA,CAFF,2BAEE,wBAIA,0BACA,CAFF,2BAEE,sBAIA,qBACA,CAFF,2BAEE,oCAGF,0BAEI,uBACA,CAFF,2BAEE,wBAIA,uBACA,CAFF,2BAEE,0BAIA,wBACA,CAFF,2BAEE,2BAIA,0BACA,CAFF,2BAEE,yBAIA,qBACA,CAFF,2BAEE,yDAIJ,yBAEI,uBACA,CAFF,2BAEE,uBAIA,uBACA,CAFF,2BAEE,yBAIA,wBACA,CAFF,2BAEE,0BAIA,0BACA,CAFF,2BAEE,wBAIA,qBACA,CAFF,2BAEE,qCAIJ,yBAEI,uBACA,CAFF,2BAEE,uBAIA,uBACA,CAFF,2BAEE,yBAIA,wBACA,CAFF,2BAEE,0BAIA,0BACA,CAFF,2BAEE,wBAIA,qBACA,CAFF,2BAEE,uBChHJ,mDACA,qDACA,6CAEA,wBACE,sDACA,wDACA,kEAGF,uBACE,qDACA,uDACA,8CAGF,uBACE,qDACA,uDACA,2BCPA,oDACA,4DACA,gEACA,kEACA,8DACA,oEAGF,mBACE,uDACA,+DACA,mEACA,qEACA,iEACA,yFAGF,kBACE,sDACA,8DACA,kEACA,oEACA,gEACA,qEAGF,kBACE,sDACA,8DACA,kEACA,oEACA,gEACA,wDCnCF,iBC2CQ,4BD1CR,iBC2Ca,2BD1Cb,iBC2CY,0BD1CZ,iBC2CW,sBD1CX,iBC2CO,wBD1CP,iBC2CS,8BD1CT,iBC2Ce,2BD1Cf,iBC2CY,4BD1CZ,iBC2Ca,4BD1Cb,oBC2Ca,uBD1Cb,iBC2CQ,0BDzCR,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,gCCuDW,2BDtDX,iCCuDY,4BDtDZ,iCCuDa,0BDrDb,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,4BC8BW,2BD7BX,6BC8BY,4BD7BZ,6BC8Ba,0BD5Bb,oBCyCW,qBDxCX,oBCyCM,2BDxCN,oBCyCY,wBDxCZ,oBCyCS,sBDxCT,oBCyCO,wBDxCP,iBCyCS,8BDxCT,oBCyCe,wBDxCf,oBCyCS,8BDxCT,oBCyCe,2BDxCf,oBCyCY,0BDxCZ,oBCyCW,sBDxCX,oBCyCO,4BDxCP,oBCyCa,4BDxCb,oBCyCa,uBDxCb,oBCyCQ,6BDxCR,oBCyCc,sBDxCd,oBCyCO,2BDxCP,oBCyCY,sBDxCZ,oBCyCO,4BDxCP,oBCyCa,+BDxCb,oBCyCgB,6BDxChB,oBCyCc,8BDxCd,oBCyCe,+BDxCf,oBCyCgB,4BDxChB,oBCyCa,6BDvCb,wBCVc,yBDWd,sCE3DE,eDmBmB,kBClBnB,qBDmBgB,kBClBhB,oBDmBgB,kBClBhB,mBDmBgB,kBClBhB,kBDmBgB,qBClBhB,kBDmBqB,sBClBrB,oBDmBmB,yBClBnB,wBACI,0BACA,sBAEJ,2BACI,6BACA,wBAGA,2BACA,CAFJ,wBAEI,uBAGA,4BACA,CAFJ,yBAEI,oCAGN,oBACE,eDNmB,qBCOnB,qBDNgB,qBCOhB,oBDNgB,qBCOhB,mBDNgB,qBCOhB,kBDNgB,wBCOhB,kBDNqB,yBCOrB,oBDNmB,4BCOnB,wBACI,0BACA,yBAEJ,2BACI,6BACA,2BAGA,2BACA,CAFJ,wBAEI,0BAGA,4BACA,CAFJ,yBAEI,yDAIN,mBACE,eDhCmB,oBCiCnB,qBDhCgB,oBCiChB,oBDhCgB,oBCiChB,mBDhCgB,oBCiChB,kBDhCgB,uBCiChB,kBDhCqB,wBCiCrB,oBDhCmB,2BCiCnB,wBACI,0BACA,wBAEJ,2BACI,6BACA,0BAGA,2BACA,CAFJ,wBAEI,yBAGA,4BACA,CAFJ,yBAEI,qCAIN,mBACE,eD1DmB,oBC2DnB,qBD1DgB,oBC2DhB,oBD1DgB,oBC2DhB,mBD1DgB,oBC2DhB,kBD1DgB,uBC2DhB,kBD1DqB,wBC2DrB,oBD1DmB,2BC2DnB,wBACI,0BACA,wBAEJ,2BACI,6BACA,0BAGA,2BACA,CAFJ,wBAEI,yBAGA,4BACA,CAFJ,yBAEI,yBCrGN,2CACA,0CACA,wCACA,qDAEA,0BACE,8CACA,6CACA,2CACA,0EAGF,yBACE,6CACA,4CACA,0CACA,sDAGF,yBACE,6CACA,4CACA,0CACA,oCCvBF,cH6BoB,kBG5BpB,oBH6BiB,kBG5BjB,mBH6BiB,kBG5BjB,kBH6BiB,kBG5BjB,iBH6BiB,kBG5BjB,iBH6BiB,mBG1BjB,kBHqBoB,mBGpBpB,oBHoBoB,mBGnBpB,qBHmBoB,mBGlBpB,mBHkBoB,oCGhBpB,oBACE,cHekB,qBGdlB,oBHee,qBGdf,mBHee,qBGdf,kBHee,qBGdf,iBHee,qBGdf,iBHee,sBGdf,kBHSkB,sBGRlB,oBHQkB,sBGPlB,qBHOkB,sBGNlB,mBHMkB,yDGHpB,mBACE,cHEkB,oBGDlB,oBHEe,oBGDf,mBHEe,oBGDf,kBHEe,oBGDf,iBHEe,oBGDf,iBHEe,qBGDf,kBHJkB,qBGKlB,oBHLkB,qBGMlB,qBHNkB,qBGOlB,mBHPkB,qCGUpB,mBACE,cHXkB,oBGYlB,oBHXe,oBGYf,mBHXe,oBGYf,kBHXe,oBGYf,iBHXe,oBGYf,iBHXe,qBGYf,kBHjBkB,qBGkBlB,oBHlBkB,qBGmBlB,qBHnBkB,qBGoBlB,mBHpBkB,wBIxCpB,qCJ8Ce,uBI7Cf,qCJ8Ce,uBI7Cf,yCJ8Ce,uBI7Cf,uCJ8Ce,uBI7Cf,uCJ8Ce,oCI5Cf,yBACE,qCJuCa,0BItCb,qCJuCa,0BItCb,yCJuCa,0BItCb,uCJuCa,0BItCb,uCJuCa,yDIpCf,wBACE,qCJ+Ba,yBI9Bb,qCJ+Ba,yBI9Bb,yCJ+Ba,yBI9Bb,uCJ+Ba,yBI9Bb,uCJ+Ba,qCI5Bf,wBACE,qCJuBa,yBItBb,qCJuBa,yBItBb,yCJuBa,yBItBb,uCJuBa,yBItBb,uCJuBa,mBKxDf,eACE,kBACA,gBACA,oBCkBF,2BACA,8BACALine truncated
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUICore=t():e.SwaggerUICore=t()}(this,(()=>(()=>{var e={6024:(e,t,n)=>{"use strict";n.d(t,{Z:()=>E});var s=n(4250),r=n.n(s),a=n(1093),o=n.n(a),l=n(8493),c=n.n(l),i=n(3942),u=n.n(i),p=n(6689),m=n.n(p);const d=require("react-immutable-pure-component");var h=n.n(d),g=n(8082),f=n.n(g),y=n(580),S=n.n(y);const v=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class E extends(h()){constructor(){super(...arguments),o()(this,"getModelName",(e=>-1!==c()(e).call(e,"#/definitions/")?v(e.replace(/^.*#\/definitions\//,"")):-1!==c()(e).call(e,"#/components/schemas/")?v(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),o()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:s,schema:a,required:o,name:l,isRef:c,specPath:i,displayName:u,includeReadOnly:p,includeWriteOnly:d}=this.props;const h=e("ObjectModel"),g=e("ArrayModel"),f=e("PrimitiveModel");let y="object",S=a&&a.get("$$ref");if(!l&&S&&(l=this.getModelName(S)),!a&&S&&(a=this.getRefSchema(l)),!a)return m().createElement("span",{className:"model model-title"},m().createElement("span",{className:"model-title__text"},u||l),m().createElement("img",{src:n(2517),height:"20px",width:"20px"}));const v=s.isOAS3()&&a.get("deprecated");switch(c=void 0!==c?c:!!S,y=a&&a.get("type")||y,y){case"object":return m().createElement(h,r()({className:"object"},this.props,{specPath:i,getConfigs:t,schema:a,name:l,deprecated:v,isRef:c,includeReadOnly:p,includeWriteOnly:d}));case"array":return m().createElement(g,r()({className:"array"},this.props,{getConfigs:t,schema:a,name:l,deprecated:v,required:o,includeReadOnly:p,includeWriteOnly:d}));default:return m().createElement(f,r()({},this.props,{getComponent:e,getConfigs:t,schema:a,name:l,deprecated:v,required:o}))}}}o()(E,"propTypes",{schema:u()(f()).isRequired,getComponent:S().func.isRequired,getConfigs:S().func.isRequired,specSelectors:S().object.isRequired,name:S().string,displayName:S().string,isRef:S().bool,required:S().bool,expandDepth:S().number,depth:S().number,specPath:f().list.isRequired,includeReadOnly:S().bool,includeWriteOnly:S().bool})},5623:(e,t,n)=>{"use strict";n.d(t,{Z:()=>d});var s=n(1093),r=n.n(s),a=n(7252),o=n.n(a),l=n(6689),c=n.n(l),i=n(3883),u=n.n(i),p=(n(580),n(1669)),m=n(7504);class d extends c().Component{constructor(e,t){super(e,t),r()(this,"getDefinitionUrl",(()=>{let{specSelectors:e}=this.props;return new(u())(e.url(),m.Z.location).toString()}));let{getConfigs:n}=e,{validatorUrl:s}=n();this.state={url:this.getDefinitionUrl(),validatorUrl:void 0===s?"https://validator.swagger.io/validator":s}}UNSAFE_componentWillReceiveProps(e){let{getConfigs:t}=e,{validatorUrl:n}=t();this.setState({url:this.getDefinitionUrl(),validatorUrl:void 0===n?"https://validator.swagger.io/validator":n})}render(){let{getConfigs:e}=this.props,{spec:t}=e(),n=(0,p.Nm)(this.state.validatorUrl);return"object"==typeof t&&o()(t).length?null:this.state.url&&(0,p.hW)(this.state.validatorUrl)&&(0,p.hW)(this.state.url)?c().createElement("span",{className:"float-right"},c().createElement("a",{target:"_blank",rel:"noopener noreferrer",href:`${n}/debug?url=${encodeURIComponent(this.state.url)}`},c().createElement(h,{src:`${n}?url=${encodeURIComponent(this.state.url)}`,alt:"Online validator badge"}))):null}}class h extends c().Component{constructor(e){super(e),this.state={loaded:!1,error:!1}}componentDidMount(){const e=new Image;e.onload=()=>{this.setState({loaded:!0})},e.onerror=()=>{this.setState({error:!0})},e.src=this.props.src}UNSAFE_componentWillReceiveProps(e){if(e.src!==this.props.src){const t=new Image;t.onload=()=>{this.setState({loaded:!0})},t.onerror=()=>{this.setState({error:!0})},t.src=e.src}}render(){return this.state.error?c().createElement("img",{alt:"Error"}):this.state.loaded?c().createElement("img",{src:this.props.src,alt:this.props.alt}):null}}},2552:(e,t,n)=>{"use strict";n.d(t,{Z:()=>m,s:()=>d});var s=n(6689),r=n.n(s),a=(n(580),n(963));const o=require("remarkable/linkify"),l=require("dompurify");var c=n.n(l),i=n(9003),u=n.n(i);function p(e){let{source:t,className:n="",getConfigs:s}=e;if("string"!=typeof t)return null;const l=new a.Remarkable({html:!0,typographer:!0,breaks:!0,linkTarget:"_blank"}).use(o.linkify);l.core.ruler.disable(["replacements","smartquotes"]);const{useUnsafeMarkdown:c}=s(),i=l.render(t),p=d(i,{useUnsafeMarkdown:c});return t&&i&&p?r().createElement("div",{className:u()(n,"markdown"),dangerouslySetInnerHTML:{__html:p}}):null}c().addHook&&c().addHook("beforeSanitizeElements",(function(e){return e.href&&e.setAttribute("rel","noopener noreferrer"),e})),p.defaultProps={getConfigs:()=>({useUnsafeMarkdown:!1})};const m=p;function d(e){let{useUnsafeMarkdown:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguLine truncated
|
!function webpackUniversalModuleDefinition(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUICore=t():e.SwaggerUICore=t()}(this,(()=>(()=>{"use strict";var e={158(e){e.exports=require("buffer")}},t={};function __webpack_require__(a){var r=t[a];if(void 0!==r)return r.exports;var n=t[a]={exports:{}};return e[a](n,n.exports,__webpack_require__),n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var a in t)__webpack_require__.o(t,a)&&!__webpack_require__.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var a={};__webpack_require__.d(a,{default:()=>Cl});var r={};__webpack_require__.r(r),__webpack_require__.d(r,{CLEAR:()=>$,CLEAR_BY:()=>L,NEW_AUTH_ERR:()=>V,NEW_SPEC_ERR:()=>R,NEW_SPEC_ERR_BATCH:()=>J,NEW_THROWN_ERR:()=>P,NEW_THROWN_ERR_BATCH:()=>T,clear:()=>clear,clearBy:()=>clearBy,newAuthErr:()=>newAuthErr,newSpecErr:()=>newSpecErr,newSpecErrBatch:()=>newSpecErrBatch,newThrownErr:()=>newThrownErr,newThrownErrBatch:()=>newThrownErrBatch});var n={};__webpack_require__.r(n),__webpack_require__.d(n,{AUTHORIZE:()=>ue,AUTHORIZE_OAUTH2:()=>he,CONFIGURE_AUTH:()=>ge,LOGOUT:()=>de,RESTORE_AUTHORIZATION:()=>ye,SHOW_AUTH_POPUP:()=>me,authPopup:()=>authPopup,authorize:()=>authorize,authorizeAccessCodeWithBasicAuthentication:()=>authorizeAccessCodeWithBasicAuthentication,authorizeAccessCodeWithFormParams:()=>authorizeAccessCodeWithFormParams,authorizeApplication:()=>authorizeApplication,authorizeOauth2:()=>authorizeOauth2,authorizeOauth2WithPersistOption:()=>authorizeOauth2WithPersistOption,authorizePassword:()=>authorizePassword,authorizeRequest:()=>authorizeRequest,authorizeWithPersistOption:()=>authorizeWithPersistOption,configureAuth:()=>configureAuth,logout:()=>logout,logoutWithPersistOption:()=>logoutWithPersistOption,persistAuthorizationIfNeeded:()=>persistAuthorizationIfNeeded,preAuthorizeImplicit:()=>preAuthorizeImplicit,restoreAuthorization:()=>restoreAuthorization,showDefinitions:()=>showDefinitions});var s={};__webpack_require__.r(s),__webpack_require__.d(s,{authorized:()=>ve,definitionsForRequirements:()=>definitionsForRequirements,definitionsToAuthorize:()=>_e,getConfigs:()=>we,getDefinitionsByNames:()=>getDefinitionsByNames,isAuthorized:()=>isAuthorized,selectAuthPath:()=>selectAuthPath,shownDefinitions:()=>Ee});var o={};__webpack_require__.r(o),__webpack_require__.d(o,{TOGGLE_CONFIGS:()=>qe,UPDATE_CONFIGS:()=>Ie,downloadConfig:()=>downloadConfig,getConfigByUrl:()=>getConfigByUrl,loaded:()=>actions_loaded,toggle:()=>toggle,update:()=>update});var l={};__webpack_require__.r(l),__webpack_require__.d(l,{get:()=>get});var c={};__webpack_require__.r(c),__webpack_require__.d(c,{transform:()=>transform});var i={};__webpack_require__.r(i),__webpack_require__.d(i,{transform:()=>parameter_oneof_transform});var p={};__webpack_require__.r(p),__webpack_require__.d(p,{allErrors:()=>Ye,lastError:()=>Qe});var m={};__webpack_require__.r(m),__webpack_require__.d(m,{SHOW:()=>nt,UPDATE_FILTER:()=>at,UPDATE_LAYOUT:()=>tt,UPDATE_MODE:()=>rt,changeMode:()=>changeMode,show:()=>actions_show,updateFilter:()=>updateFilter,updateLayout:()=>updateLayout});var u={};__webpack_require__.r(u),__webpack_require__.d(u,{current:()=>current,currentFilter:()=>currentFilter,isShown:()=>isShown,showSummary:()=>ot,whatMode:()=>whatMode});var d={};__webpack_require__.r(d),__webpack_require__.d(d,{taggedOperations:()=>taggedOperations});var h={};__webpack_require__.r(h),__webpack_require__.d(h,{getActiveLanguage:()=>it,getDefaultExpanded:()=>pt,getGenerators:()=>ct,getSnippetGenerators:()=>getSnippetGenerators});var g={};__webpack_require__.r(g),__webpack_require__.d(g,{JsonSchemaArrayItemFile:()=>JsonSchemaArrayItemFile,JsonSchemaArrayItemText:()=>JsonSchemaArrayItemText,JsonSchemaForm:()=>JsonSchemaForm,JsonSchema_array:()=>JsonSchema_array,JsonSchema_boolean:()=>JsonSchema_boolean,JsonSchema_object:()=>JsonSchema_object,JsonSchema_string:()=>JsonSchema_string});var y={};__webpack_require__.r(y),__webpack_require__.d(y,{allowTryItOutFor:()=>allowTryItOutFor,basePath:()=>ga,canExecuteScheme:()=>canExecuteScheme,consumes:()=>pa,consumesOptionsFor:()=>consumesOptionsFor,contentTypeValues:()=>contentTypeValues,currentProducesFor:()=>currentProducesFor,definitions:()=>ha,externalDocs:()=>na,findDefinition:()=>findDefinition,getOAS3RequiredRequestBodyContentType:()=>getOAS3RequiredRequestBodyContentType,getParameter:()=>getParameter,hasHost:()=>Ca,host:()=>ya,info:()=>ra,isMediaTypeSchemaPropertiesEqual:()=>isMediaTypeSchemaPropertiesEqual,isOAS3:()=>aa,lastError:()=>Ht,mutatedLine truncated
|
||||||
//# sourceMappingURL=swagger-ui.js.map
|
//# sourceMappingURL=swagger-ui.js.map
|
||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"swagger-ui.js","mappings":"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 truncated
|
{"version":3,"file":"swagger-ui.js","mappings":"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 truncated
|
||||||
@@ -8,7 +8,7 @@ if(file_exists('inc/vars.local.inc.php')) {
|
|||||||
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
||||||
|
|
||||||
header('Content-type: application/json');
|
header('Content-type: application/json');
|
||||||
if (strtolower($_GET['Protocol']) == 'activesync' && getenv('SKIP_SOGO') != "y") {
|
if (strtolower($_GET['Protocol']) == 'activesync' && $autodiscover_config['autodiscoverType'] == 'activesync' && getenv('SKIP_SOGO') != "y") {
|
||||||
echo '{"Protocol":"ActiveSync","Url":"' . $autodiscover_config['activesync']['url'] . '"}';
|
echo '{"Protocol":"ActiveSync","Url":"' . $autodiscover_config['activesync']['url'] . '"}';
|
||||||
}
|
}
|
||||||
elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') {
|
elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ error_reporting(0);
|
|||||||
|
|
||||||
$data = trim(file_get_contents("php://input"));
|
$data = trim(file_get_contents("php://input"));
|
||||||
|
|
||||||
if (strpos($data, 'autodiscover/outlook/responseschema') !== false) {
|
if (strpos($data, 'autodiscover/outlook/responseschema') !== false && $autodiscover_config['autodiscoverType'] == 'activesync') {
|
||||||
$autodiscover_config['autodiscoverType'] = 'imap';
|
$autodiscover_config['autodiscoverType'] = 'imap';
|
||||||
if ($autodiscover_config['useEASforOutlook'] == 'yes' &&
|
if ($autodiscover_config['useEASforOutlook'] == 'yes' &&
|
||||||
// Office for macOS does not support EAS
|
// Office for macOS does not support EAS
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ table.dataTable>tbody>tr.child ul.dtr-details>li {
|
|||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.129);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.129);
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
/* like the first entry has no top padding, the last one needs no bottom padding */
|
||||||
|
table.dataTable>tbody>tr.child ul.dtr-details>li:last-child {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
||||||
background-color: var(--mc-dtr-hover-bg);
|
background-color: var(--mc-dtr-hover-bg);
|
||||||
@@ -28,7 +32,8 @@ table.dataTable td.dt-control:before {
|
|||||||
color: var(--mc-dtr-color);
|
color: var(--mc-dtr-color);
|
||||||
height: 1em;
|
height: 1em;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
line-height: 1.25em;
|
line-height: 1em;
|
||||||
|
margin-top: calc(-0.5em - 1.5px);
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -39,11 +44,29 @@ table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before,
|
|||||||
table.dataTable td.dt-control:before {
|
table.dataTable td.dt-control:before {
|
||||||
background-color: var(--mc-dtr-active-bg) !important;
|
background-color: var(--mc-dtr-active-bg) !important;
|
||||||
}
|
}
|
||||||
|
/* The default initial order targets column 0, which is usually the non-orderable expand column */
|
||||||
|
table.dataTable thead > tr > th.sorting_disabled.sorting_asc,
|
||||||
|
table.dataTable thead > tr > th.sorting_disabled.sorting_desc {
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
table.dataTable thead > tr > th.sorting_disabled.sorting_asc:before,
|
||||||
|
table.dataTable thead > tr > th.sorting_disabled.sorting_asc:after,
|
||||||
|
table.dataTable thead > tr > th.sorting_disabled.sorting_desc:before,
|
||||||
|
table.dataTable thead > tr > th.sorting_disabled.sorting_desc:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
table.dataTable.table-striped>tbody>tr>td {
|
table.dataTable.table-striped>tbody>tr>td {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
table.dataTable.table-striped>tbody>tr>td>input[type="checkbox"] {
|
table.dataTable.table-striped>tbody>tr>td>input[type="checkbox"] {
|
||||||
margin-top: 7px;
|
/* centers the 1em checkbox in the 1.5em line, level with the expand control */
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
/* the vertical margin is meant for the tag editor in edit forms, in tables it only adds height */
|
||||||
|
table.dataTable .tag-badge {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
td.dtr-col-lg {
|
td.dtr-col-lg {
|
||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
|
|||||||
@@ -139,6 +139,13 @@ body {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
.badge .bi {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
/* keeps icons from making a badge taller than one with text only */
|
||||||
|
.badge .bi::before {
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
.btn-group-xs > .btn, .btn-xs {
|
.btn-group-xs > .btn, .btn-xs {
|
||||||
padding: .25rem .4rem;
|
padding: .25rem .4rem;
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
@@ -343,6 +350,7 @@ code {
|
|||||||
}
|
}
|
||||||
.tag-badge .bi {
|
.tag-badge .bi {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
line-height: 0;
|
||||||
}
|
}
|
||||||
.tag-badge.btn-badge:hover {
|
.tag-badge.btn-badge:hover {
|
||||||
filter: brightness(0.9);
|
filter: brightness(0.9);
|
||||||
|
|||||||
@@ -191,7 +191,7 @@
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
margin-top: -15px;
|
margin-top: calc(-1rem - 1.5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
li .dtr-data {
|
li .dtr-data {
|
||||||
|
|||||||
@@ -601,6 +601,13 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
|
|||||||
|
|
||||||
// validate mailcow_password
|
// validate mailcow_password
|
||||||
$mailcow_password = $user_res['attributes']['mailcow_password'][0];
|
$mailcow_password = $user_res['attributes']['mailcow_password'][0];
|
||||||
|
// Keycloak returns binary LDAP attributes like userPassword Base64 encoded
|
||||||
|
if (substr($mailcow_password, 0, 1) != '{') {
|
||||||
|
$decoded_password = base64_decode($mailcow_password, true);
|
||||||
|
if ($decoded_password !== false && substr($decoded_password, 0, 1) == '{') {
|
||||||
|
$mailcow_password = $decoded_password;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!verify_hash($mailcow_password, $pass)) {
|
if (!verify_hash($mailcow_password, $pass)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ function fwdhost($_action, $_data = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$hosts = array_unique(array_map(function ($host) {
|
||||||
|
$net = explode('/', $host, 2);
|
||||||
|
if (filter_var($net[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
|
$net[0] = inet_ntop(inet_pton($net[0]));
|
||||||
|
}
|
||||||
|
return implode('/', $net);
|
||||||
|
}, $hosts));
|
||||||
foreach ($hosts as $host) {
|
foreach ($hosts as $host) {
|
||||||
try {
|
try {
|
||||||
$redis->hSet('WHITELISTED_FWD_HOST', $host, $source);
|
$redis->hSet('WHITELISTED_FWD_HOST', $host, $source);
|
||||||
|
|||||||
@@ -1394,6 +1394,18 @@ function set_tfa($_data) {
|
|||||||
case "webauthn":
|
case "webauthn":
|
||||||
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
||||||
|
|
||||||
|
// Refuse incomplete WebAuthn registrations. Without this guard a request
|
||||||
|
// that does not contain the parsed credential data (e.g. a plain form
|
||||||
|
// POST) would silently insert an empty keyHandle while reporting success.
|
||||||
|
if (empty($_data['registration']) || empty($_data['registration']->credentialId)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
|
'msg' => 'webauthn_registration_failed'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
|
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
|
||||||
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
|
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
@@ -1416,11 +1428,11 @@ function set_tfa($_data) {
|
|||||||
// Block TFA removal if force_tfa policy is active
|
// Block TFA removal if force_tfa policy is active
|
||||||
$is_forced_tfa = false;
|
$is_forced_tfa = false;
|
||||||
if ($_SESSION['mailcow_cc_role'] === 'user') {
|
if ($_SESSION['mailcow_cc_role'] === 'user') {
|
||||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
$stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
||||||
$stmt_check->execute(array($username));
|
$stmt_check->execute(array($username));
|
||||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||||
} else {
|
} else {
|
||||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
$stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
||||||
$stmt_check->execute(array($username));
|
$stmt_check->execute(array($username));
|
||||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||||
}
|
}
|
||||||
@@ -1687,11 +1699,11 @@ function unset_tfa_key($_data) {
|
|||||||
// Block key removal if force_tfa policy is active
|
// Block key removal if force_tfa policy is active
|
||||||
$is_forced_tfa = false;
|
$is_forced_tfa = false;
|
||||||
if ($_SESSION['mailcow_cc_role'] === 'user') {
|
if ($_SESSION['mailcow_cc_role'] === 'user') {
|
||||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
$stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
|
||||||
$stmt_check->execute(array($username));
|
$stmt_check->execute(array($username));
|
||||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||||
} else {
|
} else {
|
||||||
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
$stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
|
||||||
$stmt_check->execute(array($username));
|
$stmt_check->execute(array($username));
|
||||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||||
}
|
}
|
||||||
@@ -2420,7 +2432,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
case "use_ssl":
|
case "use_ssl":
|
||||||
case "use_tls":
|
case "use_tls":
|
||||||
case "login_provisioning":
|
case "login_provisioning":
|
||||||
case "ignore_ssl_errors":
|
case "ignore_ssl_error":
|
||||||
$settings[$row["key"]] = boolval($row["value"]);
|
$settings[$row["key"]] = boolval($row["value"]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -2444,7 +2456,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
if ($settings["authsource"] == "ldap"){
|
if ($settings["authsource"] == "ldap"){
|
||||||
$settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl'];
|
$settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl'];
|
||||||
$settings['use_tls'] = !isset($settings['use_tls']) ? false : $settings['use_tls'];
|
$settings['use_tls'] = !isset($settings['use_tls']) ? false : $settings['use_tls'];
|
||||||
$settings['ignore_ssl_errors'] = !isset($settings['ignore_ssl_errors']) ? false : $settings['ignore_ssl_errors'];
|
$settings['ignore_ssl_error'] = !isset($settings['ignore_ssl_error']) ? false : $settings['ignore_ssl_error'];
|
||||||
}
|
}
|
||||||
return $settings;
|
return $settings;
|
||||||
break;
|
break;
|
||||||
@@ -3106,7 +3118,17 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
case "get-keycloak-admin-token":
|
case "get-keycloak-admin-token":
|
||||||
// get access_token for service account of mailcow client
|
// get access_token for service account of mailcow client
|
||||||
if ($iam_settings['authsource'] !== 'keycloak') return false;
|
if ($iam_settings['authsource'] !== 'keycloak') return false;
|
||||||
if (isset($iam_settings['access_token'])) {
|
// only introspect a cached access_token that is not (about to be) expired,
|
||||||
|
// introspecting an expired token makes Keycloak log INTROSPECT_TOKEN_ERROR
|
||||||
|
$token_not_expired = false;
|
||||||
|
if (!empty($iam_settings['access_token'])) {
|
||||||
|
$token_parts = explode('.', $iam_settings['access_token']);
|
||||||
|
if (count($token_parts) === 3) {
|
||||||
|
$token_payload = json_decode(base64_decode(strtr($token_parts[1], '-_', '+/')), true);
|
||||||
|
$token_not_expired = is_array($token_payload) && isset($token_payload['exp']) && intval($token_payload['exp']) > time() + 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($token_not_expired) {
|
||||||
// check if access_token is valid
|
// check if access_token is valid
|
||||||
$url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect";
|
$url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect";
|
||||||
$req = http_build_query(array(
|
$req = http_build_query(array(
|
||||||
@@ -3157,6 +3179,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
':key' => 'access_token',
|
':key' => 'access_token',
|
||||||
':value' => $res['access_token']
|
':value' => $res['access_token']
|
||||||
));
|
));
|
||||||
|
// keep the cached token in sync, so subsequent calls in the same run reuse it
|
||||||
|
$iam_settings['access_token'] = $res['access_token'];
|
||||||
return $res['access_token'];
|
return $res['access_token'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2524,6 +2524,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':tag_name' => $tag,
|
':tag_name' => $tag,
|
||||||
));
|
));
|
||||||
|
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':domain' => $domain,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -2692,6 +2696,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':tag_name' => $tag,
|
':tag_name' => $tag,
|
||||||
));
|
));
|
||||||
|
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':domain' => $domain,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@@ -3170,6 +3178,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':username' => $username,
|
':username' => $username,
|
||||||
':tag_name' => $tag,
|
':tag_name' => $tag,
|
||||||
));
|
));
|
||||||
|
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':username' => $username,
|
||||||
|
));
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3988,6 +4000,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
|
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
while ($row = array_shift($rows)) {
|
while ($row = array_shift($rows)) {
|
||||||
|
if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Keep old array for backward compatibility
|
// Keep old array for backward compatibility
|
||||||
$data['fixed_sender_aliases'][] = $row['address'];
|
$data['fixed_sender_aliases'][] = $row['address'];
|
||||||
// Split into allowed/blocked for proper display
|
// Split into allowed/blocked for proper display
|
||||||
@@ -4923,6 +4938,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
else if ($SaslLogs['service'] == 'SSO') {
|
else if ($SaslLogs['service'] == 'SSO') {
|
||||||
$last_sso_login = strtotime($SaslLogs['datetime']);
|
$last_sso_login = strtotime($SaslLogs['datetime']);
|
||||||
}
|
}
|
||||||
|
else if ($SaslLogs['service'] == 'SOGO') {
|
||||||
|
$last_sogo_login = strtotime($SaslLogs['datetime']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
||||||
$last_imap_login = 0;
|
$last_imap_login = 0;
|
||||||
@@ -4936,10 +4954,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
||||||
$last_sso_login = 0;
|
$last_sso_login = 0;
|
||||||
}
|
}
|
||||||
|
if (!isset($last_sogo_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
||||||
|
$last_sogo_login = 0;
|
||||||
|
}
|
||||||
$mailboxdata['last_imap_login'] = $last_imap_login;
|
$mailboxdata['last_imap_login'] = $last_imap_login;
|
||||||
$mailboxdata['last_smtp_login'] = $last_smtp_login;
|
$mailboxdata['last_smtp_login'] = $last_smtp_login;
|
||||||
$mailboxdata['last_pop3_login'] = $last_pop3_login;
|
$mailboxdata['last_pop3_login'] = $last_pop3_login;
|
||||||
$mailboxdata['last_sso_login'] = $last_sso_login;
|
$mailboxdata['last_sso_login'] = $last_sso_login;
|
||||||
|
$mailboxdata['last_sogo_login'] = $last_sogo_login;
|
||||||
|
|
||||||
if (!isset($_extra) || $_extra != 'reduced') {
|
if (!isset($_extra) || $_extra != 'reduced') {
|
||||||
$rl = ratelimit('get', 'mailbox', $_data);
|
$rl = ratelimit('get', 'mailbox', $_data);
|
||||||
@@ -4977,7 +4999,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(':username' => $_data));
|
$stmt->execute(array(':username' => $_data));
|
||||||
$sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
foreach ($sender_acl_rows as $sender_acl_row) {
|
foreach ($sender_acl_rows as $sender_acl_row) {
|
||||||
$mailboxdata['sender_acl'][] = $sender_acl_row['send_as'];
|
$send_as = $sender_acl_row['send_as'];
|
||||||
|
if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $send_as != '*') {
|
||||||
|
$in_scope = (substr($send_as, 0, 1) == '@')
|
||||||
|
? hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr($send_as, 1))
|
||||||
|
: hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $send_as);
|
||||||
|
if (!$in_scope) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$mailboxdata['sender_acl'][] = $send_as;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("SELECT `tag_name`
|
$stmt = $pdo->prepare("SELECT `tag_name`
|
||||||
@@ -5916,6 +5947,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':tag_name' => $tag,
|
':tag_name' => $tag,
|
||||||
));
|
));
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':domain' => $domain,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5968,6 +6005,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':username' => $username,
|
':username' => $username,
|
||||||
':tag_name' => $tag,
|
':tag_name' => $tag,
|
||||||
));
|
));
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':username' => $username,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -614,7 +614,7 @@ jQuery(function($){
|
|||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'RL',
|
title: lang_edit.ratelimit,
|
||||||
data: 'rl',
|
data: 'rl',
|
||||||
searchable: false,
|
searchable: false,
|
||||||
orderable: false,
|
orderable: false,
|
||||||
@@ -715,10 +715,6 @@ jQuery(function($){
|
|||||||
$.each(json, function (i, item) {
|
$.each(json, function (i, item) {
|
||||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
|
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
|
||||||
|
|
||||||
item.attributes.def_quota_for_mbox = humanFileSize(item.attributes.def_quota_for_mbox);
|
|
||||||
item.attributes.max_quota_for_mbox = humanFileSize(item.attributes.max_quota_for_mbox);
|
|
||||||
item.attributes.max_quota_for_domain = humanFileSize(item.attributes.max_quota_for_domain);
|
|
||||||
|
|
||||||
item.template = escapeHtml(item.template);
|
item.template = escapeHtml(item.template);
|
||||||
if (item.attributes.rl_frame === "s"){
|
if (item.attributes.rl_frame === "s"){
|
||||||
item.attributes.rl_frame = lang_rl.second;
|
item.attributes.rl_frame = lang_rl.second;
|
||||||
@@ -801,16 +797,25 @@ jQuery(function($){
|
|||||||
title: lang.mailbox_defquota,
|
title: lang.mailbox_defquota,
|
||||||
data: 'attributes.def_quota_for_mbox',
|
data: 'attributes.def_quota_for_mbox',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
|
render: function (data, type) {
|
||||||
|
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.max_quota,
|
title: lang.max_quota,
|
||||||
data: 'attributes.max_quota_for_mbox',
|
data: 'attributes.max_quota_for_mbox',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
|
render: function (data, type) {
|
||||||
|
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.domain_quota_total,
|
title: lang.domain_quota_total,
|
||||||
data: 'attributes.max_quota_for_domain',
|
data: 'attributes.max_quota_for_domain',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
|
render: function (data, type) {
|
||||||
|
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.gal,
|
title: lang.gal,
|
||||||
@@ -932,6 +937,7 @@ jQuery(function($){
|
|||||||
item.last_mail_login = (item.attributes.imap_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">IMAP @ ' + unix_time_format(Number(item.last_imap_login)) + '</div><br>' : '') +
|
item.last_mail_login = (item.attributes.imap_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">IMAP @ ' + unix_time_format(Number(item.last_imap_login)) + '</div><br>' : '') +
|
||||||
(item.attributes.pop3_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">POP3 @ ' + unix_time_format(Number(item.last_pop3_login)) + '</div><br>' : '') +
|
(item.attributes.pop3_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">POP3 @ ' + unix_time_format(Number(item.last_pop3_login)) + '</div><br>' : '') +
|
||||||
(item.attributes.smtp_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SMTP @ ' + unix_time_format(Number(item.last_smtp_login)) + '</div><br>' : '') +
|
(item.attributes.smtp_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SMTP @ ' + unix_time_format(Number(item.last_smtp_login)) + '</div><br>' : '') +
|
||||||
|
'<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SOGO @ ' + unix_time_format(Number(item.last_sogo_login)) + '</div><br>' +
|
||||||
'<div class="text-start badge bg-info" style="min-width: 70px;">SSO @ ' + unix_time_format(Number(item.last_sso_login)) + '</div>';
|
'<div class="text-start badge bg-info" style="min-width: 70px;">SSO @ ' + unix_time_format(Number(item.last_sso_login)) + '</div>';
|
||||||
/*
|
/*
|
||||||
if (!item.rl) {
|
if (!item.rl) {
|
||||||
@@ -1244,8 +1250,6 @@ jQuery(function($){
|
|||||||
item.attributes.rl_value = (!item.attributes.rl_value) ? "∞" : escapeHtml(item.attributes.rl_value);
|
item.attributes.rl_value = (!item.attributes.rl_value) ? "∞" : escapeHtml(item.attributes.rl_value);
|
||||||
item.attributes.ratelimit = item.attributes.rl_value + " " + item.attributes.rl_frame;
|
item.attributes.ratelimit = item.attributes.rl_value + " " + item.attributes.rl_frame;
|
||||||
|
|
||||||
item.attributes.quota = humanFileSize(item.attributes.quota);
|
|
||||||
|
|
||||||
item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></i>';
|
item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></i>';
|
||||||
item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></i>';
|
item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></i>';
|
||||||
item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></i>';
|
item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></i>';
|
||||||
@@ -1332,6 +1336,9 @@ jQuery(function($){
|
|||||||
title: lang.domain_quota,
|
title: lang.domain_quota,
|
||||||
data: 'attributes.quota',
|
data: 'attributes.quota',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
|
render: function (data, type) {
|
||||||
|
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.tls_enforce_in,
|
title: lang.tls_enforce_in,
|
||||||
|
|||||||
@@ -1087,14 +1087,18 @@ if (isset($_GET['query'])) {
|
|||||||
$data['mailboxes'][$mailbox][] = $mailbox;
|
$data['mailboxes'][$mailbox][] = $mailbox;
|
||||||
$user_alias_details = user_get_alias_details($mailbox);
|
$user_alias_details = user_get_alias_details($mailbox);
|
||||||
foreach ($user_alias_details['direct_aliases'] as $k => $v) {
|
foreach ($user_alias_details['direct_aliases'] as $k => $v) {
|
||||||
|
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr(strrchr($k, '@'), 1))) {
|
||||||
$data['mailboxes'][$mailbox][] = $k;
|
$data['mailboxes'][$mailbox][] = $k;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
foreach ($user_alias_details['shared_aliases'] as $k => $v) {
|
foreach ($user_alias_details['shared_aliases'] as $k => $v) {
|
||||||
|
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr(strrchr($k, '@'), 1))) {
|
||||||
$data['mailboxes'][$mailbox][] = $k;
|
$data['mailboxes'][$mailbox][] = $k;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!empty($alias_domains)) {
|
if (!empty($alias_domains)) {
|
||||||
foreach ($alias_domains as $alias_domain) {
|
foreach ($alias_domains as $alias_domain) {
|
||||||
$data['alias_domains'][] = $alias_domain;
|
$data['alias_domains'][] = $alias_domain;
|
||||||
|
|||||||
@@ -1127,7 +1127,7 @@
|
|||||||
"mailbox_defaults_info": "Define default settings for new mailboxes.",
|
"mailbox_defaults_info": "Define default settings for new mailboxes.",
|
||||||
"mailbox_defquota": "Default mailbox size",
|
"mailbox_defquota": "Default mailbox size",
|
||||||
"mailbox_templates": "Mailbox Templates",
|
"mailbox_templates": "Mailbox Templates",
|
||||||
"mailbox_quota": "Max. size of a mailbox",
|
"mailbox_quota": "Max. mailbox size",
|
||||||
"mailboxes": "Mailboxes",
|
"mailboxes": "Mailboxes",
|
||||||
"max_aliases": "Max. aliases",
|
"max_aliases": "Max. aliases",
|
||||||
"max_mailboxes": "Max. possible mailboxes",
|
"max_mailboxes": "Max. possible mailboxes",
|
||||||
@@ -1429,6 +1429,7 @@
|
|||||||
"u2f_deprecated": "It seems that your Key was registered using the deprecated U2F method. We will deactivate Two-Factor-Authenticaiton for you and delete your Key.",
|
"u2f_deprecated": "It seems that your Key was registered using the deprecated U2F method. We will deactivate Two-Factor-Authenticaiton for you and delete your Key.",
|
||||||
"u2f_deprecated_important": "Please register your Key in the admin panel with the new WebAuthn method.",
|
"u2f_deprecated_important": "Please register your Key in the admin panel with the new WebAuthn method.",
|
||||||
"webauthn": "WebAuthn authentication",
|
"webauthn": "WebAuthn authentication",
|
||||||
|
"webauthn_registration_failed": "WebAuthn registration failed. The credential could not be verified, please try again.",
|
||||||
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.",
|
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.",
|
||||||
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
|
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
|
||||||
"yubi_otp": "Yubico OTP authentication"
|
"yubi_otp": "Yubico OTP authentication"
|
||||||
|
|||||||
@@ -1074,7 +1074,6 @@
|
|||||||
"verified_webauthn_login": "Inicio de sesión WebAuthn verificado",
|
"verified_webauthn_login": "Inicio de sesión WebAuthn verificado",
|
||||||
"verified_yotp_login": "Inicio de sesión Yubico OTP verificado",
|
"verified_yotp_login": "Inicio de sesión Yubico OTP verificado",
|
||||||
"admin_added": "Se agregó el administrador %s",
|
"admin_added": "Se agregó el administrador %s",
|
||||||
"admin_api_modified": "Se guardaron los cambios en la API",
|
|
||||||
"admin_removed": "Se eliminó el administrador %s",
|
"admin_removed": "Se eliminó el administrador %s",
|
||||||
"app_links": "Se guardaron los cambios en los enlaces de aplicaciones",
|
"app_links": "Se guardaron los cambios en los enlaces de aplicaciones",
|
||||||
"app_passwd_added": "Se agregó una nueva contraseña de aplicación",
|
"app_passwd_added": "Se agregó una nueva contraseña de aplicación",
|
||||||
@@ -1333,10 +1332,6 @@
|
|||||||
"sogo_profile_reset_help": "Esto destruirá el perfil de SOGo del usuario y <b>eliminará de forma irrecuperable todos sus contactos y datos de calendario</b>.",
|
"sogo_profile_reset_help": "Esto destruirá el perfil de SOGo del usuario y <b>eliminará de forma irrecuperable todos sus contactos y datos de calendario</b>.",
|
||||||
"spam_score_reset": "Restablecer al valor predeterminado del servidor",
|
"spam_score_reset": "Restablecer al valor predeterminado del servidor",
|
||||||
"spamfilter_table_domain_policy": "n/d (política del dominio)",
|
"spamfilter_table_domain_policy": "n/d (política del dominio)",
|
||||||
"syncjob_check_log": "Revisar registro",
|
|
||||||
"syncjob_EX_OK": "Correcto",
|
|
||||||
"syncjob_EXIT_OVERQUOTA": "El buzón de destino superó su cuota",
|
|
||||||
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Usuario o contraseña incorrectos",
|
|
||||||
"tfa_info": "La autenticación de dos factores ayuda a proteger su cuenta. Si la activa, necesitará contraseñas de aplicación para iniciar sesión en aplicaciones o servicios que no admiten la autenticación de dos factores (p. ej., clientes de correo)."
|
"tfa_info": "La autenticación de dos factores ayuda a proteger su cuenta. Si la activa, necesitará contraseñas de aplicación para iniciar sesión en aplicaciones o servicios que no admiten la autenticación de dos factores (p. ej., clientes de correo)."
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@@ -95,7 +95,6 @@
|
|||||||
"bcc_dest_format": "Il destinatario in copia nascosta deve essere un singolo indirizzo email.<br>Se si vuole spedire una copia del messaggio a più destinatari, bisogna creare un alias ed utilizzarlo per questa opzione.",
|
"bcc_dest_format": "Il destinatario in copia nascosta deve essere un singolo indirizzo email.<br>Se si vuole spedire una copia del messaggio a più destinatari, bisogna creare un alias ed utilizzarlo per questa opzione.",
|
||||||
"app_passwd_protocols": "Protocolli consentiti per la password dell'app",
|
"app_passwd_protocols": "Protocolli consentiti per la password dell'app",
|
||||||
"tags": "Tag",
|
"tags": "Tag",
|
||||||
"dry": "Simula sincronizzazione",
|
|
||||||
"internal": "Interno",
|
"internal": "Interno",
|
||||||
"internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.",
|
"internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.",
|
||||||
"sender_allowed": "Consenti l'invio con questo alias"
|
"sender_allowed": "Consenti l'invio con questo alias"
|
||||||
|
|||||||
@@ -416,18 +416,24 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
|||||||
return navigator.credentials.create(json);
|
return navigator.credentials.create(json);
|
||||||
}).then(function(cred) {
|
}).then(function(cred) {
|
||||||
return {
|
return {
|
||||||
id: cred.id,
|
clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON),
|
||||||
rawId: arrayBufferToBase64(cred.rawId),
|
|
||||||
response: {
|
|
||||||
attestationObject: arrayBufferToBase64(cred.response.attestationObject),
|
attestationObject: arrayBufferToBase64(cred.response.attestationObject),
|
||||||
clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON)
|
key_id: keyId,
|
||||||
},
|
tfa_method: 'webauthn',
|
||||||
type: cred.type
|
confirm_password: ''
|
||||||
};
|
};
|
||||||
}).then(function(credData) {
|
}).then(JSON.stringify).then(function(credentialData) {
|
||||||
$('#setup_webauthn_register_data').val(JSON.stringify(credData));
|
// send the attestation to the JSON API, like the regular WebAuthn modal does,
|
||||||
$('#setup_webauthn_reg_form input[name=set_tfa]').val('1');
|
// so that the credential is parsed and stored correctly
|
||||||
$('#setup_webauthn_reg_form').submit();
|
return window.fetch('/api/v1/add/webauthn-tfa-registration', {method: 'POST', body: credentialData, cache: 'no-cache'});
|
||||||
|
}).then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
}).then(function(json) {
|
||||||
|
if (json.success) {
|
||||||
|
window.location = window.location.href.split('#')[0];
|
||||||
|
} else {
|
||||||
|
throw new Error(json.msg);
|
||||||
|
}
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
$('#setup_webauthn_return_code').show().text(err.message || 'Registration failed');
|
$('#setup_webauthn_return_code').show().text(err.message || 'Registration failed');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -97,9 +97,11 @@
|
|||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select data-live-search="true" data-width="100%" style="width:100%" id="editSelectSenderACL" name="sender_acl" size="10" multiple>
|
<select data-live-search="true" data-width="100%" style="width:100%" id="editSelectSenderACL" name="sender_acl" size="10" multiple>
|
||||||
{% for domain in sender_acl_handles.sender_acl_domains.ro %}
|
{% for domain in sender_acl_handles.sender_acl_domains.ro %}
|
||||||
|
{% if domain == '*' or mailcow_cc_role == 'admin' %}
|
||||||
<option data-subtext="Admin" value="{{ domain }}" disabled selected>
|
<option data-subtext="Admin" value="{{ domain }}" disabled selected>
|
||||||
{{ lang.edit.dont_check_sender_acl|format(domain) }}
|
{{ lang.edit.dont_check_sender_acl|format(domain) }}
|
||||||
</option>
|
</option>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for domain in sender_acl_handles.sender_acl_domains.rw %}
|
{% for domain in sender_acl_handles.sender_acl_domains.rw %}
|
||||||
<option value="{{ domain }}" selected>
|
<option value="{{ domain }}" selected>
|
||||||
|
|||||||
+5
-5
@@ -68,7 +68,7 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
|
|
||||||
clamd-mailcow:
|
clamd-mailcow:
|
||||||
image: ghcr.io/mailcow/clamd:1.4.6-1
|
image: ghcr.io/mailcow/clamd:1.4.6-2
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
@@ -348,7 +348,7 @@ services:
|
|||||||
- dovecot
|
- dovecot
|
||||||
|
|
||||||
postfix-mailcow:
|
postfix-mailcow:
|
||||||
image: ghcr.io/mailcow/postfix:3.10.12-1
|
image: ghcr.io/mailcow/postfix:3.10.13-1
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql-mailcow:
|
mysql-mailcow:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
@@ -391,7 +391,7 @@ services:
|
|||||||
- postfix
|
- postfix
|
||||||
|
|
||||||
postfix-tlspol-mailcow:
|
postfix-tlspol-mailcow:
|
||||||
image: ghcr.io/mailcow/postfix-tlspol:1.14.0
|
image: ghcr.io/mailcow/postfix-tlspol:1.14.5
|
||||||
depends_on:
|
depends_on:
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -428,7 +428,7 @@ services:
|
|||||||
- php-fpm-mailcow
|
- php-fpm-mailcow
|
||||||
- sogo-mailcow
|
- sogo-mailcow
|
||||||
- rspamd-mailcow
|
- rspamd-mailcow
|
||||||
image: ghcr.io/mailcow/nginx:1.30.6-2
|
image: ghcr.io/mailcow/nginx:1.30.6-3
|
||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
environment:
|
environment:
|
||||||
@@ -612,7 +612,7 @@ services:
|
|||||||
- watchdog
|
- watchdog
|
||||||
|
|
||||||
dockerapi-mailcow:
|
dockerapi-mailcow:
|
||||||
image: ghcr.io/mailcow/dockerapi:2.15
|
image: ghcr.io/mailcow/dockerapi:2.16
|
||||||
security_opt:
|
security_opt:
|
||||||
- label=disable
|
- label=disable
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Admin;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes admin: administrator accounts */
|
||||||
|
class AdminRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const DETAILS = ['username' => 'admin', 'tfa_active' => 0, 'tfa_active_int' => 0, 'active' => 1, 'active_int' => 1,
|
||||||
|
'created' => '2026-09-21 10:23:08', 'attributes' => ['force_tfa' => '0', 'force_pw_update' => '0']];
|
||||||
|
|
||||||
|
public function testGetAllCollectsTheDetailsOfEveryAdmin(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/admin/all', ['script' => [
|
||||||
|
self::answer('admin', ['get'], ['admin', 'gone']),
|
||||||
|
self::answer('admin', ['details', 'admin'], self::DETAILS),
|
||||||
|
self::answer('admin', ['details', 'gone'], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([self::DETAILS], $result->json());
|
||||||
|
$this->assertSame([['get'], ['details', 'admin'], ['details', 'gone']], $result->callsTo('admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllWithoutAdmins(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/admin/all', ['script' => [self::answer('admin', ['get'], [])]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
$this->assertSame([['get']], $result->callsTo('admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOne(): void
|
||||||
|
{
|
||||||
|
$found = $this->get('get/admin/admin', ['script' => [self::answer('admin', ['details', 'admin'], self::DETAILS)]]);
|
||||||
|
$this->assertSame(self::DETAILS, $found->json());
|
||||||
|
|
||||||
|
$missing = $this->get('get/admin/nobody', ['script' => [self::answer('admin', ['details'], false)]]);
|
||||||
|
$this->assertSame('{}', $missing->body);
|
||||||
|
$this->assertCalled($missing, 'admin', ['details', 'nobody']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdd(): void
|
||||||
|
{
|
||||||
|
$attr = ['username' => 'admin2', 'password' => 'Rand0m-Passw0rd!', 'password2' => 'Rand0m-Passw0rd!', 'active' => 1];
|
||||||
|
$messages = [self::message('success', ['admin_added', 'admin2'], ['admin', 'add', ['username' => 'admin2', 'password' => '*']])];
|
||||||
|
|
||||||
|
$result = $this->post('add/admin', $attr, ['script' => [self::answer('admin', ['add'], null, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['add', $attr]], $result->callsTo('admin'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEdit(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/admin', ['items' => ['admin2', 'admin3'], 'attr' => ['active' => '0', 'force_tfa' => 1]]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', ['username' => ['admin2', 'admin3'], 'active' => '0', 'force_tfa' => 1]]], $result->callsTo('admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditTakesASingleUsername(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/admin', ['items' => 'admin2', 'attr' => ['password' => 'x', 'password2' => 'x']]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'admin', ['edit', ['username' => ['admin2'], 'password' => 'x', 'password2' => 'x']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete(): void
|
||||||
|
{
|
||||||
|
$messages = [
|
||||||
|
self::message('success', ['admin_removed', 'admin2']),
|
||||||
|
self::message('danger', ['username_invalid', 'nobody']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this->post('delete/admin', ['admin2', 'nobody'], ['script' => [self::answer('admin', ['delete'], null, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', ['username' => ['admin2', 'nobody']]]], $result->callsTo('admin'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Admin;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes api-key: the ApiKey module */
|
||||||
|
class ApiKeyRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const API = 'Mailcow\ApiKey\ApiKeyModule->api->';
|
||||||
|
private const KEY = ['id' => 3, 'description' => 'provisioning', 'access' => 'rw', 'api_key' => '1B7E2C-94F0A3-6D5B18-C2E7F4-03A9D6',
|
||||||
|
'allow_from' => ['192.0.2.10'], 'skip_ip_check' => 0, 'service' => 0, 'active' => 1, 'created' => '2026-11-02 09:12:44', 'modified' => null];
|
||||||
|
|
||||||
|
public function testGetAll(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/api-key/all', ['script' => [self::answer(self::API . 'getKeys', [], [self::KEY])]]);
|
||||||
|
|
||||||
|
$this->assertSame([self::KEY], $result->json());
|
||||||
|
$this->assertSame([[]], $result->callsTo(self::API . 'getKeys'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllAnswersAnEmptyObjectWithoutKeys(): void
|
||||||
|
{
|
||||||
|
// also the answer for read-only API keys: the module returns no keys for them
|
||||||
|
$result = $this->get('get/api-key/all', ['session' => self::API_RO, 'script' => [self::answer(self::API . 'getKeys', [], [])]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOne(): void
|
||||||
|
{
|
||||||
|
$found = $this->get('get/api-key/3', ['script' => [self::answer(self::API . 'getKey', ['3'], self::KEY)]]);
|
||||||
|
$this->assertSame(self::KEY, $found->json());
|
||||||
|
|
||||||
|
$missing = $this->get('get/api-key/99', ['script' => [self::answer(self::API . 'getKey', [], [])]]);
|
||||||
|
$this->assertSame('{}', $missing->body);
|
||||||
|
$this->assertSame([['99']], $missing->callsTo(self::API . 'getKey'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdd(): void
|
||||||
|
{
|
||||||
|
$attr = ['description' => 'provisioning', 'access' => 'rw', 'allow_from' => '192.0.2.10, 2001:db8::/64', 'skip_ip_check' => 0, 'active' => 1];
|
||||||
|
$messages = [self::message('success', ['api_key_added', 'provisioning'], ['api', 'add', 'key', $attr])];
|
||||||
|
|
||||||
|
$result = $this->post('add/api-key', $attr, ['script' => [self::answer(self::API . 'addKey', [], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertSame([[$attr]], $result->callsTo(self::API . 'addKey'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEdit(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/api-key', ['items' => [3, 4], 'attr' => ['regen_key' => 1]]);
|
||||||
|
|
||||||
|
$this->assertSame([[[3, 4], ['regen_key' => 1]]], $result->callsTo(self::API . 'editKeys'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditTakesASingleId(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/api-key', ['items' => 3, 'attr' => ['active' => 0]]);
|
||||||
|
|
||||||
|
$this->assertSame([[[3], ['active' => 0]]], $result->callsTo(self::API . 'editKeys'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete(): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('danger', ['api_key_service_locked', 'API_KEY'])];
|
||||||
|
|
||||||
|
$result = $this->post('delete/api-key', [1], ['script' => [self::answer(self::API . 'deleteKeys', [], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertSame([[[1]]], $result->callsTo(self::API . 'deleteKeys'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Admin;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes domain-admin, da-acl and sso/domain-admin */
|
||||||
|
class DomainAdminRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const DETAILS = ['username' => 'domainadmin1', 'tfa_active' => 0, 'tfa_active_int' => 0, 'active' => 1, 'active_int' => 1,
|
||||||
|
'created' => '2026-09-21 10:24:48', 'attributes' => ['force_tfa' => '0', 'force_pw_update' => '0'],
|
||||||
|
'selected_domains' => ['example.org'], 'unselected_domains' => ['example.com']];
|
||||||
|
|
||||||
|
public function testGetAllCollectsTheDetailsOfEveryDomainAdmin(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain-admin/all', ['script' => [
|
||||||
|
self::answer('domain_admin', ['get'], ['domainadmin1', 'gone']),
|
||||||
|
self::answer('domain_admin', ['details', 'domainadmin1'], self::DETAILS),
|
||||||
|
self::answer('domain_admin', ['details', 'gone'], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([self::DETAILS], $result->json());
|
||||||
|
$this->assertSame([['get'], ['details', 'domainadmin1'], ['details', 'gone']], $result->callsTo('domain_admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllWithoutDomainAdmins(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain-admin/all', ['script' => [self::answer('domain_admin', ['get'], [])]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOne(): void
|
||||||
|
{
|
||||||
|
$found = $this->get('get/domain-admin/domainadmin1', ['script' => [self::answer('domain_admin', ['details', 'domainadmin1'], self::DETAILS)]]);
|
||||||
|
$this->assertSame(self::DETAILS, $found->json());
|
||||||
|
|
||||||
|
$missing = $this->get('get/domain-admin/nobody', ['script' => [self::answer('domain_admin', ['details'], false)]]);
|
||||||
|
$this->assertSame('{}', $missing->body);
|
||||||
|
$this->assertSame([['details', 'nobody']], $missing->callsTo('domain_admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdd(): void
|
||||||
|
{
|
||||||
|
$attr = ['username' => 'domainadmin1', 'password' => 'Rand0m-Passw0rd!', 'password2' => 'Rand0m-Passw0rd!',
|
||||||
|
'domains' => ['example.org', 'example.com'], 'active' => 1];
|
||||||
|
|
||||||
|
$result = $this->post('add/domain-admin', $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['add', $attr]], $result->callsTo('domain_admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEdit(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/domain-admin', ['items' => ['domainadmin1'], 'attr' => ['domains' => ['example.org'], 'disable_tfa' => 1]]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', ['username' => ['domainadmin1'], 'domains' => ['example.org'], 'disable_tfa' => 1]]], $result->callsTo('domain_admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/domain-admin', ['domainadmin1', 'domainadmin2']);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', ['username' => ['domainadmin1', 'domainadmin2']]]], $result->callsTo('domain_admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditAcl(): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('success', ['acl_saved', 'domainadmin1'])];
|
||||||
|
|
||||||
|
$result = $this->post('edit/da-acl', ['items' => 'domainadmin1', 'attr' => ['da_acl' => ['syncjobs', 'quarantine']]],
|
||||||
|
['script' => [self::answer('acl', ['edit', 'domainadmin'], null, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', 'domainadmin', ['username' => ['domainadmin1'], 'da_acl' => ['syncjobs', 'quarantine']]]], $result->callsTo('acl'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSsoTokenIsTheAnswer(): void
|
||||||
|
{
|
||||||
|
$token = ['token' => '591F6D-5C3DD2-7455CD-DAF1C1-AA4FCC'];
|
||||||
|
|
||||||
|
$result = $this->post('add/sso/domain-admin', ['username' => 'domainadmin1'],
|
||||||
|
['script' => [self::answer('domain_admin_sso', ['issue'], $token)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['issue', ['username' => 'domainadmin1']]], $result->callsTo('domain_admin_sso'));
|
||||||
|
$this->assertSame($token, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSsoTokenFailureAnswersTheMessages(): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('danger', ['object_doesnt_exist', 'nobody'])];
|
||||||
|
|
||||||
|
$failed = $this->post('add/sso/domain-admin', ['username' => 'nobody'],
|
||||||
|
['script' => [self::answer('domain_admin_sso', ['issue'], false, $messages)]]);
|
||||||
|
$this->assertSame($messages, $failed->json());
|
||||||
|
|
||||||
|
$silent = $this->post('add/sso/domain-admin', ['username' => 'nobody'],
|
||||||
|
['script' => [self::answer('domain_admin_sso', ['issue'], false)]]);
|
||||||
|
$this->assertSame(['type' => 'error', 'msg' => 'Cannot add item'], $silent->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSsoForOtherObjectsAnswersNothing(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('add/sso/mailbox', ['username' => 'user@example.org']);
|
||||||
|
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body]);
|
||||||
|
$this->assertNotCalled($result, 'domain_admin_sso');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Admin;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
|
||||||
|
/** Routes logs/<type>[/<count>] */
|
||||||
|
class LogRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
/** @return array<string, array{0: string, 1: string}> route type => log name of get_logs() */
|
||||||
|
public static function logs(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'dovecot' => ['dovecot', 'dovecot-mailcow'],
|
||||||
|
'ratelimited' => ['ratelimited', 'ratelimited'],
|
||||||
|
'netfilter' => ['netfilter', 'netfilter-mailcow'],
|
||||||
|
'cron' => ['cron', 'cron-mailcow'],
|
||||||
|
'postfix' => ['postfix', 'postfix-mailcow'],
|
||||||
|
'autodiscover' => ['autodiscover', 'autodiscover-mailcow'],
|
||||||
|
'sogo' => ['sogo', 'sogo-mailcow'],
|
||||||
|
'ui' => ['ui', 'mailcow-ui'],
|
||||||
|
'sasl' => ['sasl', 'sasl'],
|
||||||
|
'watchdog' => ['watchdog', 'watchdog-mailcow'],
|
||||||
|
'acme' => ['acme', 'acme-mailcow'],
|
||||||
|
'api' => ['api', 'api-mailcow'],
|
||||||
|
'rspamd-history' => ['rspamd-history', 'rspamd-history'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('logs')]
|
||||||
|
public function testWithoutCount(string $type, string $log): void
|
||||||
|
{
|
||||||
|
$entries = [['time' => '1794308452', 'message' => 'first'], ['time' => '1794308451', 'message' => 'second']];
|
||||||
|
|
||||||
|
$result = $this->get("get/logs/$type", ['script' => [self::answer('get_logs', [$log], $entries)]]);
|
||||||
|
|
||||||
|
$this->assertSame([[$log]], $result->callsTo('get_logs'));
|
||||||
|
$this->assertSame($entries, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('logs')]
|
||||||
|
public function testCountOrRange(string $type, string $log): void
|
||||||
|
{
|
||||||
|
$this->assertSame([[$log, '50']], $this->get("get/logs/$type/50")->callsTo('get_logs'));
|
||||||
|
$this->assertSame([[$log, '2-3']], $this->get("get/logs/$type/2-3")->callsTo('get_logs'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('logs')]
|
||||||
|
public function testEmptyLogs(string $type, string $log): void
|
||||||
|
{
|
||||||
|
$this->assertSame('{}', $this->get("get/logs/$type/5", ['script' => [self::answer('get_logs', [$log], false)]])->body);
|
||||||
|
$this->assertSame('{}', $this->get("get/logs/$type", ['script' => [self::answer('get_logs', [$log], [])]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCountKeepsOnlyDigitsAndDashes(): void
|
||||||
|
{
|
||||||
|
$this->assertSame([['postfix-mailcow', '12-30']], $this->get('get/logs/postfix/a12-3x0')->callsTo('get_logs'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTrailingSlashPassesAnEmptyCount(): void
|
||||||
|
{
|
||||||
|
$this->assertSame([['postfix-mailcow', '']], $this->get('get/logs/postfix/')->callsTo('get_logs'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRspamdStatsIgnoresTheCount(): void
|
||||||
|
{
|
||||||
|
$stats = ['version' => '3.12.1', 'scanned' => 300, 'actions' => ['reject' => 38]];
|
||||||
|
|
||||||
|
$result = $this->get('get/logs/rspamd-stats/10', ['script' => [self::answer('get_logs', ['rspamd-stats'], $stats)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['rspamd-stats']], $result->callsTo('get_logs'));
|
||||||
|
$this->assertSame($stats, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownLog(): void
|
||||||
|
{
|
||||||
|
$this->assertApiError($this->get('get/logs/clamd'), 404, 'route not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Admin;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes oauth2-client */
|
||||||
|
class OAuth2ClientRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const CLIENT = ['id' => 1, 'client_id' => '17c76aaa88c0', 'client_secret' => '73fc668a88147e32a31ff80c',
|
||||||
|
'redirect_uri' => 'https://app.example.org/oauth/callback', 'grant_types' => null, 'scope' => 'profile', 'user_id' => null];
|
||||||
|
|
||||||
|
public function testGetAllCollectsTheDetailsOfEveryClient(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/oauth2-client/all', ['script' => [
|
||||||
|
self::answer('oauth2', ['get', 'clients'], [1, 2]),
|
||||||
|
self::answer('oauth2', ['details', 'client', 1], self::CLIENT),
|
||||||
|
self::answer('oauth2', ['details', 'client', 2], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([self::CLIENT], $result->json());
|
||||||
|
$this->assertSame([['get', 'clients'], ['details', 'client', 1], ['details', 'client', 2]], $result->callsTo('oauth2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllWithoutClients(): void
|
||||||
|
{
|
||||||
|
$this->assertSame('{}', $this->get('get/oauth2-client/all', ['script' => [self::answer('oauth2', ['get', 'clients'], null)]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOne(): void
|
||||||
|
{
|
||||||
|
$found = $this->get('get/oauth2-client/1', ['script' => [self::answer('oauth2', ['details', 'client', '1'], self::CLIENT)]]);
|
||||||
|
$this->assertSame(self::CLIENT, $found->json());
|
||||||
|
|
||||||
|
$missing = $this->get('get/oauth2-client/9', ['script' => [self::answer('oauth2', ['details'], false)]]);
|
||||||
|
$this->assertSame('{}', $missing->body);
|
||||||
|
$this->assertSame([['details', 'client', '9']], $missing->callsTo('oauth2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdd(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('add/oauth2-client', ['redirect_uri' => 'https://app.example.org/oauth/callback']);
|
||||||
|
|
||||||
|
$this->assertSame([['add', 'client', ['redirect_uri' => 'https://app.example.org/oauth/callback']]], $result->callsTo('oauth2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEdit(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/oauth2-client', ['items' => [1, 2], 'attr' => ['renew_secret' => 1]]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', 'client', ['id' => [1, 2], 'renew_secret' => 1]]], $result->callsTo('oauth2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/oauth2-client', [1, 2]);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', 'client', ['id' => [1, 2]]]], $result->callsTo('oauth2'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Admin;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes self and fido2-fn act on the account of the session; the pending password update of a login */
|
||||||
|
class OwnAccountRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const PASSWORDS = ['user_old_pass' => 'old', 'user_new_pass' => 'N3w-Passw0rd!', 'user_new_pass2' => 'N3w-Passw0rd!'];
|
||||||
|
|
||||||
|
public function testSelfDoesNothingForAnApiKey(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/self', ['attr' => self::PASSWORDS]);
|
||||||
|
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body]);
|
||||||
|
$this->assertNotCalled($result, 'domain_admin');
|
||||||
|
$this->assertNotCalled($result, 'edit_user_account');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSelfOfADomainAdmin(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/self', ['attr' => self::PASSWORDS], ['session' => self::DOMAIN_ADMIN]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', self::PASSWORDS]], $result->callsTo('domain_admin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSelfOfAMailboxUser(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/self', ['attr' => self::PASSWORDS], ['session' => self::USER]);
|
||||||
|
|
||||||
|
$this->assertSame([[self::PASSWORDS]], $result->callsTo('edit_user_account'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFido2FriendlyName(): void
|
||||||
|
{
|
||||||
|
$attr = ['fido2_cid' => hash('sha256', 'credential'), 'fido2_fn' => 'YubiKey'];
|
||||||
|
|
||||||
|
$result = $this->post('edit/fido2-fn', ['attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertSame([[['action' => 'edit_fn', 'fido2_attrs' => $attr]]], $result->callsTo('fido2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPendingPasswordUpdateAllowsOnlyThePasswordRoutes(): void
|
||||||
|
{
|
||||||
|
$session = self::ADMIN + ['pending_pw_update' => true];
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('edit/admin', ['items' => 'admin', 'attr' => ['password' => 'x', 'password2' => 'x']], ['session' => $session]),
|
||||||
|
'admin', ['edit', ['username' => ['admin'], 'password' => 'x', 'password2' => 'x']]);
|
||||||
|
$this->assertCalled($this->get('get/passwordpolicy', ['session' => $session]), 'password_complexity', ['get']);
|
||||||
|
|
||||||
|
$denied = $this->post('edit/domain-admin', ['items' => 'domainadmin1', 'attr' => ['active' => 0]], ['session' => $session]);
|
||||||
|
$this->assertApiError($denied, 403, 'API access denied until the pending account setup is finished');
|
||||||
|
$this->assertNotCalled($denied, 'domain_admin');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Admin;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
|
||||||
|
/** Routes passwordpolicy, reset-password-notification, the UI customization routes and cors */
|
||||||
|
class SettingsRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const POLICY = ['length' => '8', 'chars' => '1', 'special_chars' => '0', 'lowerupper' => '1', 'numbers' => '1'];
|
||||||
|
|
||||||
|
public function testGetPasswordPolicy(): void
|
||||||
|
{
|
||||||
|
foreach (['get/passwordpolicy', 'get/passwordpolicy/all'] as $route) {
|
||||||
|
$result = $this->get($route, ['script' => [self::answer('password_complexity', ['get'], self::POLICY)]]);
|
||||||
|
$this->assertSame([['get']], $result->callsTo('password_complexity'), $route);
|
||||||
|
$this->assertSame(self::POLICY, $result->json(), $route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPasswordPolicyAsHtml(): void
|
||||||
|
{
|
||||||
|
$html = '<p class="help-block small">- Minimum password length is 8</p>';
|
||||||
|
|
||||||
|
$result = $this->get('get/passwordpolicy/html', ['script' => [self::answer('password_complexity', ['html'], $html)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['html']], $result->callsTo('password_complexity'));
|
||||||
|
$this->assertSame($html, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPasswordPolicyFailure(): void
|
||||||
|
{
|
||||||
|
$this->assertSame('{}', $this->get('get/passwordpolicy', ['script' => [self::answer('password_complexity', ['get'], false)]])->body);
|
||||||
|
$this->assertSame('{}', $this->get('get/passwordpolicy/html', ['script' => [self::answer('password_complexity', ['html'], false)]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditPasswordPolicyIgnoresItems(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/passwordpolicy', ['items' => ['x'], 'attr' => ['length' => 12, 'numbers' => 1]]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', ['length' => 12, 'numbers' => 1]]], $result->callsTo('password_complexity'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditPasswordResetNotification(): void
|
||||||
|
{
|
||||||
|
$attr = ['from' => 'noreply@example.org', 'subject' => 'Reset your password', 'text_tmpl' => '{{link}}', 'html_tmpl' => ''];
|
||||||
|
|
||||||
|
$result = $this->post('edit/reset-password-notification', ['attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit_notification', $attr]], $result->callsTo('reset_password'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, array{0: string, 1: array}> */
|
||||||
|
public static function customizations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ui_texts' => ['ui_texts', ['title_name' => 'Example Mail', 'ui_announcement_type' => 'warning', 'ui_announcement_active' => 1]],
|
||||||
|
'ip_check' => ['ip_check', ['ip_check_opt_in' => '1']],
|
||||||
|
'custom_login' => ['custom_login', ['hide_admin_quicklink' => 1, 'force_sso' => 0]],
|
||||||
|
'ui_theme' => ['ui_theme', ['ui_theme' => 'servercow']],
|
||||||
|
'app_links' => ['app_links', ['app' => ['Wiki'], 'href' => ['https://wiki.example.org'], 'user_href' => [''], 'hide' => ['0']]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('customizations')]
|
||||||
|
public function testCustomizationsPassAttrOnly(string $item, array $attr): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('success', $item === 'ip_check' ? 'ip_check_opt_in_modified' : $item)];
|
||||||
|
|
||||||
|
$result = $this->post("edit/$item", ['items' => ['ignored'], 'attr' => $attr], ['script' => [self::answer('customize', ['edit', $item], null, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', $item, $attr]], $result->callsTo('customize'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomizationWithoutAttrPassesNothing(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/app_links', ['items' => []]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', 'app_links', []]], $result->callsTo('customize'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditCors(): void
|
||||||
|
{
|
||||||
|
$attr = ['allowed_origins' => ['https://app.example.org'], 'allowed_methods' => ['GET', 'POST']];
|
||||||
|
|
||||||
|
$result = $this->post('edit/cors', ['attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertSame([['set_headers'], ['edit', $attr]], $result->callsTo('cors'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Admin;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes status/<object> */
|
||||||
|
class StatusRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testContainersAreReducedToStateStartAndImage(): void
|
||||||
|
{
|
||||||
|
$info = [
|
||||||
|
'dovecot-mailcow' => ['State' => ['Status' => 'running', 'StartedAt' => '2026-11-10T11:00:51.541059812Z', 'Pid' => 42],
|
||||||
|
'Config' => ['Image' => 'ghcr.io/mailcow/dovecot:2.3.21.1-4', 'Hostname' => 'dovecot'], 'Id' => '5d0f3cbb2a6e'],
|
||||||
|
'ofelia-mailcow' => ['State' => ['Status' => 'exited', 'StartedAt' => '2026-11-09T08:00:00Z'],
|
||||||
|
'Config' => ['Image' => 'mcuadros/ofelia:latest'], 'Id' => '77aa01'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this->get('get/status/containers', ['script' => [self::answer('docker', ['info'], $info)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['info']], $result->callsTo('docker'));
|
||||||
|
$this->assertSame([
|
||||||
|
'dovecot-mailcow' => ['type' => 'info', 'container' => 'dovecot-mailcow', 'state' => 'running',
|
||||||
|
'started_at' => '2026-11-10T11:00:51.541059812Z', 'image' => 'ghcr.io/mailcow/dovecot:2.3.21.1-4'],
|
||||||
|
'ofelia-mailcow' => ['type' => 'info', 'container' => 'ofelia-mailcow', 'state' => 'exited',
|
||||||
|
'started_at' => '2026-11-09T08:00:00Z', 'image' => 'mcuadros/ofelia:latest'],
|
||||||
|
], $result->json());
|
||||||
|
$this->assertStringContainsString('"ghcr.io/mailcow/dovecot', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testContainersWithoutDockerapi(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/status/containers', ['script' => [self::answer('docker', ['info'], 'Could not resolve host: dockerapi')]]);
|
||||||
|
|
||||||
|
$this->assertSame('null', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testContainerStatsByContainerId(): void
|
||||||
|
{
|
||||||
|
$stats = [['read' => '2026-11-10T19:20:01Z', 'pids_stats' => ['current' => 38]], ['read' => '2026-11-10T19:20:06Z', 'pids_stats' => ['current' => 39]]];
|
||||||
|
|
||||||
|
$result = $this->get('get/status/container/5d0f3cbb2a6e', ['script' => [self::answer('docker', ['container_stats'], $stats)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['container_stats', '5d0f3cbb2a6e']], $result->callsTo('docker'));
|
||||||
|
$this->assertSame($stats, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testContainerStatsWithoutId(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/status/container', ['script' => [self::answer('docker', ['container_stats'], false)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['container_stats', null]], $result->callsTo('docker'));
|
||||||
|
$this->assertSame('false', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVmailSplitsTheDfLine(): void
|
||||||
|
{
|
||||||
|
$df = json_encode('/dev/sda1,105G,65G,40G,66%,/var/vmail');
|
||||||
|
|
||||||
|
$result = $this->get('get/status/vmail', ['script' => [self::answer('docker', ['post', 'dovecot-mailcow', 'exec'], $df)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['post', 'dovecot-mailcow', 'exec', ['cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail']]], $result->callsTo('docker'));
|
||||||
|
$this->assertSame(['type' => 'info', 'disk' => '/dev/sda1', 'used' => '65G', 'total' => '105G', 'used_percent' => '66%'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHost(): void
|
||||||
|
{
|
||||||
|
$stats = ['cpu' => ['cores' => 4, 'usage' => 6], 'memory' => ['total' => 12541669376, 'usage' => 62.5], 'uptime' => 703800.5,
|
||||||
|
'system_time' => '10.11.2026 19:13:07', 'architecture' => 'x86_64'];
|
||||||
|
|
||||||
|
$result = $this->get('get/status/host', ['script' => [self::answer('docker', ['host_stats'], $stats)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['host_stats']], $result->callsTo('docker'));
|
||||||
|
$this->assertSame($stats, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPublicAddressesAskMailcowEmail(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/status/host/ip', ['script' => [
|
||||||
|
self::answer('curl_exec', [], '198.51.100.7', once: true),
|
||||||
|
self::answer('curl_exec', [], '2001:db8::7', once: true),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([[null, CURLOPT_URL, 'http://ipv4.mailcow.email'], [null, CURLOPT_URL, 'http://ipv6.mailcow.email']],
|
||||||
|
array_values(array_filter($result->callsTo('curl_setopt'), fn(array $args): bool => $args[1] === CURLOPT_URL)));
|
||||||
|
$this->assertSame(['ipv4' => '198.51.100.7', 'ipv6' => '2001:db8::7'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHostWithAnotherSegmentAnswersNothing(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/status/host/cpu');
|
||||||
|
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body]);
|
||||||
|
$this->assertNotCalled($result, 'docker');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVersion(): void
|
||||||
|
{
|
||||||
|
// $MAILCOW_GIT_VERSION comes from app_info.inc.php, which the harness does not load
|
||||||
|
$this->assertSame(['version' => null], $this->get('get/status/version')->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownStatusAnswersNothing(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/status/disks');
|
||||||
|
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnlyForAdmins(): void
|
||||||
|
{
|
||||||
|
foreach (['containers', 'vmail', 'host', 'version'] as $object) {
|
||||||
|
$result = $this->get("get/status/$object", ['session' => self::DOMAIN_ADMIN]);
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body], $object);
|
||||||
|
$this->assertNotCalled($result, 'docker');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api;
|
||||||
|
|
||||||
|
/** What json_api.php did for one request of ApiTestCase: status, output and the calls it made */
|
||||||
|
final class ApiResult
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param list<array{0: string, 1: array}> $calls function or method name and arguments, in call order
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public readonly int $status,
|
||||||
|
public readonly string $body,
|
||||||
|
public readonly array $calls,
|
||||||
|
public readonly ?array $sessionReturn,
|
||||||
|
public readonly ?string $error
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The decoded output, null if it is no JSON */
|
||||||
|
public function json(): mixed
|
||||||
|
{
|
||||||
|
return json_decode($this->body, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arguments of every call of $name, e.g. 'mailbox' or 'Mailcow\ApiKey\ApiKeyModule->api->addKey'
|
||||||
|
*
|
||||||
|
* @return list<array>
|
||||||
|
*/
|
||||||
|
public function callsTo(string $name): array
|
||||||
|
{
|
||||||
|
return array_values(array_map(fn(array $call): array => $call[1], array_filter($this->calls, fn(array $call): bool => $call[0] === $name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Names of the calls, without the API log of every request (redis->lPush) */
|
||||||
|
public function callNames(): array
|
||||||
|
{
|
||||||
|
return array_values(array_filter(array_column($this->calls, 0), fn(string $name): bool => $name !== 'redis->lPush'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs requests through the real data/web/json_api.php in a process of its own (Harness/runner.php). The legacy
|
||||||
|
* functions (mailbox(), bcc(), ...) and the module classes it calls are recorders: a test checks the HTTP behaviour of
|
||||||
|
* a route and which backend call it makes with which arguments, not the backend itself.
|
||||||
|
*
|
||||||
|
* $result = $this->post('add/alias', ['address' => 'a@example.org', 'goto' => 'b@example.org']);
|
||||||
|
* $this->assertCalled($result, 'mailbox', ['add', 'alias', ['address' => 'a@example.org', 'goto' => 'b@example.org']]);
|
||||||
|
*
|
||||||
|
* Backend answers are scripted with self::answer(); a call without one returns null (module chains like
|
||||||
|
* Module::fromGlobals()->api->x() resolve to recorders).
|
||||||
|
*/
|
||||||
|
abstract class ApiTestCase extends TestCase
|
||||||
|
{
|
||||||
|
/** Session of a read/write API key (sessions.inc.php logs it in as the admin "API") */
|
||||||
|
protected const API_RW = ['mailcow_cc_username' => 'API', 'mailcow_cc_role' => 'admin', 'mailcow_cc_api' => true, 'mailcow_cc_api_access' => 'rw'];
|
||||||
|
/** Session of a read-only API key */
|
||||||
|
protected const API_RO = ['mailcow_cc_username' => 'API', 'mailcow_cc_role' => 'admin', 'mailcow_cc_api' => true, 'mailcow_cc_api_access' => 'ro'];
|
||||||
|
/** UI sessions */
|
||||||
|
protected const ADMIN = ['mailcow_cc_username' => 'admin', 'mailcow_cc_role' => 'admin'];
|
||||||
|
protected const DOMAIN_ADMIN = ['mailcow_cc_username' => 'domainadmin', 'mailcow_cc_role' => 'domainadmin'];
|
||||||
|
protected const USER = ['mailcow_cc_username' => 'user@example.org', 'mailcow_cc_role' => 'user'];
|
||||||
|
protected const NO_SESSION = [];
|
||||||
|
|
||||||
|
private const JSON_API = __DIR__ . '/../../../data/web/json_api.php';
|
||||||
|
/** PHP functions with outside effects that json_api.php calls directly: disabled, so the harness records them */
|
||||||
|
private const DISABLED = ['curl_init', 'curl_setopt', 'curl_setopt_array', 'curl_exec', 'curl_close', 'curl_error', 'curl_errno', 'curl_getinfo'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One scripted backend answer: the first call of $call whose arguments start with $args returns $return and
|
||||||
|
* appends $messages to $_SESSION['return'] (what the legacy functions do), $once makes it answer one call only.
|
||||||
|
*/
|
||||||
|
protected static function answer(string $call, array $args = [], mixed $return = null, array $messages = [], bool $once = false): array
|
||||||
|
{
|
||||||
|
return ['call' => $call, 'args' => $args, 'return' => $return, 'messages' => $messages, 'once' => $once];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A mailcow message as the legacy functions put it into $_SESSION['return'] */
|
||||||
|
protected static function message(string $type, string|array $msg, array $log = []): array
|
||||||
|
{
|
||||||
|
return ['type' => $type, 'log' => $log, 'msg' => $msg];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{session?: array, script?: list<array>, server?: array, query?: array} $options
|
||||||
|
*/
|
||||||
|
protected function get(string $route, array $options = []): ApiResult
|
||||||
|
{
|
||||||
|
return $this->request('GET', $route, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST with a JSON body (any value; a string is sent as it is), as API clients send it
|
||||||
|
*
|
||||||
|
* @param array{session?: array, script?: list<array>, server?: array, query?: array, content_type?: string} $options
|
||||||
|
*/
|
||||||
|
protected function post(string $route, mixed $json = null, array $options = []): ApiResult
|
||||||
|
{
|
||||||
|
if ($json !== null) {
|
||||||
|
$options['body'] = is_string($json) ? $json : json_encode($json);
|
||||||
|
$options['content_type'] ??= 'application/json';
|
||||||
|
}
|
||||||
|
return $this->request('POST', $route, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST with form fields (attr, items as JSON strings), as the mailcow UI sends it
|
||||||
|
*
|
||||||
|
* @param array<string, string> $fields
|
||||||
|
*/
|
||||||
|
protected function postForm(string $route, array $fields, array $options = []): ApiResult
|
||||||
|
{
|
||||||
|
$options['post'] = $fields;
|
||||||
|
$options['content_type'] ??= 'application/x-www-form-urlencoded';
|
||||||
|
return $this->request('POST', $route, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $route the part after /api/v1/, e.g. "get/domain/all"
|
||||||
|
* @param array{session?: array, script?: list<array>, server?: array, query?: array, post?: array, body?: string,
|
||||||
|
* content_type?: string, globals?: array, allow_error?: bool} $options globals: variables prerequisites.inc.php
|
||||||
|
* would set (e.g. iam_settings)
|
||||||
|
*/
|
||||||
|
protected function request(string $method, string $route, array $options = []): ApiResult
|
||||||
|
{
|
||||||
|
$dir = sys_get_temp_dir();
|
||||||
|
$requestFile = tempnam($dir, 'api-req-');
|
||||||
|
$resultFile = tempnam($dir, 'api-res-');
|
||||||
|
try {
|
||||||
|
file_put_contents($requestFile, json_encode([
|
||||||
|
'json_api' => realpath(self::JSON_API),
|
||||||
|
'method' => $method,
|
||||||
|
'query' => ltrim(preg_replace('#^/?api/v1/#', '', $route), '/'),
|
||||||
|
'get' => $options['query'] ?? [],
|
||||||
|
'post' => $options['post'] ?? [],
|
||||||
|
'body' => $options['body'] ?? null,
|
||||||
|
'content_type' => $options['content_type'] ?? null,
|
||||||
|
'server' => $options['server'] ?? [],
|
||||||
|
'session' => $options['session'] ?? self::API_RW,
|
||||||
|
'script' => $options['script'] ?? [],
|
||||||
|
'globals' => $options['globals'] ?? [],
|
||||||
|
'result' => $resultFile,
|
||||||
|
], JSON_THROW_ON_ERROR));
|
||||||
|
$process = proc_open([PHP_BINARY, '-d', 'display_errors=stderr', '-d', 'disable_functions=' . implode(',', self::DISABLED),
|
||||||
|
__DIR__ . '/Harness/runner.php', $requestFile],
|
||||||
|
[0 => ['file', '/dev/null', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
|
||||||
|
$stdout = stream_get_contents($pipes[1]);
|
||||||
|
$stderr = stream_get_contents($pipes[2]);
|
||||||
|
fclose($pipes[1]);
|
||||||
|
fclose($pipes[2]);
|
||||||
|
proc_close($process);
|
||||||
|
$data = json_decode((string)file_get_contents($resultFile), true);
|
||||||
|
$this->assertIsArray($data, "the harness wrote no result for $method $route:\n$stdout$stderr");
|
||||||
|
} finally {
|
||||||
|
@unlink($requestFile);
|
||||||
|
@unlink($resultFile);
|
||||||
|
}
|
||||||
|
$result = new ApiResult((int)$data['status'], (string)$data['body'], $data['calls'], $data['session_return'], $data['error']);
|
||||||
|
if (empty($options['allow_error'])) {
|
||||||
|
$this->assertNull($result->error, "json_api.php failed for $method $route");
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** At least one call of $name had exactly $args */
|
||||||
|
protected function assertCalled(ApiResult $result, string $name, array $args): void
|
||||||
|
{
|
||||||
|
$this->assertContains($args, $result->callsTo($name),
|
||||||
|
"no call $name(" . json_encode($args) . "), calls were:\n" . json_encode($result->calls, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** $name was not called at all */
|
||||||
|
protected function assertNotCalled(ApiResult $result, string $name): void
|
||||||
|
{
|
||||||
|
$this->assertSame([], $result->callsTo($name), "unexpected call of $name");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The JSON answer json_api.php gives without calling a backend, e.g. 404 route not found */
|
||||||
|
protected function assertApiError(ApiResult $result, int $status, string $msg): void
|
||||||
|
{
|
||||||
|
$this->assertSame([$status, ['type' => 'error', 'msg' => $msg]], [$result->status, $result->json()], $result->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Domains;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** get/add/edit/delete alias-domain */
|
||||||
|
class AliasDomainTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testAllAliasDomainsAreReadOneByOne(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/alias-domain/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'alias_domains'], ['example.net', 'example.com']),
|
||||||
|
self::answer('mailbox', ['get', 'alias_domain_details', 'example.net'], ['alias_domain' => 'example.net', 'target_domain' => 'example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'alias_domain_details', 'example.com'], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'alias_domains']);
|
||||||
|
$this->assertSame([['alias_domain' => 'example.net', 'target_domain' => 'example.org']], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllAliasDomainsWithoutAliasDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/alias-domain/all', ['script' => [self::answer('mailbox', ['get', 'alias_domains'], [])]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
$this->assertSame([['get', 'alias_domains']], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOneAliasDomain(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/alias-domain/example.net', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'alias_domain_details', 'example.net'], ['alias_domain' => 'example.net', 'rl' => false]),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame(['alias_domain' => 'example.net', 'rl' => false], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownAliasDomain(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/alias-domain/example.com', ['script' => [self::answer('mailbox', ['get', 'alias_domain_details'], false)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'alias_domain_details', 'example.com']);
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddAliasDomains(): void
|
||||||
|
{
|
||||||
|
$attr = ['alias_domain' => "example.net\nexample.com", 'target_domain' => 'example.org', 'active' => '1', 'key_size' => '2048', 'dkim_selector' => 'dkim'];
|
||||||
|
|
||||||
|
$result = $this->post('add/alias-domain', $attr);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['add', 'alias_domain', $attr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditAliasDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/alias-domain', ['items' => ['example.net', 'example.com'], 'attr' => ['active' => '0']]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'alias_domain', ['alias_domain' => ['example.net', 'example.com'], 'active' => '0']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteAliasDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/alias-domain', ['example.net']);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['delete', 'alias_domain', ['alias_domain' => ['example.net']]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Domains;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** get/dkim, add/dkim, add/dkim_duplicate, add/dkim_import, delete/dkim */
|
||||||
|
class DkimTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testDkimKeyOfADomain(): void
|
||||||
|
{
|
||||||
|
$key = ['pubkey' => 'MIIBIjAN', 'length' => '2048', 'dkim_txt' => 'v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjAN', 'dkim_selector' => 'dkim', 'privkey' => ''];
|
||||||
|
|
||||||
|
$result = $this->get('get/dkim/example.org', ['script' => [self::answer('dkim', ['details', 'example.org'], $key)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['details', 'example.org']], $result->callsTo('dkim'));
|
||||||
|
$this->assertSame($key, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDomainWithoutKey(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/dkim/example.com', ['script' => [self::answer('dkim', ['details'], [])]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGenerateKeys(): void
|
||||||
|
{
|
||||||
|
$attr = ['domains' => 'example.org, example.net', 'dkim_selector' => 'dkim', 'key_size' => 2048];
|
||||||
|
|
||||||
|
$result = $this->post('add/dkim', $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['add', $attr]], $result->callsTo('dkim'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDuplicateKey(): void
|
||||||
|
{
|
||||||
|
$attr = ['from_domain' => 'example.org', 'to_domain' => ['example.net', 'example.com']];
|
||||||
|
|
||||||
|
$result = $this->post('add/dkim_duplicate', $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['duplicate', $attr]], $result->callsTo('dkim'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportKey(): void
|
||||||
|
{
|
||||||
|
$attr = ['domain' => 'example.org', 'dkim_selector' => 'dkim2026', 'private_key_file' => "-----BEGIN PRIVATE KEY-----\nMIIE\n-----END PRIVATE KEY-----", 'overwrite_existing' => '1'];
|
||||||
|
|
||||||
|
$result = $this->post('add/dkim_import', $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['import', $attr]], $result->callsTo('dkim'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteKeys(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/dkim', ['example.org', 'example.net']);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', ['domains' => ['example.org', 'example.net']]]], $result->callsTo('dkim'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Domains;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** get/policy_wl_domain, get/policy_bl_domain, add/domain-policy, delete/domain-policy */
|
||||||
|
class DomainPolicyTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const LISTS = [
|
||||||
|
'whitelist' => [['object' => 'example.org', 'value' => '*@partner.example.com', 'prefid' => 12]],
|
||||||
|
'blacklist' => [['object' => 'anna@example.org', 'value' => '*@spam.example.com', 'prefid' => 13]],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function testAllowListOfADomain(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/policy_wl_domain/example.org', ['script' => [self::answer('policy', ['get', 'domain', 'example.org'], self::LISTS)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'domain', 'example.org']], $result->callsTo('policy'));
|
||||||
|
$this->assertSame(self::LISTS['whitelist'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDenyListOfADomain(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/policy_bl_domain/example.org', ['script' => [self::answer('policy', ['get', 'domain', 'example.org'], self::LISTS)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'domain', 'example.org']], $result->callsTo('policy'));
|
||||||
|
$this->assertSame(self::LISTS['blacklist'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyListsAndUnknownDomains(): void
|
||||||
|
{
|
||||||
|
$empty = $this->get('get/policy_wl_domain/example.org', ['script' => [self::answer('policy', ['get', 'domain'], ['whitelist' => [], 'blacklist' => []])]]);
|
||||||
|
$this->assertSame('{}', $empty->body);
|
||||||
|
|
||||||
|
$unknown = $this->get('get/policy_bl_domain/example.com', ['script' => [self::answer('policy', ['get', 'domain'], false)]]);
|
||||||
|
$this->assertSame('{}', $unknown->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddEntry(): void
|
||||||
|
{
|
||||||
|
$attr = ['domain' => 'example.org', 'object_list' => 'wl', 'object_from' => '*@partner.example.com'];
|
||||||
|
|
||||||
|
$result = $this->post('add/domain-policy', $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['add', 'domain', $attr]], $result->callsTo('policy'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteEntries(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/domain-policy', [12, 13]);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', 'domain', ['prefid' => [12, 13]]]], $result->callsTo('policy'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Domains;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** get/rl-domain, edit/rl-domain */
|
||||||
|
class DomainRatelimitTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testRatelimitOfADomain(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/rl-domain/example.org', ['script' => [self::answer('ratelimit', ['get', 'domain', 'example.org'], ['value' => '100', 'frame' => 'h'])]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'domain', 'example.org']], $result->callsTo('ratelimit'));
|
||||||
|
$this->assertSame(['value' => '100', 'frame' => 'h'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDomainWithoutRatelimit(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/rl-domain/example.org', ['script' => [self::answer('ratelimit', ['get', 'domain'], false)]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllRatelimitsOfDomainsAndAliasDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/rl-domain/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com']),
|
||||||
|
self::answer('mailbox', ['get', 'alias_domains'], ['example.net']),
|
||||||
|
self::answer('ratelimit', ['get', 'domain', 'example.org'], ['value' => '100', 'frame' => 'h']),
|
||||||
|
self::answer('ratelimit', ['get', 'domain', 'example.com'], false),
|
||||||
|
self::answer('ratelimit', ['get', 'domain', 'example.net'], ['value' => '10', 'frame' => 's']),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'domains'], ['get', 'alias_domains']], $result->callsTo('mailbox'));
|
||||||
|
$this->assertSame([['get', 'domain', 'example.org'], ['get', 'domain', 'example.com'], ['get', 'domain', 'example.net']], $result->callsTo('ratelimit'));
|
||||||
|
$this->assertSame([
|
||||||
|
['value' => '100', 'frame' => 'h', 'domain' => 'example.org'],
|
||||||
|
['value' => '10', 'frame' => 's', 'domain' => 'example.net'],
|
||||||
|
], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllWithoutRatelimits(): void
|
||||||
|
{
|
||||||
|
$none = $this->get('get/rl-domain/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'alias_domains'], []),
|
||||||
|
self::answer('ratelimit', ['get', 'domain'], false),
|
||||||
|
]]);
|
||||||
|
$this->assertSame('{}', $none->body);
|
||||||
|
|
||||||
|
$noDomains = $this->get('get/rl-domain/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], []),
|
||||||
|
self::answer('mailbox', ['get', 'alias_domains'], []),
|
||||||
|
]]);
|
||||||
|
$this->assertSame('{}', $noDomains->body);
|
||||||
|
$this->assertNotCalled($noDomains, 'ratelimit');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetRatelimits(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/rl-domain', ['items' => ['example.org', 'example.net'], 'attr' => ['rl_value' => 100, 'rl_frame' => 'h']]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', 'domain', ['object' => ['example.org', 'example.net'], 'rl_value' => 100, 'rl_frame' => 'h']]], $result->callsTo('ratelimit'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Domains;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiResult;
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** search/domain: DataTables server-side processing (SSP) of the domain table */
|
||||||
|
class DomainSearchTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const REQUEST = [
|
||||||
|
'draw' => 3,
|
||||||
|
'start' => 0,
|
||||||
|
'length' => 10,
|
||||||
|
'search' => ['value' => 'example'],
|
||||||
|
'order' => [['column' => 2, 'dir' => 'asc']],
|
||||||
|
'columns' => [
|
||||||
|
['data' => '', 'searchable' => false, 'orderable' => false],
|
||||||
|
['data' => 'chkbox', 'searchable' => false, 'orderable' => false],
|
||||||
|
['data' => 'domain_h_name', 'searchable' => true, 'orderable' => true, 'search' => ['value' => '']],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
private const PAGE = ['draw' => 3, 'recordsTotal' => 12, 'recordsFiltered' => 2, 'data' => [[2 => 'example.org'], [2 => 'example.com']]];
|
||||||
|
|
||||||
|
public function testAdminsSearchAllDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('search/domain', self::REQUEST, ['script' => [self::answer('SSP::simple', [], self::PAGE)]]);
|
||||||
|
|
||||||
|
$call = $this->sspCall($result, 'SSP::simple');
|
||||||
|
$this->assertSame([self::REQUEST, '@pdo', ['domain', 'd'], 'domain'], array_slice($call, 0, 4));
|
||||||
|
$this->assertSame([
|
||||||
|
2 => 'domain', 3 => 'aliases', 4 => 'mailboxes', 5 => 'quota', 6 => 'stats', 7 => 'defquota', 8 => 'maxquota',
|
||||||
|
10 => 'backupmx', 14 => 'tags', 15 => 'active',
|
||||||
|
], array_column($call[4], 'db', 'dt'));
|
||||||
|
$this->assertNotCalled($result, 'SSP::complex');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRowsAreReplacedByTheDomainDetails(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('search/domain', self::REQUEST, ['script' => [
|
||||||
|
self::answer('SSP::simple', [], self::PAGE),
|
||||||
|
self::answer('mailbox', ['get', 'domain_details', 'example.org'], ['domain_name' => 'example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'domain_details', 'example.com'], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'domain_details', 'example.org'], ['get', 'domain_details', 'example.com']], $result->callsTo('mailbox'));
|
||||||
|
$this->assertSame(['draw' => 3, 'recordsTotal' => 12, 'recordsFiltered' => 2, 'data' => [['domain_name' => 'example.org']]], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnEmptyPageIsAnsweredAsItIs(): void
|
||||||
|
{
|
||||||
|
$page = ['draw' => 1, 'recordsTotal' => 12, 'recordsFiltered' => 0, 'data' => []];
|
||||||
|
|
||||||
|
$result = $this->post('search/domain', ['draw' => 1], ['script' => [self::answer('SSP::simple', [], $page)]]);
|
||||||
|
|
||||||
|
$this->assertSame($page, $result->json());
|
||||||
|
$this->assertNotCalled($result, 'mailbox');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testErrorsOfTheSearchArePassedOn(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('search/domain', self::REQUEST, ['script' => [self::answer('SSP::simple', [], ['error' => 'An SQL error occurred: unknown column'])]]);
|
||||||
|
|
||||||
|
$this->assertSame(['error' => 'An SQL error occurred: unknown column'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadOnlyKeysCanSearch(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('search/domain', self::REQUEST, ['session' => self::API_RO, 'script' => [self::answer('SSP::simple', [], self::PAGE)]]);
|
||||||
|
|
||||||
|
$this->assertSame(200, $result->status);
|
||||||
|
$this->assertSame(self::REQUEST, $this->sspCall($result, 'SSP::simple')[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDomainAdminsSearchTheirDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('search/domain', self::REQUEST, ['session' => self::DOMAIN_ADMIN, 'script' => [self::answer('SSP::complex', [], self::PAGE)]]);
|
||||||
|
|
||||||
|
$call = $this->sspCall($result, 'SSP::complex');
|
||||||
|
$this->assertSame([self::REQUEST, '@pdo', ['domain', 'd'], 'domain'], array_slice($call, 0, 4));
|
||||||
|
$this->assertSame(['INNER JOIN domain_admins as da ON da.domain = d.domain', [
|
||||||
|
'condition' => 'da.active = 1 and da.username = :username',
|
||||||
|
'bindings' => ['username' => 'domainadmin'],
|
||||||
|
]], array_slice($call, 5));
|
||||||
|
$this->assertNotCalled($result, 'SSP::simple');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOtherLoginsAreRefused(): void
|
||||||
|
{
|
||||||
|
foreach ([self::USER, self::NO_SESSION] as $session) {
|
||||||
|
$result = $this->post('search/domain', self::REQUEST, ['session' => $session]);
|
||||||
|
|
||||||
|
$this->assertApiError($result, 403, 'Insufficient permissions');
|
||||||
|
$this->assertNotCalled($result, 'SSP::simple');
|
||||||
|
$this->assertNotCalled($result, 'SSP::complex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithoutJsonBodyTheSearchGetsNoParameters(): void
|
||||||
|
{
|
||||||
|
$result = $this->request('POST', 'search/domain', ['script' => [self::answer('SSP::simple', [], self::PAGE)]]);
|
||||||
|
|
||||||
|
$this->assertNull($this->sspCall($result, 'SSP::simple')[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sspCall(ApiResult $result, string $name): array
|
||||||
|
{
|
||||||
|
$calls = $result->callsTo($name);
|
||||||
|
$this->assertCount(1, $calls, "one call of $name");
|
||||||
|
return $calls[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Domains;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** get/add/edit/delete domain/template */
|
||||||
|
class DomainTemplateTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const TEMPLATE = ['id' => 1, 'template' => 'Default', 'type' => 'domain', 'attributes' => ['max_num_aliases_for_domain' => 400]];
|
||||||
|
|
||||||
|
public function testAllTemplates(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/template/all', ['script' => [self::answer('mailbox', ['get', 'domain_templates'], [self::TEMPLATE])]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'domain_templates']], $result->callsTo('mailbox'));
|
||||||
|
$this->assertSame([self::TEMPLATE], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOneTemplate(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/template/1', ['script' => [self::answer('mailbox', ['get', 'domain_templates', '1'], self::TEMPLATE)]]);
|
||||||
|
|
||||||
|
$this->assertSame(self::TEMPLATE, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownTemplate(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/template/9', ['script' => [self::answer('mailbox', ['get', 'domain_templates'], false)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'domain_templates', '9']);
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTemplateWithoutIdAsksForAll(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/template');
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'domain_templates', null]], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddTemplate(): void
|
||||||
|
{
|
||||||
|
$attr = ['template' => 'Small business', 'max_num_mboxes_for_domain' => 25, 'def_quota_for_mbox' => 2048, 'tags' => ['small']];
|
||||||
|
|
||||||
|
$result = $this->post('add/domain/template', $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['add', 'domain_templates', $attr]], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditTemplates(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/domain/template', ['items' => [2, 3], 'attr' => ['template' => 'Small business', 'active' => 1]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'domain_templates', ['ids' => [2, 3], 'template' => 'Small business', 'active' => 1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteTemplates(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/domain/template', [2, 3]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['delete', 'domain_templates', ['ids' => [2, 3]]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Domains;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** get/domain, add/domain, edit/domain, delete/domain, delete/domain/tag, edit/domain/footer */
|
||||||
|
class DomainTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testAllDomainsAreReadOneByOne(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com', 'example.net']),
|
||||||
|
self::answer('mailbox', ['get', 'domain_details', 'example.org'], ['domain_name' => 'example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'domain_details', 'example.com'], false),
|
||||||
|
self::answer('mailbox', ['get', 'domain_details', 'example.net'], ['domain_name' => 'example.net']),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'domains', null, null]);
|
||||||
|
$this->assertSame([['domain_name' => 'example.org'], ['domain_name' => 'example.net']], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllDomainsFilteredByTags(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/all', ['query' => ['tags' => 'customer,premium'], 'script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'domain_details', 'example.org'], ['domain_name' => 'example.org', 'tags' => ['customer']]),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'domains', null, ['customer', 'premium']]);
|
||||||
|
$this->assertSame([['domain_name' => 'example.org', 'tags' => ['customer']]], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnEmptyTagsParameterDoesNotFilter(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/all', ['query' => ['tags' => ''], 'script' => [self::answer('mailbox', ['get', 'domains'], [])]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'domains', null, null]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllDomainsWithoutDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/all', ['script' => [self::answer('mailbox', ['get', 'domains'], [])]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
$this->assertSame([['get', 'domains', null, null]], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllDomainsWithoutReadableDetails(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'domain_details'], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOneDomain(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/example.org', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domain_details', 'example.org'], ['domain_name' => 'example.org', 'rl' => false]),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame(['domain_name' => 'example.org', 'rl' => false], $result->json());
|
||||||
|
$this->assertSame([['get', 'domain_details', 'example.org']], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownDomain(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/example.com', ['script' => [self::answer('mailbox', ['get', 'domain_details'], false)]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddDomain(): void
|
||||||
|
{
|
||||||
|
$attr = ['domain' => 'example.org', 'description' => 'Example Org', 'defquota' => 3072, 'tags' => ['customer'], 'restart_sogo' => '1'];
|
||||||
|
|
||||||
|
$result = $this->post('add/domain', $attr);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['add', 'domain', $attr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddDomainIgnoresFurtherSegments(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('add/domain/example.org', ['domain' => 'example.org']);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['add', 'domain', ['domain' => 'example.org']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/domain', ['items' => ['example.org', 'example.com'], 'attr' => ['quota' => 51200, 'tags' => ['premium']]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'domain', ['domain' => ['example.org', 'example.com'], 'quota' => 51200, 'tags' => ['premium']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditDomainWithASingleItem(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/domain', ['items' => 'example.org', 'attr' => ['active' => '0']]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'domain', ['domain' => ['example.org'], 'active' => '0']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnAttributeNamedLikeTheItemsWins(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/domain', ['items' => ['example.org'], 'attr' => ['domain' => 'example.com']]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'domain', ['domain' => 'example.com']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/domain', ['example.org', 'example.com']);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['delete', 'domain', ['domain' => ['example.org', 'example.com']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteDomainTags(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/domain/tag/example.org', ['customer', 'premium']);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['delete', 'tags_domain', ['tags' => ['customer', 'premium'], 'domain' => 'example.org']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteDomainTagsWithoutTags(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/domain/tag/example.org', [], ['script' => [self::answer('mailbox', ['delete', 'tags_domain'], false)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['delete', 'tags_domain', ['tags' => [], 'domain' => 'example.org']]);
|
||||||
|
$this->assertSame(['type' => 'error', 'msg' => 'Cannot delete item'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteDomainTagsWithoutDomain(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/domain/tag', ['customer']);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['delete', 'tags_domain', ['tags' => ['customer'], 'domain' => null]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditDomainFooter(): void
|
||||||
|
{
|
||||||
|
$attr = ['html' => '<p>{= from_name =}</p>', 'plain' => '{= from_name =}', 'skip_replies' => 1, 'exclude' => ['noreply@example.org', 'example.net']];
|
||||||
|
|
||||||
|
$result = $this->post('edit/domain/footer', ['items' => ['example.org'], 'attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'domain_wide_footer', ['domains' => ['example.org']] + $attr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Domains;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** add/mta-sts, edit/mta-sts (there is no read route) */
|
||||||
|
class MtaStsTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testAddPolicy(): void
|
||||||
|
{
|
||||||
|
$attr = ['domain' => 'example.org', 'version' => 'stsv1', 'mode' => 'enforce', 'mx' => 'mail.example.org,mx2.example.org', 'max_age' => 86400, 'active' => 1];
|
||||||
|
|
||||||
|
$result = $this->post('add/mta-sts', $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['add', 'mta_sts', $attr]], $result->callsTo('mailbox'));
|
||||||
|
// the backend reports no success message of its own
|
||||||
|
$this->assertSame(['type' => 'success', 'msg' => 'Task completed'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditPolicies(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/mta-sts', ['items' => ['example.org'], 'attr' => ['version' => 'stsv1', 'mode' => 'testing']]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', 'mta_sts', ['domains' => ['example.org'], 'version' => 'stsv1', 'mode' => 'testing']]], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testThereIsNoReadRoute(): void
|
||||||
|
{
|
||||||
|
$this->assertApiError($this->get('get/mta-sts/example.org'), 404, 'route not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Filtering;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Fail2Ban settings, bans and the banlist URL */
|
||||||
|
class Fail2BanTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const BANLIST_ID = 'a2c11baf-5770-47de-9c5e-575bd7bc9218';
|
||||||
|
|
||||||
|
public function testGetFail2BanWithAnyObject(): void
|
||||||
|
{
|
||||||
|
$settings = ['ban_time' => 1800, 'ban_time_increment' => true, 'banlist_id' => self::BANLIST_ID, 'whitelist' => '192.0.2.0/24'];
|
||||||
|
|
||||||
|
foreach (['get/fail2ban', 'get/fail2ban/all'] as $route) {
|
||||||
|
$result = $this->get($route, ['script' => [self::answer('fail2ban', ['get'], $settings)]]);
|
||||||
|
$this->assertSame([['get']], $result->callsTo('fail2ban'), $route);
|
||||||
|
$this->assertSame($settings, $result->json(), $route);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/fail2ban', ['script' => [self::answer('fail2ban', ['get'], false)]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditFail2BanSettings(): void
|
||||||
|
{
|
||||||
|
$attr = ['ban_time' => '1800', 'max_attempts' => '10', 'whitelist' => "192.0.2.0/24\nmail.example.org"];
|
||||||
|
|
||||||
|
$result = $this->post('edit/fail2ban', ['items' => [], 'attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', ['network' => []] + $attr]], $result->callsTo('fail2ban'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItemsAreTheNetworks(): void
|
||||||
|
{
|
||||||
|
$list = $this->post('edit/fail2ban', ['items' => ['203.0.113.7/32', '2001:db8::/64'], 'attr' => ['action' => 'unban']]);
|
||||||
|
$this->assertCalled($list, 'fail2ban', ['edit', ['network' => ['203.0.113.7/32', '2001:db8::/64'], 'action' => 'unban']]);
|
||||||
|
|
||||||
|
$single = $this->post('edit/fail2ban', ['items' => '198.51.100.66/32', 'attr' => ['action' => 'blacklist']]);
|
||||||
|
$this->assertCalled($single, 'fail2ban', ['edit', ['network' => ['198.51.100.66/32'], 'action' => 'blacklist']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFurtherSegmentsAreIgnored(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/fail2ban/self', ['items' => ['self'], 'attr' => ['action' => 'unban-all']]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', ['network' => ['self'], 'action' => 'unban-all']]], $result->callsTo('fail2ban'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRefreshBanlist(): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('success', 'f2b_banlist_refreshed')];
|
||||||
|
|
||||||
|
$result = $this->post('edit/fail2ban/banlist', ['items' => [self::BANLIST_ID]], ['script' => [self::answer('fail2ban', ['banlist', 'refresh'], true, $messages)]]);
|
||||||
|
$this->assertSame([['banlist', 'refresh', [self::BANLIST_ID]]], $result->callsTo('fail2ban'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
|
||||||
|
$single = $this->post('edit/fail2ban/banlist', ['items' => self::BANLIST_ID, 'attr' => []]);
|
||||||
|
$this->assertSame([['banlist', 'refresh', [self::BANLIST_ID]]], $single->callsTo('fail2ban'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRefreshBanlistWithAWrongId(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/fail2ban/banlist', ['items' => ['wrong']], ['script' => [self::answer('fail2ban', ['banlist'], false)]]);
|
||||||
|
|
||||||
|
$this->assertSame(['type' => 'error', 'msg' => 'Cannot edit item'], $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Filtering;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Global Sieve prefilter and postfilter */
|
||||||
|
class GlobalFilterTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const FILTERS = ['prefilter' => "# before\n", 'postfilter' => "require \"fileinto\";\n"];
|
||||||
|
|
||||||
|
public function testAddGlobalFilter(): void
|
||||||
|
{
|
||||||
|
$attr = ['filter_type' => 'postfilter', 'script_data' => "require \"fileinto\";\nfileinto \"Junk\";"];
|
||||||
|
$messages = [
|
||||||
|
self::message('success', 'dovecot_restart_success'),
|
||||||
|
self::message('success', 'global_filter_written'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this->post('add/global-filter', $attr, ['script' => [self::answer('mailbox', ['add', 'global_filter'], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['add', 'global_filter', $attr]);
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllGlobalFilters(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/global_filters/all', ['script' => [self::answer('mailbox', ['get', 'global_filter_details'], self::FILTERS)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'global_filter_details']], $result->callsTo('mailbox'));
|
||||||
|
$this->assertSame(self::FILTERS, $result->json());
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/global_filters/all', ['script' => [self::answer('mailbox', ['get', 'global_filter_details'], false)]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOneGlobalFilterIsAJsonString(): void
|
||||||
|
{
|
||||||
|
$script = [self::answer('mailbox', ['get', 'global_filter_details'], self::FILTERS)];
|
||||||
|
|
||||||
|
$this->assertSame("# before\n", $this->get('get/global_filters/prefilter', ['script' => $script])->json());
|
||||||
|
$this->assertSame("require \"fileinto\";\n", $this->get('get/global_filters/postfilter', ['script' => $script])->json());
|
||||||
|
|
||||||
|
$empty = [self::answer('mailbox', ['get', 'global_filter_details'], ['prefilter' => '', 'postfilter' => ''])];
|
||||||
|
$this->assertSame('{}', $this->get('get/global_filters/prefilter', ['script' => $empty])->body);
|
||||||
|
$this->assertSame('{}', $this->get('get/global_filters/postfilter', ['script' => $empty])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOtherObjectsAnswerNothing(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/global_filters/before', ['script' => [self::answer('mailbox', ['get', 'global_filter_details'], self::FILTERS)]]);
|
||||||
|
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Filtering;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Quarantine items and settings */
|
||||||
|
class QuarantineTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testGetQuarantine(): void
|
||||||
|
{
|
||||||
|
$items = [['id' => 33, 'qid' => '8224615004C1', 'subject' => 'You have won', 'virus_flag' => 0, 'score' => 15.48, 'rcpt' => 'john.doe@example.org']];
|
||||||
|
|
||||||
|
$result = $this->get('get/quarantine/all', ['script' => [self::answer('quarantine', ['get'], $items)]]);
|
||||||
|
$this->assertSame([['get']], $result->callsTo('quarantine'));
|
||||||
|
$this->assertSame($items, $result->json());
|
||||||
|
|
||||||
|
$this->assertSame('[]', $this->get('get/quarantine/all')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetQuarantineItem(): void
|
||||||
|
{
|
||||||
|
$item = ['id' => 33, 'qid' => '8224615004C1', 'msg' => "Subject: You have won\r\n\r\nClick here\r\n", 'qhash' => str_repeat('a', 64)];
|
||||||
|
|
||||||
|
$result = $this->get('get/quarantine/33', ['script' => [self::answer('quarantine', ['details', '33'], $item)]]);
|
||||||
|
$this->assertSame([['details', '33']], $result->callsTo('quarantine'));
|
||||||
|
$this->assertSame($item, $result->json());
|
||||||
|
|
||||||
|
$this->assertSame('[]', $this->get('get/quarantine/34', ['script' => [self::answer('quarantine', ['details'], false)]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditQuarantineItems(): void
|
||||||
|
{
|
||||||
|
$release = $this->post('edit/qitem', ['items' => ['33', '34'], 'attr' => ['action' => 'release']]);
|
||||||
|
$this->assertCalled($release, 'quarantine', ['edit', ['id' => ['33', '34'], 'action' => 'release']]);
|
||||||
|
|
||||||
|
$spam = $this->post('edit/qitem', ['items' => 35, 'attr' => ['action' => 'learnspam']]);
|
||||||
|
$this->assertCalled($spam, 'quarantine', ['edit', ['id' => [35], 'action' => 'learnspam']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteQuarantineItems(): void
|
||||||
|
{
|
||||||
|
$this->assertCalled($this->post('delete/qitem', ['33', '34']), 'quarantine', ['delete', ['id' => ['33', '34']]]);
|
||||||
|
$this->assertCalled($this->post('delete/qitem', '"33"'), 'quarantine', ['delete', ['id' => ['33']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditQuarantineSettingsIgnoresItems(): void
|
||||||
|
{
|
||||||
|
$attr = ['action' => 'settings', 'retention_size' => '20', 'max_size' => '15', 'exclude_domains' => ['example.com']];
|
||||||
|
$messages = [self::message('success', 'saved_settings')];
|
||||||
|
|
||||||
|
$result = $this->post('edit/quarantine', ['items' => ['self'], 'attr' => $attr], ['script' => [self::answer('quarantine', ['edit'], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', $attr]], $result->callsTo('quarantine'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Filtering;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Postfix mail queue */
|
||||||
|
class QueueTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testGetMailQueuePassesTheJsonOfTheBackendThrough(): void
|
||||||
|
{
|
||||||
|
$queue = '[{"queue_name":"deferred","queue_id":"B98C6260CA1","sender":"sender@example.org","recipients":["rcpt@example.com"]}]';
|
||||||
|
|
||||||
|
$result = $this->get('get/mailq/all', ['script' => [self::answer('mailq', ['get'], $queue)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get']], $result->callsTo('mailq'));
|
||||||
|
$this->assertSame($queue, $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyMailQueue(): void
|
||||||
|
{
|
||||||
|
$this->assertSame('[]', $this->get('get/mailq/all', ['script' => [self::answer('mailq', ['get'], '[]')]])->body);
|
||||||
|
$this->assertSame('[]', $this->get('get/mailq/all', ['script' => [self::answer('mailq', ['get'], false)]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPostcatAnswersThePlainText(): void
|
||||||
|
{
|
||||||
|
$text = "*** ENVELOPE RECORDS deferred/B/B98C6260CA1 ***\nsender: sender@example.org\n*** MESSAGE CONTENTS deferred/B/B98C6260CA1 ***\nSubject: Test\n";
|
||||||
|
|
||||||
|
$result = $this->get('get/postcat/B98C6260CA1', ['script' => [self::answer('mailq', ['cat'], $text)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['cat', ['qid' => 'B98C6260CA1']]], $result->callsTo('mailq'));
|
||||||
|
$this->assertSame($text, $result->body);
|
||||||
|
$this->assertNull($result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPostcatWithoutAccessAnswersNothing(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/postcat/B98C6260CA1', ['script' => [self::answer('mailq', ['cat'], false)]]);
|
||||||
|
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditMailQueue(): void
|
||||||
|
{
|
||||||
|
$hold = $this->post('edit/mailq', ['items' => ['B98C6260CA1', '4C1E8260D02'], 'attr' => ['action' => 'hold']]);
|
||||||
|
$this->assertCalled($hold, 'mailq', ['edit', ['qid' => ['B98C6260CA1', '4C1E8260D02'], 'action' => 'hold']]);
|
||||||
|
|
||||||
|
$deliver = $this->post('edit/mailq', ['items' => 'B98C6260CA1', 'attr' => ['action' => 'deliver']]);
|
||||||
|
$this->assertCalled($deliver, 'mailq', ['edit', ['qid' => ['B98C6260CA1'], 'action' => 'deliver']]);
|
||||||
|
|
||||||
|
$flush = $this->post('edit/mailq', ['items' => [], 'attr' => ['action' => 'flush']]);
|
||||||
|
$this->assertCalled($flush, 'mailq', ['edit', ['qid' => [], 'action' => 'flush']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnActionOutsideOfAttrIsNotSeen(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/mailq', ['action' => 'flush']);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailq', ['edit', ['qid' => []]]);
|
||||||
|
$this->assertSame(['type' => 'success', 'msg' => 'Task completed'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteQueuedMessages(): void
|
||||||
|
{
|
||||||
|
$this->assertCalled($this->post('delete/mailq', ['B98C6260CA1', '4C1E8260D02']), 'mailq', ['delete', ['qid' => ['B98C6260CA1', '4C1E8260D02']]]);
|
||||||
|
$this->assertCalled($this->post('delete/mailq', '"B98C6260CA1"'), 'mailq', ['delete', ['qid' => ['B98C6260CA1']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteTakesNoAction(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/mailq', ['action' => 'super_delete']);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', ['qid' => ['action' => 'super_delete']]]], $result->callsTo('mailq'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Filtering;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Quota warning mails and their per-domain BCC */
|
||||||
|
class QuotaNotificationTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testEditQuotaNotificationIgnoresItems(): void
|
||||||
|
{
|
||||||
|
$attr = ['sender' => 'quota-warning@example.org', 'subject' => 'Quota warning', 'html_tmpl' => ''];
|
||||||
|
|
||||||
|
$result = $this->post('edit/quota_notification', ['items' => ['quota_notification'], 'attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', $attr]], $result->callsTo('quota_notification'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditQuotaNotificationBccIgnoresItems(): void
|
||||||
|
{
|
||||||
|
$attr = ['domain' => 'example.org', 'bcc_rcpt' => "postmaster@example.org\nhelpdesk@example.org", 'active' => '1'];
|
||||||
|
$messages = [self::message('success', 'saved_settings')];
|
||||||
|
|
||||||
|
$result = $this->post('edit/quota_notification_bcc', ['items' => ['quota_bcc'], 'attr' => $attr], ['script' => [self::answer('quota_notification_bcc', ['edit'], null, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', $attr]], $result->callsTo('quota_notification_bcc'));
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Filtering;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Rspamd settings rules, global regex maps, action statistics and presets */
|
||||||
|
class RspamdTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const RULE = ['desc' => 'Disable Rspamd for example.org', 'content' => 'priority = 10;', 'active' => '1'];
|
||||||
|
|
||||||
|
public function testAddRsetting(): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('success', 'settings_map_added', ['rsettings', 'add', self::RULE])];
|
||||||
|
|
||||||
|
$result = $this->post('add/rsetting', self::RULE, ['script' => [self::answer('rsettings', ['add'], null, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'rsettings', ['add', self::RULE]);
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllRsettingsReadsTheDetailsOfEveryRule(): void
|
||||||
|
{
|
||||||
|
$first = ['id' => 1, 'desc' => 'first', 'content' => 'priority = 10;', 'active' => 1];
|
||||||
|
$third = ['id' => 3, 'desc' => 'third', 'content' => 'priority = 5;', 'active' => 0];
|
||||||
|
|
||||||
|
$result = $this->get('get/rsetting/all', ['script' => [
|
||||||
|
self::answer('rsettings', ['get'], [['id' => 1, 'desc' => 'first', 'active' => 1], ['id' => 2, 'desc' => 'gone', 'active' => 1], ['id' => 3, 'desc' => 'third', 'active' => 0]]),
|
||||||
|
self::answer('rsettings', ['details', 1], $first),
|
||||||
|
self::answer('rsettings', ['details', 2], false),
|
||||||
|
self::answer('rsettings', ['details', 3], $third),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get'], ['details', 1], ['details', 2], ['details', 3]], $result->callsTo('rsettings'));
|
||||||
|
$this->assertSame([$first, $third], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllRsettingsWithoutRules(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/rsetting/all', ['script' => [self::answer('rsettings', ['get'], false)]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
$this->assertSame([['get']], $result->callsTo('rsettings'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOneRsetting(): void
|
||||||
|
{
|
||||||
|
$rule = ['id' => 7, 'desc' => 'seven', 'content' => 'priority = 10;', 'active' => 1];
|
||||||
|
|
||||||
|
$found = $this->get('get/rsetting/7', ['script' => [self::answer('rsettings', ['details', '7'], $rule)]]);
|
||||||
|
$this->assertSame([['details', '7']], $found->callsTo('rsettings'));
|
||||||
|
$this->assertSame($rule, $found->json());
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/rsetting/8', ['script' => [self::answer('rsettings', ['details'], false)]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditRsetting(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/rsetting', ['items' => [1, 2], 'attr' => ['desc' => 'renamed', 'active' => '0']]);
|
||||||
|
$this->assertCalled($result, 'rsettings', ['edit', ['id' => [1, 2], 'desc' => 'renamed', 'active' => '0']]);
|
||||||
|
|
||||||
|
$single = $this->post('edit/rsetting', ['items' => '3', 'attr' => ['content' => 'priority = 1;']]);
|
||||||
|
$this->assertCalled($single, 'rsettings', ['edit', ['id' => ['3'], 'content' => 'priority = 1;']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteRsetting(): void
|
||||||
|
{
|
||||||
|
$this->assertCalled($this->post('delete/rsetting', [1, 2]), 'rsettings', ['delete', ['id' => [1, 2]]]);
|
||||||
|
$this->assertCalled($this->post('delete/rsetting', 3), 'rsettings', ['delete', ['id' => [3]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditRspamdMap(): void
|
||||||
|
{
|
||||||
|
$data = "/^spammer@example\\.com$/i\n/@spam\\.example\\.net$/i";
|
||||||
|
|
||||||
|
$result = $this->post('edit/rspamd-map', ['items' => ['global_smtp_from_blacklist.map', 'global_rcpt_blacklist.map'], 'attr' => ['rspamd_map_data' => $data]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'rspamd_maps', ['edit', ['map' => ['global_smtp_from_blacklist.map', 'global_rcpt_blacklist.map'], 'rspamd_map_data' => $data]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRspamdActions(): void
|
||||||
|
{
|
||||||
|
$actions = [['reject', 38], ['soft reject', 1], ['add header', 6], ['no action', 255]];
|
||||||
|
|
||||||
|
$result = $this->get('get/rspamd/actions', ['script' => [self::answer('rspamd_actions', [], $actions)]]);
|
||||||
|
$this->assertSame([[]], $result->callsTo('rspamd_actions'));
|
||||||
|
$this->assertSame($actions, $result->json());
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/rspamd/actions', ['script' => [self::answer('rspamd_actions', [], false)]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOtherRspamdObjectsAnswerNothing(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/rspamd/stat');
|
||||||
|
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body]);
|
||||||
|
$this->assertNotCalled($result, 'rspamd_actions');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPresets(): void
|
||||||
|
{
|
||||||
|
$preset = [['name' => 'preset_1', 'headline' => 'Disable all but DKIM', 'content' => 'priority = 10;']];
|
||||||
|
|
||||||
|
$rspamd = $this->get('get/presets/rspamd', ['script' => [self::answer('presets', ['get', 'rspamd'], $preset)]]);
|
||||||
|
$this->assertSame([['get', 'rspamd']], $rspamd->callsTo('presets'));
|
||||||
|
$this->assertSame($preset, $rspamd->json());
|
||||||
|
|
||||||
|
$sieve = $this->get('get/presets/sieve', ['session' => self::DOMAIN_ADMIN]);
|
||||||
|
$this->assertSame([['get', 'sieve']], $sieve->callsTo('presets'));
|
||||||
|
$this->assertSame('{}', $sieve->body);
|
||||||
|
|
||||||
|
$other = $this->get('get/presets/other');
|
||||||
|
$this->assertSame([200, ''], [$other->status, $other->body]);
|
||||||
|
$this->assertNotCalled($other, 'presets');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Runs data/web/json_api.php for one request of ApiTestCase in a process of its own. The request comes from the JSON
|
||||||
|
// file in argv[1]; every legacy function and class json_api.php calls is a recorder; the status code, the output and
|
||||||
|
// the recorded calls go to the result file named in the request.
|
||||||
|
|
||||||
|
namespace ApiHarness {
|
||||||
|
|
||||||
|
/** The recorded calls and the scripted answers of one request */
|
||||||
|
final class Calls
|
||||||
|
{
|
||||||
|
/** @var list<array{0: string, 1: array}> */
|
||||||
|
public static array $calls = [];
|
||||||
|
/** @var list<array{call: string, args?: array, return?: mixed, messages?: list<array>, once?: bool}> */
|
||||||
|
public static array $script = [];
|
||||||
|
/** Methods that return a recorder unless scripted, so that chains like Module::fromGlobals()->api->x() work */
|
||||||
|
private const CHAINED = ['fromGlobals', 'create', 'prepare', 'query'];
|
||||||
|
|
||||||
|
public static function record(string $name, array $args, ?string $method = null): mixed
|
||||||
|
{
|
||||||
|
$args = self::plain($args);
|
||||||
|
self::$calls[] = [$name, $args];
|
||||||
|
foreach (self::$script as $i => $entry) {
|
||||||
|
if ($entry['call'] !== $name || !self::startsWith($args, $entry['args'] ?? [])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($entry['messages'] ?? [] as $message) {
|
||||||
|
$_SESSION['return'][] = $message;
|
||||||
|
}
|
||||||
|
if (!empty($entry['once'])) {
|
||||||
|
unset(self::$script[$i]);
|
||||||
|
}
|
||||||
|
return $entry['return'] ?? null;
|
||||||
|
}
|
||||||
|
return ($method !== null && in_array($method, self::CHAINED, true))
|
||||||
|
? new Recorder(preg_replace('/::(fromGlobals|create)$/', '', $name)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Arguments as JSON data: recorders and other objects become "@<name>" */
|
||||||
|
private static function plain(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if ($value instanceof Recorder) {
|
||||||
|
return '@' . $value->label();
|
||||||
|
}
|
||||||
|
if (is_object($value)) {
|
||||||
|
return '@' . get_class($value);
|
||||||
|
}
|
||||||
|
return is_array($value) ? array_map([self::class, 'plain'], $value) : $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function startsWith(array $args, array $prefix): bool
|
||||||
|
{
|
||||||
|
foreach (array_values($prefix) as $i => $expected) {
|
||||||
|
if (!array_key_exists($i, $args) || $args[$i] !== $expected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stand-in for objects and classes: records method calls, reads give further recorders */
|
||||||
|
class Recorder implements \Countable, \IteratorAggregate, \ArrayAccess, \JsonSerializable
|
||||||
|
{
|
||||||
|
private string $recorderLabel;
|
||||||
|
|
||||||
|
public function __construct(mixed ...$args)
|
||||||
|
{
|
||||||
|
$this->recorderLabel = (isset($args[0]) && is_string($args[0]) && static::class === self::class) ? $args[0] : static::class;
|
||||||
|
if (static::class !== self::class) {
|
||||||
|
Calls::record('new ' . static::class, $args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return $this->recorderLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get(string $name): self
|
||||||
|
{
|
||||||
|
return new self($this->recorderLabel . '->' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call(string $name, array $args): mixed
|
||||||
|
{
|
||||||
|
return Calls::record($this->recorderLabel . '->' . $name, $args, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function __callStatic(string $name, array $args): mixed
|
||||||
|
{
|
||||||
|
return Calls::record(static::class . '::' . $name, $args, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIterator(): \Iterator
|
||||||
|
{
|
||||||
|
return new \EmptyIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetExists(mixed $offset): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetGet(mixed $offset): mixed
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetSet(mixed $offset, mixed $value): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetUnset(mixed $offset): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): mixed
|
||||||
|
{
|
||||||
|
return ['@' => $this->recorderLabel];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** php://input with the request body (the CLI has none); every other php:// stream is the real one */
|
||||||
|
final class InputStream
|
||||||
|
{
|
||||||
|
public static string $body = '';
|
||||||
|
/** @var resource|null */
|
||||||
|
public $context;
|
||||||
|
private int $position = 0;
|
||||||
|
/** @var resource|null */
|
||||||
|
private $inner = null;
|
||||||
|
|
||||||
|
public function stream_open(string $path, string $mode, int $options, ?string &$openedPath): bool
|
||||||
|
{
|
||||||
|
if (strtolower($path) === 'php://input') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
stream_wrapper_restore('php');
|
||||||
|
$this->inner = fopen($path, $mode) ?: null;
|
||||||
|
stream_wrapper_unregister('php');
|
||||||
|
stream_wrapper_register('php', self::class);
|
||||||
|
return $this->inner !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_read(int $count): string|false
|
||||||
|
{
|
||||||
|
if ($this->inner !== null) {
|
||||||
|
return fread($this->inner, $count);
|
||||||
|
}
|
||||||
|
$chunk = substr(self::$body, $this->position, $count);
|
||||||
|
$this->position += strlen($chunk);
|
||||||
|
return $chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_write(string $data): int
|
||||||
|
{
|
||||||
|
return ($this->inner !== null) ? (int)fwrite($this->inner, $data) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_eof(): bool
|
||||||
|
{
|
||||||
|
return ($this->inner !== null) ? feof($this->inner) : $this->position >= strlen(self::$body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_stat(): array|false
|
||||||
|
{
|
||||||
|
return ($this->inner !== null) ? fstat($this->inner) : ['size' => strlen(self::$body)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_close(): void
|
||||||
|
{
|
||||||
|
if ($this->inner !== null) {
|
||||||
|
fclose($this->inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Names of the functions $file calls that are neither PHP functions nor declared in $file */
|
||||||
|
function calledFunctions(string $file): array
|
||||||
|
{
|
||||||
|
$tokens = array_values(array_filter(token_get_all(file_get_contents($file)),
|
||||||
|
fn($t) => !is_array($t) || !in_array($t[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)));
|
||||||
|
$declared = [];
|
||||||
|
$called = [];
|
||||||
|
foreach ($tokens as $i => $token) {
|
||||||
|
if (!is_array($token) || $token[0] !== T_STRING) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$previous = $tokens[$i - 1] ?? null;
|
||||||
|
$next = $tokens[$i + 1] ?? null;
|
||||||
|
if (is_array($previous) && $previous[0] === T_FUNCTION) {
|
||||||
|
$declared[strtolower($token[1])] = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($next !== '(' || (is_array($previous) && in_array($previous[0], [T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR, T_DOUBLE_COLON, T_NEW], true))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$called[strtolower($token[1])] = $token[1];
|
||||||
|
}
|
||||||
|
return array_values(array_filter($called, fn(string $name): bool => !isset($declared[strtolower($name)]) && !function_exists($name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
use ApiHarness\Calls;
|
||||||
|
use ApiHarness\InputStream;
|
||||||
|
|
||||||
|
$request = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$jsonApi = $request['json_api'];
|
||||||
|
|
||||||
|
foreach (ApiHarness\calledFunctions($jsonApi) as $name) {
|
||||||
|
eval('function ' . $name . '(...$args) { return \ApiHarness\Calls::record(' . var_export($name, true) . ', $args); }');
|
||||||
|
}
|
||||||
|
spl_autoload_register(function (string $class): void {
|
||||||
|
$parts = explode('\\', ltrim($class, '\\'));
|
||||||
|
$short = array_pop($parts);
|
||||||
|
$namespace = implode('\\', $parts);
|
||||||
|
eval(($namespace !== '' ? "namespace $namespace; " : '') . "class $short extends \\ApiHarness\\Recorder {}");
|
||||||
|
});
|
||||||
|
|
||||||
|
$_SERVER['DOCUMENT_ROOT'] = __DIR__ . '/webroot';
|
||||||
|
$_SERVER['REQUEST_METHOD'] = $request['method'];
|
||||||
|
$_SERVER['REQUEST_URI'] = '/api/v1/' . $request['query'];
|
||||||
|
$_SERVER['REMOTE_ADDR'] = '192.0.2.10';
|
||||||
|
if (isset($request['content_type'])) {
|
||||||
|
$_SERVER['HTTP_CONTENT_TYPE'] = $request['content_type'];
|
||||||
|
}
|
||||||
|
foreach ($request['server'] ?? [] as $key => $value) {
|
||||||
|
$_SERVER[$key] = $value;
|
||||||
|
}
|
||||||
|
$_GET = ['query' => $request['query']] + ($request['get'] ?? []);
|
||||||
|
$_POST = $request['post'] ?? [];
|
||||||
|
$GLOBALS['__harness_session'] = $request['session'] ?? [];
|
||||||
|
$GLOBALS['__harness_globals'] = $request['globals'] ?? [];
|
||||||
|
Calls::$script = $request['script'] ?? [];
|
||||||
|
InputStream::$body = (string)($request['body'] ?? '');
|
||||||
|
stream_wrapper_unregister('php');
|
||||||
|
stream_wrapper_register('php', InputStream::class);
|
||||||
|
|
||||||
|
register_shutdown_function(function () use ($request): void {
|
||||||
|
$output = '';
|
||||||
|
while (ob_get_level() > 0) {
|
||||||
|
$output = ob_get_clean() . $output;
|
||||||
|
}
|
||||||
|
$error = error_get_last();
|
||||||
|
$fatal = ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR], true));
|
||||||
|
stream_wrapper_restore('php');
|
||||||
|
file_put_contents($request['result'], json_encode([
|
||||||
|
'status' => http_response_code() ?: 200,
|
||||||
|
'body' => $output,
|
||||||
|
'calls' => Calls::$calls,
|
||||||
|
'session_return' => $_SESSION['return'] ?? null,
|
||||||
|
'error' => $fatal ? $error['message'] . ' in ' . $error['file'] . ':' . $error['line'] : null,
|
||||||
|
], JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE));
|
||||||
|
});
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
require $jsonApi;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Stand-in for data/web/inc/lib/ssp.class.php in the API harness: the class SSP is a recorder (runner.php autoloader).
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Stand-in for data/web/inc/prerequisites.inc.php in the API harness (runner.php): the session of the request
|
||||||
|
// instead of the API key login, recorders for the database and Redis, the globals the test sets.
|
||||||
|
|
||||||
|
$pdo = new \ApiHarness\Recorder('pdo');
|
||||||
|
$redis = new \ApiHarness\Recorder('redis');
|
||||||
|
$_SESSION = $GLOBALS['__harness_session'] ?? [];
|
||||||
|
foreach ($GLOBALS['__harness_globals'] ?? [] as $name => $value) {
|
||||||
|
$GLOBALS[$name] = $value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Mailboxes;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** App passwords of mailboxes */
|
||||||
|
class AppPasswordRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testAddAppPassword(): void
|
||||||
|
{
|
||||||
|
$body = ['username' => 'john@example.org', 'app_name' => 'Thunderbird', 'app_passwd' => 'eiQu4ahz8iePh', 'app_passwd2' => 'eiQu4ahz8iePh', 'protocols' => ['imap_access']];
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('add/app-passwd', $body), 'app_passwd', ['add', $body]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testListsOfAMailboxAndOfTheSessionUser(): void
|
||||||
|
{
|
||||||
|
$script = [
|
||||||
|
self::answer('app_passwd', ['get'], [['id' => 23, 'name' => 'Thunderbird'], ['id' => 24, 'name' => 'Gone']]),
|
||||||
|
self::answer('app_passwd', ['details', 23], ['id' => 23, 'mailbox' => 'john@example.org']),
|
||||||
|
self::answer('app_passwd', ['details', 24], false),
|
||||||
|
];
|
||||||
|
|
||||||
|
$mailbox = $this->get('get/app-passwd/all/john@example.org', ['script' => $script]);
|
||||||
|
$this->assertSame([['get', ['username' => 'john@example.org']], ['details', 23], ['details', 24]], $mailbox->callsTo('app_passwd'));
|
||||||
|
$this->assertSame([['id' => 23, 'mailbox' => 'john@example.org']], $mailbox->json());
|
||||||
|
|
||||||
|
$own = $this->get('get/app-passwd/all', ['session' => self::USER, 'script' => $script]);
|
||||||
|
$this->assertSame(['get'], $own->callsTo('app_passwd')[0]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/app-passwd/all')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleAppPasswordFails(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/app-passwd/23', ['allow_error' => true]);
|
||||||
|
|
||||||
|
$this->assertStringContainsString('Cannot access offset of type string on string', (string)$result->error);
|
||||||
|
$this->assertNotCalled($result, 'app_passwd');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditAndDeleteAppPasswords(): void
|
||||||
|
{
|
||||||
|
$edit = $this->post('edit/app-passwd', ['items' => [23], 'attr' => ['username' => 'john@example.org', 'active' => '0']]);
|
||||||
|
$this->assertCalled($edit, 'app_passwd', ['edit', ['id' => [23], 'username' => 'john@example.org', 'active' => '0']]);
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('delete/app-passwd', [23, 24]), 'app_passwd', ['delete', ['id' => [23, 24]]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Mailboxes;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Sieve filters and the spam allow/deny lists of mailboxes */
|
||||||
|
class FilterAndPolicyRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testAddEditDeleteFilters(): void
|
||||||
|
{
|
||||||
|
$body = ['username' => 'john@example.org', 'script_desc' => 'Move newsletters', 'script_data' => 'keep;', 'filter_type' => 'prefilter', 'active' => 1];
|
||||||
|
$this->assertCalled($this->post('add/filter', $body), 'mailbox', ['add', 'filter', $body]);
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('edit/filter', ['items' => [7], 'attr' => ['active' => 0]]), 'mailbox', ['edit', 'filter', ['id' => [7], 'active' => 0]]);
|
||||||
|
$this->assertCalled($this->post('delete/filter', [7]), 'mailbox', ['delete', 'filter', ['id' => [7]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllFiltersOfAllMailboxes(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/filters/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.org'], ['john@example.org', 'jane@example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'filters', 'john@example.org'], [7, 8]),
|
||||||
|
self::answer('mailbox', ['get', 'filter_details', 7], ['id' => 7]),
|
||||||
|
self::answer('mailbox', ['get', 'filter_details', 8], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'filters', 'jane@example.org']);
|
||||||
|
$this->assertSame([['id' => 7]], $result->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/filters/all')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFiltersOfAMailbox(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/filters/john@example.org', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'filters'], [7]),
|
||||||
|
self::answer('mailbox', ['get', 'filter_details', 7], ['id' => 7, 'username' => 'john@example.org']),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'filters', 'john@example.org']);
|
||||||
|
$this->assertSame([['id' => 7, 'username' => 'john@example.org']], $result->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/filters/jane@example.org')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testActiveUserSieveIsAListWithOneScript(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/active-user-sieve/john@example.org', ['script' => [self::answer('mailbox', ['get', 'active_user_sieve'], "require \"fileinto\";\nkeep;")]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'active_user_sieve', 'john@example.org']], $result->callsTo('mailbox'));
|
||||||
|
$this->assertSame(["require \"fileinto\";\nkeep;"], $result->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/active-user-sieve/john@example.org', ['script' => [self::answer('mailbox', [], false)]])->body);
|
||||||
|
|
||||||
|
$none = $this->get('get/active-user-sieve');
|
||||||
|
$this->assertNotCalled($none, 'mailbox');
|
||||||
|
$this->assertSame('{}', $none->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddAndDeleteMailboxPolicies(): void
|
||||||
|
{
|
||||||
|
$body = ['username' => 'john@example.org', 'object_list' => 'bl', 'object_from' => '*@spam.example.com'];
|
||||||
|
$this->assertCalled($this->post('add/mailbox-policy', $body), 'policy', ['add', 'mailbox', $body]);
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('delete/mailbox-policy', [13]), 'policy', ['delete', 'mailbox', ['prefid' => [13]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllowAndDenyListsArePartsOfThePolicy(): void
|
||||||
|
{
|
||||||
|
$policy = [
|
||||||
|
'whitelist' => [['object' => 'john@example.org', 'value' => '*@partner.example.com', 'prefid' => 12]],
|
||||||
|
'blacklist' => [['object' => 'example.org', 'value' => '*@spam.example.com', 'prefid' => 13]],
|
||||||
|
];
|
||||||
|
$script = [self::answer('policy', ['get', 'mailbox'], $policy)];
|
||||||
|
|
||||||
|
$allow = $this->get('get/policy_wl_mailbox/john@example.org', ['script' => $script]);
|
||||||
|
$this->assertSame([['get', 'mailbox', 'john@example.org']], $allow->callsTo('policy'));
|
||||||
|
$this->assertSame($policy['whitelist'], $allow->json());
|
||||||
|
|
||||||
|
$this->assertSame($policy['blacklist'], $this->get('get/policy_bl_mailbox/john@example.org', ['script' => $script])->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/policy_bl_mailbox/nobody@example.org')->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Mailboxes;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Mailboxes, their templates, tags, custom attributes, renaming, ACLs, logins and resets */
|
||||||
|
class MailboxRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testAddMailbox(): void
|
||||||
|
{
|
||||||
|
$body = ['local_part' => 'john.doe', 'domain' => 'example.org', 'password' => 'Moo2Shoh5aiXu', 'password2' => 'Moo2Shoh5aiXu', 'tags' => ['sales']];
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('add/mailbox', $body), 'mailbox', ['add', 'mailbox', $body]);
|
||||||
|
// any other object is a mailbox as well
|
||||||
|
$this->assertCalled($this->post('add/mailbox/something', $body), 'mailbox', ['add', 'mailbox', $body]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddMailboxTemplate(): void
|
||||||
|
{
|
||||||
|
$body = ['template' => 'Sales', 'quota' => 2048, 'acl' => ['spam_alias']];
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('add/mailbox/template', $body), 'mailbox', ['add', 'mailbox_templates', $body]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllMailboxesWalksAllDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/mailbox/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com']),
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.org'], ['john@example.org', 'gone@example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.com'], ['jane@example.com']),
|
||||||
|
self::answer('mailbox', ['get', 'mailbox_details', 'john@example.org'], ['username' => 'john@example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'mailbox_details', 'gone@example.org'], false),
|
||||||
|
self::answer('mailbox', ['get', 'mailbox_details', 'jane@example.com'], ['username' => 'jane@example.com']),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'mailboxes', 'example.org', null]);
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'mailbox_details', 'john@example.org', 'all']);
|
||||||
|
$this->assertSame([['username' => 'john@example.org'], ['username' => 'jane@example.com']], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReducedDetailsOfGivenDomainsWithTags(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/mailbox/reduced/example.org,example.com', [
|
||||||
|
'query' => ['tags' => 'sales,support'],
|
||||||
|
'script' => [
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.org'], ['john@example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'mailbox_details'], ['username' => 'john@example.org']),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertNotContains(['get', 'domains'], $result->callsTo('mailbox'));
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'mailboxes', 'example.org', ['sales', 'support']]);
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'mailboxes', 'example.com', ['sales', 'support']]);
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'mailbox_details', 'john@example.org', 'reduced']);
|
||||||
|
$this->assertSame([['username' => 'john@example.org']], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMailboxListsWithoutResults(): void
|
||||||
|
{
|
||||||
|
$this->assertSame('{}', $this->get('get/mailbox/all')->body);
|
||||||
|
$this->assertSame('{}', $this->get('get/mailbox/all/example.org')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOneMailbox(): void
|
||||||
|
{
|
||||||
|
$found = $this->get('get/mailbox/john@example.org', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'mailbox_details', 'john@example.org'], ['username' => 'john@example.org', 'quota' => 0]),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($found, 'mailbox', ['get', 'mailbox_details', 'john@example.org']);
|
||||||
|
$this->assertSame(['username' => 'john@example.org', 'quota' => 0], $found->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/mailbox/nobody@example.org')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMailboxWithTagsSearchesByAddressPart(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/mailbox/example.org', [
|
||||||
|
'query' => ['tags' => 'sales'],
|
||||||
|
'script' => [
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.org', ['sales']], ['john@example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'mailbox_details', 'john@example.org'], ['username' => 'john@example.org']),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'mailbox_details', 'john@example.org']);
|
||||||
|
$this->assertSame([['username' => 'john@example.org']], $result->json());
|
||||||
|
$this->assertSame('[]', $this->get('get/mailbox/example.org', ['query' => ['tags' => 'sales']])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMailboxTemplates(): void
|
||||||
|
{
|
||||||
|
$templates = [['id' => 2, 'template' => 'Default', 'type' => 'mailbox', 'attributes' => ['quota' => 0]]];
|
||||||
|
$all = $this->get('get/mailbox/template/all', ['script' => [self::answer('mailbox', ['get', 'mailbox_templates'], $templates)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'mailbox_templates']], $all->callsTo('mailbox'));
|
||||||
|
$this->assertSame($templates, $all->json());
|
||||||
|
$this->assertCalled($this->get('get/mailbox/template/2'), 'mailbox', ['get', 'mailbox_templates', '2']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditMailbox(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/mailbox', ['items' => 'john@example.org', 'attr' => ['quota' => 5120, 'sender_acl' => ['@example.com']]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'mailbox', ['username' => ['john@example.org'], 'quota' => 5120, 'sender_acl' => ['@example.com']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditMailboxTemplatesAndCustomAttributes(): void
|
||||||
|
{
|
||||||
|
$template = $this->post('edit/mailbox/template', ['items' => [3], 'attr' => ['template' => 'Sales', 'quarantine_category' => 'reject']]);
|
||||||
|
$this->assertCalled($template, 'mailbox', ['edit', 'mailbox_templates', ['ids' => [3], 'template' => 'Sales', 'quarantine_category' => 'reject']]);
|
||||||
|
|
||||||
|
$custom = $this->post('edit/mailbox/custom-attribute', ['items' => ['john@example.org'], 'attr' => ['attribute' => ['department'], 'value' => ['Sales']]]);
|
||||||
|
$this->assertCalled($custom, 'mailbox', ['edit', 'mailbox_custom_attribute', ['mailboxes' => ['john@example.org'], 'attribute' => ['department'], 'value' => ['Sales']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameMailboxTakesTheNamesFromAttr(): void
|
||||||
|
{
|
||||||
|
$attr = ['domain' => 'example.org', 'old_local_part' => 'john.doe', 'new_local_part' => 'john', 'create_alias' => 1];
|
||||||
|
|
||||||
|
$result = $this->post('edit/rename-mbox', ['items' => [], 'attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'mailbox_rename', ['mailbox' => []] + $attr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditUserAcl(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/user-acl', ['items' => ['john@example.org'], 'attr' => ['user_acl' => ['spam_alias', 'app_passwds']]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'acl', ['edit', 'user', ['username' => ['john@example.org'], 'user_acl' => ['spam_alias', 'app_passwds']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteMailboxesTagsAndTemplates(): void
|
||||||
|
{
|
||||||
|
$this->assertCalled($this->post('delete/mailbox', ['john@example.org']), 'mailbox', ['delete', 'mailbox', ['username' => ['john@example.org']]]);
|
||||||
|
|
||||||
|
$tags = $this->post('delete/mailbox/tag/john@example.org', ['sales', 'trainee']);
|
||||||
|
$this->assertCalled($tags, 'mailbox', ['delete', 'tags_mailbox', ['tags' => ['sales', 'trainee'], 'username' => 'john@example.org']]);
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('delete/mailbox/template', [3]), 'mailbox', ['delete', 'mailbox_templates', ['ids' => [3]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResetEasCacheAndSogoProfile(): void
|
||||||
|
{
|
||||||
|
$this->assertCalled($this->post('delete/eas_cache', ['john@example.org']), 'mailbox', ['delete', 'eas_cache', ['username' => ['john@example.org']]]);
|
||||||
|
$this->assertCalled($this->post('delete/sogo_profile', ['john@example.org']), 'mailbox', ['delete', 'sogo_profile', ['username' => ['john@example.org']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLastLogin(): void
|
||||||
|
{
|
||||||
|
$logins = ['ui' => false, 'sasl' => [['real_rip' => '198.51.100.23', 'service' => 'imap']]];
|
||||||
|
$default = $this->get('get/last-login/john@example.org', ['script' => [self::answer('last_login', [], $logins)]]);
|
||||||
|
$this->assertSame([['get', 'john@example.org']], $default->callsTo('last_login'));
|
||||||
|
$this->assertSame($logins, $default->json());
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'john@example.org', 30]], $this->get('get/last-login/john@example.org/30')->callsTo('last_login'));
|
||||||
|
$this->assertSame([['get', 'john@example.org']], $this->get('get/last-login/john@example.org/0')->callsTo('last_login'));
|
||||||
|
|
||||||
|
$none = $this->get('get/last-login');
|
||||||
|
$this->assertSame('', $none->body);
|
||||||
|
$this->assertNotCalled($none, 'last_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResetLastLoginIsAReadRoute(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/reset-last-login/john@example.org', ['session' => self::API_RO, 'script' => [self::answer('last_login', ['reset'], true)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['reset', 'john@example.org']], $result->callsTo('last_login'));
|
||||||
|
$this->assertSame('true', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBccDestinationOptions(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/bcc-destination-options', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'alias_domains'], ['example.net']),
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.org'], ['john@example.org']),
|
||||||
|
self::answer('user_get_alias_details', ['john@example.org'], [
|
||||||
|
'direct_aliases' => ['sales@example.org' => ['public_comment' => ''], 'john@other.example' => ['public_comment' => '']],
|
||||||
|
'shared_aliases' => ['team@example.org' => ['public_comment' => '']],
|
||||||
|
]),
|
||||||
|
self::answer('hasDomainAccess', ['API', 'admin', 'example.org'], true),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([
|
||||||
|
'domains' => ['example.org'],
|
||||||
|
'mailboxes' => ['john@example.org' => ['john@example.org', 'sales@example.org', 'team@example.org']],
|
||||||
|
'alias_domains' => ['example.net'],
|
||||||
|
], $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Mailboxes;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiResult;
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** search/mailbox: the DataTables server-side search of the mailbox table */
|
||||||
|
class MailboxSearchTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const REQUEST = [
|
||||||
|
'draw' => 1,
|
||||||
|
'start' => 0,
|
||||||
|
'length' => 25,
|
||||||
|
'search' => ['value' => 'john'],
|
||||||
|
'order' => [['column' => 2, 'dir' => 'asc']],
|
||||||
|
'columns' => [['data' => 'username', 'searchable' => true, 'orderable' => true]],
|
||||||
|
];
|
||||||
|
|
||||||
|
private const KIND_FILTER = "(`m`.`kind` = '' OR `m`.`kind` = NULL)";
|
||||||
|
|
||||||
|
public function testAdminsSearchAllMailboxes(): void
|
||||||
|
{
|
||||||
|
$call = $this->searchCall($this->post('search/mailbox', self::REQUEST));
|
||||||
|
|
||||||
|
$this->assertSame([self::REQUEST, '@pdo', ['mailbox', 'm'], 'username'], array_slice($call, 0, 4));
|
||||||
|
$this->assertSame([null, self::KIND_FILTER], array_slice($call, 5));
|
||||||
|
$this->assertSame([2 => 'username', 3 => 'quota', 4 => 'last_mail_login', 5 => 'last_pw_change', 6 => 'in_use', 7 => 'name',
|
||||||
|
20 => 'messages', 23 => 'tags', 24 => 'active'], array_column($call[4], 'db', 'dt'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadOnlyKeysMaySearch(): void
|
||||||
|
{
|
||||||
|
$this->searchCall($this->post('search/mailbox', self::REQUEST, ['session' => self::API_RO]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDomainAdminsSearchTheirDomains(): void
|
||||||
|
{
|
||||||
|
$call = $this->searchCall($this->post('search/mailbox', self::REQUEST, ['session' => self::DOMAIN_ADMIN]));
|
||||||
|
|
||||||
|
$this->assertSame('INNER JOIN domain_admins as da ON da.domain = m.domain', $call[5]);
|
||||||
|
$this->assertSame([
|
||||||
|
'condition' => self::KIND_FILTER . ' AND `da`.`active` = 1 AND `da`.`username` = :username',
|
||||||
|
'bindings' => ['username' => 'domainadmin'],
|
||||||
|
], $call[6]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOthersAreRefused(): void
|
||||||
|
{
|
||||||
|
foreach ([self::USER, self::NO_SESSION] as $session) {
|
||||||
|
$result = $this->post('search/mailbox', self::REQUEST, ['session' => $session]);
|
||||||
|
$this->assertApiError($result, 403, 'Insufficient permissions');
|
||||||
|
$this->assertNotCalled($result, 'SSP::complex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRowsBecomeMailboxDetails(): void
|
||||||
|
{
|
||||||
|
$page = ['draw' => 1, 'recordsTotal' => 2, 'recordsFiltered' => 2, 'data' => [
|
||||||
|
[2 => 'john@example.org', 3 => 0, 4 => '', 7 => 'John'],
|
||||||
|
[2 => 'gone@example.org', 3 => 0, 4 => '', 7 => 'Gone'],
|
||||||
|
]];
|
||||||
|
$result = $this->post('search/mailbox', self::REQUEST, ['script' => [
|
||||||
|
self::answer('SSP::complex', [], $page),
|
||||||
|
self::answer('mailbox', ['get', 'mailbox_details', 'john@example.org'], ['username' => 'john@example.org']),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'mailbox_details', 'john@example.org'], ['get', 'mailbox_details', 'gone@example.org']], $result->callsTo('mailbox'));
|
||||||
|
$this->assertSame(['draw' => 1, 'recordsTotal' => 2, 'recordsFiltered' => 2, 'data' => [['username' => 'john@example.org']]], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyPageIsPassedOn(): void
|
||||||
|
{
|
||||||
|
$page = ['draw' => 4, 'recordsTotal' => 0, 'recordsFiltered' => 0, 'data' => []];
|
||||||
|
|
||||||
|
$result = $this->post('search/mailbox', self::REQUEST, ['script' => [self::answer('SSP::complex', [], $page)]]);
|
||||||
|
|
||||||
|
$this->assertNotCalled($result, 'mailbox');
|
||||||
|
$this->assertSame($page, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function searchCall(ApiResult $result): array
|
||||||
|
{
|
||||||
|
$calls = $result->callsTo('SSP::complex');
|
||||||
|
$this->assertCount(1, $calls, $result->body);
|
||||||
|
$this->assertNotCalled($result, 'SSP::simple');
|
||||||
|
return $calls[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Mailboxes;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Settings a mailbox user changes on its page: time-limited aliases, Pushover, tagged mail, TLS, quarantine, spam score */
|
||||||
|
class MailboxSettingsRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testTimeLimitedAliases(): void
|
||||||
|
{
|
||||||
|
$body = ['username' => 'john@example.org', 'domain' => 'example.org', 'validity' => 720];
|
||||||
|
$this->assertCalled($this->post('add/time_limited_alias', $body), 'mailbox', ['add', 'time_limited_alias', $body]);
|
||||||
|
|
||||||
|
$edit = $this->post('edit/time_limited_alias', ['items' => ['kahd.osuwe@example.org'], 'attr' => ['validity' => 24]]);
|
||||||
|
$this->assertCalled($edit, 'mailbox', ['edit', 'time_limited_alias', ['address' => ['kahd.osuwe@example.org'], 'validity' => 24]]);
|
||||||
|
|
||||||
|
$delete = $this->post('delete/time_limited_alias', ['kahd.osuwe@example.org']);
|
||||||
|
$this->assertCalled($delete, 'mailbox', ['delete', 'time_limited_alias', ['address' => ['kahd.osuwe@example.org']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testListTimeLimitedAliases(): void
|
||||||
|
{
|
||||||
|
$aliases = [['address' => 'kahd.osuwe@example.org', 'goto' => 'john@example.org', 'permanent' => 0]];
|
||||||
|
|
||||||
|
$result = $this->get('get/time_limited_aliases/john@example.org', ['script' => [self::answer('mailbox', ['get', 'time_limited_aliases'], $aliases)]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'time_limited_aliases', 'john@example.org']], $result->callsTo('mailbox'));
|
||||||
|
$this->assertSame($aliases, $result->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/time_limited_aliases/jane@example.org')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPushover(): void
|
||||||
|
{
|
||||||
|
$attr = ['key' => str_repeat('a', 30), 'token' => str_repeat('b', 30), 'active' => 1];
|
||||||
|
$this->assertCalled($this->post('edit/pushover', ['items' => ['john@example.org'], 'attr' => $attr]), 'pushover', ['edit', ['username' => ['john@example.org']] + $attr]);
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('edit/pushover-test', ['items' => ['john@example.org'], 'attr' => []]), 'pushover', ['test', ['username' => ['john@example.org']]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMailboxUserSettingsGetTheMailboxesAsUsername(): void
|
||||||
|
{
|
||||||
|
$routes = [
|
||||||
|
'delimiter_action' => ['tagged_mail_handler' => 'subfolder'],
|
||||||
|
'tls_policy' => ['tls_enforce_in' => 1],
|
||||||
|
'quarantine_notification' => ['quarantine_notification' => 'daily'],
|
||||||
|
'quarantine_category' => ['quarantine_category' => 'all'],
|
||||||
|
'spam_score' => ['spam_score' => '6,12'],
|
||||||
|
];
|
||||||
|
foreach ($routes as $type => $attr) {
|
||||||
|
$route = 'edit/' . ($type === 'spam_score' ? 'spam-score' : $type);
|
||||||
|
$result = $this->post($route, ['items' => 'john@example.org', 'attr' => $attr]);
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', $type, ['username' => ['john@example.org']] + $attr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheUiSendsItsOwnAddressAsItem(): void
|
||||||
|
{
|
||||||
|
$result = $this->postForm('edit/delimiter_action', ['items' => '["user@example.org"]', 'attr' => '{"tagged_mail_handler":"subject","csrf_token":"t"}'], ['session' => self::USER]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'delimiter_action', ['username' => ['user@example.org'], 'tagged_mail_handler' => 'subject']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSpamScoreWithoutSpaces(): void
|
||||||
|
{
|
||||||
|
$mailbox = $this->get('get/spam-score/john@example.org', ['script' => [self::answer('mailbox', ['get', 'spam_score'], '6, 12')]]);
|
||||||
|
$this->assertSame([['get', 'spam_score', 'john@example.org']], $mailbox->callsTo('mailbox'));
|
||||||
|
$this->assertSame(['score' => '6,12'], $mailbox->json());
|
||||||
|
|
||||||
|
$default = $this->get('get/spam-score', ['script' => [self::answer('mailbox', ['get', 'spam_score'], '8, 15')]]);
|
||||||
|
$this->assertSame([['get', 'spam_score', null]], $default->callsTo('mailbox'));
|
||||||
|
$this->assertSame(['score' => '8,15'], $default->json());
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/spam-score/jane@example.org', ['script' => [self::answer('mailbox', [], false)]])->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Mailboxes;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Rate limits of mailboxes and the reset of rate limit counters */
|
||||||
|
class RatelimitRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testRatelimitsOfAllMailboxesAddTheMailbox(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/rl-mbox/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.org'], ['john@example.org', 'jane@example.org']),
|
||||||
|
self::answer('ratelimit', ['get', 'mailbox', 'john@example.org'], ['value' => '100', 'frame' => 'h']),
|
||||||
|
self::answer('ratelimit', ['get', 'mailbox', 'jane@example.org'], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([['value' => '100', 'frame' => 'h', 'mailbox' => 'john@example.org']], $result->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/rl-mbox/all')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRatelimitOfAMailbox(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/rl-mbox/john@example.org', ['script' => [self::answer('ratelimit', [], ['value' => '100', 'frame' => 'h'])]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'mailbox', 'john@example.org']], $result->callsTo('ratelimit'));
|
||||||
|
$this->assertSame(['value' => '100', 'frame' => 'h'], $result->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/rl-mbox/jane@example.org')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditRatelimit(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/rl-mbox', ['items' => ['john@example.org'], 'attr' => ['rl_value' => 100, 'rl_frame' => 'h']]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'ratelimit', ['edit', 'mailbox', ['object' => ['john@example.org'], 'rl_value' => 100, 'rl_frame' => 'h']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteRatelimitHashAnswersTheRawResult(): void
|
||||||
|
{
|
||||||
|
$deleted = $this->post('delete/rlhash', ['RLabc123'], ['script' => [self::answer('ratelimit', [], true, [self::message('success', 'hash_deleted')])]]);
|
||||||
|
$this->assertSame([['delete', null, 'RLabc123']], $deleted->callsTo('ratelimit'));
|
||||||
|
$this->assertSame('1', $deleted->body);
|
||||||
|
|
||||||
|
$missing = $this->post('delete/rlhash', ['RLabc123'], ['script' => [self::answer('ratelimit', [], false, [self::message('warning', 'hash_not_found')])]]);
|
||||||
|
$this->assertSame('', $missing->body);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', null, 'RLabcRLdef']], $this->post('delete/rlhash', ['RLabc', 'RLdef'])->callsTo('ratelimit'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Mailboxes;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Resources (rooms, equipment, groups) */
|
||||||
|
class ResourceRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testAddResource(): void
|
||||||
|
{
|
||||||
|
$body = ['domain' => 'example.org', 'description' => 'Meeting Room 1', 'kind' => 'location', 'multiple_bookings' => 0, 'active' => 1];
|
||||||
|
|
||||||
|
$this->assertCalled($this->post('add/resource', $body), 'mailbox', ['add', 'resource', $body]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllResourcesWalksAllDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/resource/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com']),
|
||||||
|
self::answer('mailbox', ['get', 'resources', 'example.org'], ['room1@example.org', 'gone@example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'resource_details', 'room1@example.org'], ['name' => 'room1@example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'resource_details', 'gone@example.org'], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'resources', 'example.com']);
|
||||||
|
$this->assertSame([['name' => 'room1@example.org']], $result->json());
|
||||||
|
$this->assertSame('{}', $this->get('get/resource/all')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOneResource(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/resource/room1@example.org', ['script' => [self::answer('mailbox', ['get', 'resource_details'], ['name' => 'room1@example.org'])]]);
|
||||||
|
|
||||||
|
$this->assertSame([['get', 'resource_details', 'room1@example.org']], $result->callsTo('mailbox'));
|
||||||
|
$this->assertSame(['name' => 'room1@example.org'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditAndDeleteResources(): void
|
||||||
|
{
|
||||||
|
$edit = $this->post('edit/resource', ['items' => ['room1@example.org'], 'attr' => ['multiple_bookings' => -1]]);
|
||||||
|
$this->assertCalled($edit, 'mailbox', ['edit', 'resource', ['name' => ['room1@example.org'], 'multiple_bookings' => -1]]);
|
||||||
|
|
||||||
|
$delete = $this->post('delete/resource', ['room1@example.org']);
|
||||||
|
$this->assertCalled($delete, 'mailbox', ['delete', 'resource', ['name' => ['room1@example.org']]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Modules;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes identity-provider, identity-provider-test, identity-provider-sync and identity-link */
|
||||||
|
class IdentityRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const API = 'Mailcow\Identity\IdentityModule->api->';
|
||||||
|
/** The Identity Provider settings that prerequisites.inc.php loads into $iam_settings */
|
||||||
|
private const IAM_SETTINGS = ['iam_settings' => [
|
||||||
|
'authsource' => 'keycloak',
|
||||||
|
'server_url' => 'https://auth.example.org',
|
||||||
|
'realm' => 'mailcow',
|
||||||
|
'client_id' => 'mailcow',
|
||||||
|
'client_secret' => 'client-secret-value',
|
||||||
|
'bindpass' => 'bind-password',
|
||||||
|
'access_token' => 'cached-token',
|
||||||
|
'redirect_url_extra' => ['https://webmail.example.org'],
|
||||||
|
'login_provisioning' => true,
|
||||||
|
]];
|
||||||
|
private const SETTINGS = [
|
||||||
|
'authsource' => 'keycloak',
|
||||||
|
'server_url' => 'https://auth.example.org',
|
||||||
|
'realm' => 'mailcow',
|
||||||
|
'client_id' => 'mailcow',
|
||||||
|
'client_secret' => 'client-secret-value',
|
||||||
|
'bindpass' => 'bind-password',
|
||||||
|
'access_token' => 'cached-token',
|
||||||
|
'redirect_url_extra' => ['https://webmail.example.org'],
|
||||||
|
'login_provisioning' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function testSettingsForReadWriteKeys(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/identity-provider', ['globals' => self::IAM_SETTINGS]);
|
||||||
|
|
||||||
|
$this->assertSame(self::SETTINGS, $result->json());
|
||||||
|
$this->assertNotCalled($result, self::API . 'getStatus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadOnlyKeysGetNoSecrets(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/identity-provider', ['globals' => self::IAM_SETTINGS, 'session' => self::API_RO]);
|
||||||
|
|
||||||
|
$this->assertSame(array_replace(self::SETTINGS, ['client_secret' => '*', 'bindpass' => '*', 'access_token' => '*']), $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSettingsOnlyForAdmins(): void
|
||||||
|
{
|
||||||
|
foreach ([self::DOMAIN_ADMIN, self::USER] as $session) {
|
||||||
|
$this->assertSame('{}', $this->get('get/identity-provider', ['globals' => self::IAM_SETTINGS, 'session' => $session])->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOtherObjectsAnswerTheSettingsToo(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/identity-provider/anything', ['globals' => self::IAM_SETTINGS]);
|
||||||
|
|
||||||
|
$this->assertSame(self::SETTINGS, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSyncStatus(): void
|
||||||
|
{
|
||||||
|
$status = ['provider' => 'keycloak', 'can_sync' => true, 'sync_enabled' => false, 'counts' => ['accounts' => 2], 'runs' => []];
|
||||||
|
|
||||||
|
$result = $this->get('get/identity-provider/sync-status', ['session' => self::API_RO, 'script' => [self::answer(self::API . 'getStatus', [], $status)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getStatus', []);
|
||||||
|
$this->assertSame($status, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLink(): void
|
||||||
|
{
|
||||||
|
$link = ['provider' => 'keycloak', 'external_id' => '8d5da28d-7541-428b-8356-a9704f73e7f4', 'idp_disabled' => 0, 'missing_since' => null];
|
||||||
|
|
||||||
|
$result = $this->get('get/identity-link/alice@example.org', ['script' => [self::answer(self::API . 'getLink', ['alice@example.org'], $link)]]);
|
||||||
|
$this->assertCalled($result, self::API . 'getLink', ['alice@example.org']);
|
||||||
|
$this->assertSame($link, $result->json());
|
||||||
|
|
||||||
|
$none = $this->get('get/identity-link');
|
||||||
|
$this->assertCalled($none, self::API . 'getLink', ['']);
|
||||||
|
$this->assertSame('{}', $none->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditSettings(): void
|
||||||
|
{
|
||||||
|
$attr = ['authsource' => 'ldap', 'host' => 'ldap.example.org', 'basedn' => 'dc=example,dc=org', 'binddn' => 'cn=mailcow', 'bindpass' => 'secret'];
|
||||||
|
$messages = [self::message('success', ['object_modified', ''], ['identity_provider', 'edit', ['bindpass' => '*']])];
|
||||||
|
|
||||||
|
$result = $this->post('edit/identity-provider', ['items' => ['identity-provider'], 'attr' => $attr],
|
||||||
|
['script' => [self::answer('identity_provider', ['edit'], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'identity_provider', ['edit', $attr]);
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConnectionTestAnswersWithoutDetails(): void
|
||||||
|
{
|
||||||
|
$attr = ['authsource' => 'keycloak', 'server_url' => 'https://auth.example.org', 'realm' => 'mailcow', 'client_id' => 'mailcow', 'client_secret' => 'secret'];
|
||||||
|
|
||||||
|
$ok = $this->post('edit/identity-provider-test', ['attr' => $attr], ['script' => [self::answer('identity_provider', ['test'], true)]]);
|
||||||
|
$this->assertCalled($ok, 'identity_provider', ['test', $attr]);
|
||||||
|
$this->assertSame(['type' => 'success', 'msg' => 'Task completed'], $ok->json());
|
||||||
|
|
||||||
|
$failed = $this->post('edit/identity-provider-test', ['attr' => $attr], ['script' => [self::answer('identity_provider', ['test'], false)]]);
|
||||||
|
$this->assertSame(['type' => 'error', 'msg' => 'Cannot edit item'], $failed->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRequestSync(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/identity-provider-sync', ['items' => ['sync'], 'attr' => ['mode' => 'preview']]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'requestRun', [['mode' => 'preview']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteSettingsIgnoresTheBody(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/identity-provider', ['anything']);
|
||||||
|
|
||||||
|
$this->assertSame([['delete']], $result->callsTo('identity_provider'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteLinks(): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('success', ['iam_link_removed', 'alice@example.org'], ['identity_provider', 'delete', 'link', ['alice@example.org']])];
|
||||||
|
|
||||||
|
$result = $this->post('delete/identity-link', ['alice@example.org'], ['script' => [self::answer(self::API . 'unlink', [], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'unlink', [['alice@example.org']]);
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Modules;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes mailbox-group: json_api.php hands them to the MailboxGroup module and passes its answers through */
|
||||||
|
class MailboxGroupRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const API = 'Mailcow\MailboxGroup\MailboxGroupModule->api->';
|
||||||
|
|
||||||
|
public function testAdd(): void
|
||||||
|
{
|
||||||
|
$attr = ['domain' => 'example.org', 'name' => 'Sales', 'members' => ['alice@example.org'], 'sender_allowed' => 1];
|
||||||
|
$messages = [
|
||||||
|
self::message('success', ['group_added', 'Sales'], ['mailbox_group', 'add', 'group', $attr]),
|
||||||
|
self::message('warning', ['group_imap_grant_failed', 'shared@example.org'], ['mailbox_group', 'add', 'group', $attr]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this->post('add/mailbox-group', $attr, ['script' => [self::answer(self::API . 'addGroup', [], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'addGroup', [$attr]);
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAll(): void
|
||||||
|
{
|
||||||
|
$groups = [['id' => 7, 'domain' => 'example.org', 'name' => 'Sales', 'can_edit' => true]];
|
||||||
|
|
||||||
|
$result = $this->get('get/mailbox-group/all', ['script' => [self::answer(self::API . 'getGroups', [], $groups)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getGroups', [null]);
|
||||||
|
$this->assertSame($groups, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllOfDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/mailbox-group/all/example.org,example.com');
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getGroups', ['example.org,example.com']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoGroupsAnswerAnEmptyObject(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/mailbox-group/all', ['script' => [self::answer(self::API . 'getGroups', [], [])]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMemberships(): void
|
||||||
|
{
|
||||||
|
$memberships = ['groups' => [['id' => 7, 'name' => 'Sales', 'source' => 'manual']], 'template' => 'Staff', 'send_as' => ['sales@example.org']];
|
||||||
|
|
||||||
|
$result = $this->get('get/mailbox-group/member/alice@example.org', ['script' => [self::answer(self::API . 'getMemberships', [], $memberships)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getMemberships', ['alice@example.org']);
|
||||||
|
$this->assertSame($memberships, $result->json());
|
||||||
|
$this->assertNotCalled($result, self::API . 'getGroup');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMembershipsWithoutMailbox(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/mailbox-group/member');
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getMemberships', ['']);
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOne(): void
|
||||||
|
{
|
||||||
|
$group = ['id' => 7, 'domain' => 'example.org', 'name' => 'Sales'];
|
||||||
|
|
||||||
|
$result = $this->get('get/mailbox-group/7', ['script' => [self::answer(self::API . 'getGroup', ['7'], $group)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getGroup', ['7']);
|
||||||
|
$this->assertSame($group, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEdit(): void
|
||||||
|
{
|
||||||
|
$attr = ['add_members' => ['carol@example.org'], 'remove_members' => 'bob@example.org'];
|
||||||
|
|
||||||
|
$result = $this->post('edit/mailbox-group', ['items' => [7, '8'], 'attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'editGroups', [[7, '8'], $attr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditWithASingleIdOrWithoutItems(): void
|
||||||
|
{
|
||||||
|
$single = $this->post('edit/mailbox-group', ['items' => 7, 'attr' => ['active' => 0]]);
|
||||||
|
$this->assertCalled($single, self::API . 'editGroups', [[7], ['active' => 0]]);
|
||||||
|
|
||||||
|
$none = $this->post('edit/mailbox-group', ['attr' => ['id' => [9], 'active' => 0]]);
|
||||||
|
$this->assertCalled($none, self::API . 'editGroups', [[], ['id' => [9], 'active' => 0]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete(): void
|
||||||
|
{
|
||||||
|
$messages = [
|
||||||
|
self::message('success', ['group_removed', 'Sales'], ['mailbox_group', 'delete', 'group', ['id' => [7, 'x']]]),
|
||||||
|
self::message('danger', 'access_denied', ['mailbox_group', 'delete', 'group', ['id' => [7, 'x']]]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this->post('delete/mailbox-group', [7, 'x'], ['script' => [self::answer(self::API . 'deleteGroups', [], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'deleteGroups', [[7, 'x']]);
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Modules;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** Routes syncjob, syncjobs, syncjob_source, syncjob_options and imapsync_settings: handed to the Imapsync module */
|
||||||
|
class SyncJobRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const API = 'Mailcow\Imapsync\ImapsyncModule->api->';
|
||||||
|
|
||||||
|
public function testAddJob(): void
|
||||||
|
{
|
||||||
|
$attr = ['username' => 'alice@example.org', 'source_id' => 3, 'user1' => 'alice@example.com', 'password1' => 'secret', 'mins_interval' => 20];
|
||||||
|
$messages = [self::message('success', ['mailbox_modified', 'alice@example.org'], ['syncjob', 'add', 'job', ['password1' => '*']])];
|
||||||
|
|
||||||
|
$result = $this->post('add/syncjob', $attr, ['script' => [self::answer(self::API . 'addJob', [], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'addJob', [$attr]);
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddSource(): void
|
||||||
|
{
|
||||||
|
$attr = ['name' => 'Old provider', 'host' => 'imap.example.com', 'port' => 993, 'enc' => 'SSL', 'scope' => 'domain', 'domains' => ['example.org']];
|
||||||
|
|
||||||
|
$result = $this->post('add/syncjob_source', $attr);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'addSource', [$attr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllJobsOfAnApiKey(): void
|
||||||
|
{
|
||||||
|
$jobs = [['id' => 12, 'user2' => 'alice@example.org', 'log' => 'done']];
|
||||||
|
|
||||||
|
$result = $this->get('get/syncjobs/all', ['script' => [self::answer(self::API . 'getAllJobs', [], $jobs)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getAllJobs', [null, []]);
|
||||||
|
$this->assertNotCalled($result, 'mailbox');
|
||||||
|
$this->assertSame($jobs, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllJobsWithoutLog(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/syncjobs/all/no_log');
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getAllJobs', ['no_log', []]);
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllJobsOfADomainAdminAreTheJobsOfHisMailboxes(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/syncjobs/all/no_log', ['session' => self::DOMAIN_ADMIN, 'script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com']),
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.org'], ['alice@example.org', 'bob@example.org']),
|
||||||
|
self::answer('mailbox', ['get', 'mailboxes', 'example.com'], false),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getAllJobs', ['no_log', ['alice@example.org', 'bob@example.org']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllJobsOfAUser(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/syncjobs/all', ['session' => self::USER, 'script' => [self::answer('mailbox', ['get', 'domains'], false)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getAllJobs', [null, []]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testJobsOfAMailbox(): void
|
||||||
|
{
|
||||||
|
$withLog = $this->get('get/syncjobs/alice@example.org');
|
||||||
|
$this->assertCalled($withLog, self::API . 'getJobs', ['alice@example.org', null]);
|
||||||
|
$this->assertNotCalled($withLog, self::API . 'getAllJobs');
|
||||||
|
|
||||||
|
$withoutLog = $this->get('get/syncjobs/alice@example.org/no_log');
|
||||||
|
$this->assertCalled($withoutLog, self::API . 'getJobs', ['alice@example.org', 'no_log']);
|
||||||
|
|
||||||
|
$own = $this->get('get/syncjobs');
|
||||||
|
$this->assertCalled($own, self::API . 'getJobs', [null, null]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSources(): void
|
||||||
|
{
|
||||||
|
$sources = [['id' => 3, 'name' => 'Old provider', 'has_client_secret' => false]];
|
||||||
|
|
||||||
|
$all = $this->get('get/syncjob_source/all', ['script' => [self::answer(self::API . 'getSources', [], $sources)]]);
|
||||||
|
$this->assertCalled($all, self::API . 'getSources', []);
|
||||||
|
$this->assertSame($sources, $all->json());
|
||||||
|
|
||||||
|
$one = $this->get('get/syncjob_source/3', ['script' => [self::answer(self::API . 'getSource', ['3'], $sources[0])]]);
|
||||||
|
$this->assertCalled($one, self::API . 'getSource', ['3']);
|
||||||
|
$this->assertSame($sources[0], $one->json());
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/syncjob_source/99')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOptions(): void
|
||||||
|
{
|
||||||
|
$options = ['folder' => true, 'syncinternaldates' => false, 'sslargs1' => '^SSL_verify_mode=[01]$'];
|
||||||
|
|
||||||
|
$result = $this->get('get/syncjob_options', ['session' => self::USER, 'script' => [self::answer(self::API . 'getOptions', [], $options)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'getOptions', []);
|
||||||
|
$this->assertSame($options, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSettings(): void
|
||||||
|
{
|
||||||
|
$settings = ['max_parallel' => 3, 'max_bytes_per_second' => 2048000, 'max_kb_per_second' => 2000];
|
||||||
|
|
||||||
|
$result = $this->get('get/imapsync_settings', ['script' => [self::answer(self::API . 'getSettings', [], $settings)]]);
|
||||||
|
$this->assertCalled($result, self::API . 'getSettings', []);
|
||||||
|
$this->assertSame($settings, $result->json());
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/imapsync_settings', ['session' => self::DOMAIN_ADMIN, 'script' => [self::answer(self::API . 'getSettings', [], [])]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditJobs(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/syncjob', ['items' => [12, '13'], 'attr' => ['active' => 0, 'mins_interval' => 60]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'editJobs', [[12, '13'], ['active' => 0, 'mins_interval' => 60]]);
|
||||||
|
$this->assertNotCalled($result, self::API . 'runNext');
|
||||||
|
|
||||||
|
$other = $this->post('edit/syncjob/anything', ['items' => [12], 'attr' => ['active' => 1]]);
|
||||||
|
$this->assertCalled($other, self::API . 'editJobs', [[12], ['active' => 1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRunNext(): void
|
||||||
|
{
|
||||||
|
$messages = [
|
||||||
|
self::message('danger', ['imapsync_job_running', 13], ['syncjob', 'edit', 'run_next', ['id' => [12, 13]]]),
|
||||||
|
self::message('success', ['imapsync_run_next_done', 1], ['syncjob', 'edit', 'run_next', ['id' => [12, 13]]]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this->post('edit/syncjob/run_next', ['items' => [12, 13], 'attr' => ['ignored' => 1]],
|
||||||
|
['script' => [self::answer(self::API . 'runNext', [], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'runNext', [[12, 13]]);
|
||||||
|
$this->assertNotCalled($result, self::API . 'editJobs');
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRunNextWithoutMovedJobs(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/syncjob/run_next', ['items' => [99]], ['script' => [self::answer(self::API . 'runNext', [], false)]]);
|
||||||
|
|
||||||
|
$this->assertSame(['type' => 'error', 'msg' => 'Cannot edit item'], $result->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditSettings(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/imapsync_settings', ['items' => ['ignored'], 'attr' => ['max_parallel' => 4, 'max_kb_per_second' => 2000]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'editSettings', [['max_parallel' => 4, 'max_kb_per_second' => 2000]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditSources(): void
|
||||||
|
{
|
||||||
|
$attr = ['description' => 'Migration finished', 'scope' => 'user', 'users' => ['alice@example.org']];
|
||||||
|
|
||||||
|
$result = $this->post('edit/syncjob_source', ['items' => [3], 'attr' => $attr]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'editSources', [[3], $attr]);
|
||||||
|
$this->assertNotCalled($result, self::API . 'refreshSourceToken');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRefreshToken(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/syncjob_source/refresh_token', ['items' => [5, 6], 'attr' => []]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'refreshSourceToken', [[5, 6]]);
|
||||||
|
$this->assertNotCalled($result, self::API . 'editSources');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteJobs(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/syncjob', [12, '13']);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'deleteJobs', [[12, '13']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteSources(): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('danger', ['imapsync_source_in_use', 'Old provider'], ['syncjob', 'delete', 'source', ['id' => [3]]])];
|
||||||
|
|
||||||
|
$result = $this->post('delete/syncjob_source', [3], ['script' => [self::answer(self::API . 'deleteSources', [], true, $messages)]]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, self::API . 'deleteSources', [[3]]);
|
||||||
|
$this->assertSame($messages, $result->json());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
/** data/web/api/openapi.yaml against json_api.php: every route documented, every documented route real, the spec consistent */
|
||||||
|
class OpenApiSpecTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const SPEC = __DIR__ . '/../../../data/web/api/openapi.yaml';
|
||||||
|
private const JSON_API = __DIR__ . '/../../../data/web/json_api.php';
|
||||||
|
|
||||||
|
/** Routes of the mailcow UI that an API client cannot use, with the reason */
|
||||||
|
private const UNDOCUMENTED = [
|
||||||
|
// WebAuthn ceremonies of the browser: the challenge lives in the UI session
|
||||||
|
'add/fido2-registration' => 'WebAuthn',
|
||||||
|
'add/webauthn-tfa-registration' => 'WebAuthn',
|
||||||
|
'get/fido2-registration' => 'WebAuthn',
|
||||||
|
'get/fido2-get-args' => 'WebAuthn',
|
||||||
|
'get/webauthn-tfa-registration' => 'WebAuthn',
|
||||||
|
'get/webauthn-tfa-get-args' => 'WebAuthn',
|
||||||
|
// the own account of a UI session (domain admin, mailbox user): an API key gets an empty answer
|
||||||
|
'edit/self' => 'own account',
|
||||||
|
// renames a FIDO2 key of the logged-in account, the API account has none
|
||||||
|
'edit/fido2-fn' => 'own account',
|
||||||
|
];
|
||||||
|
|
||||||
|
private static ?array $spec = null;
|
||||||
|
|
||||||
|
private static function spec(): array
|
||||||
|
{
|
||||||
|
return self::$spec ??= Yaml::parseFile(self::SPEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<array{0: string, 1: string, 2: array}> path, method, operation */
|
||||||
|
private static function operations(): array
|
||||||
|
{
|
||||||
|
$operations = [];
|
||||||
|
foreach (self::spec()['paths'] as $path => $item) {
|
||||||
|
foreach ($item as $method => $operation) {
|
||||||
|
if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'], true)) {
|
||||||
|
$operations[] = [$path, $method, $operation];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEveryRouteIsDocumented(): void
|
||||||
|
{
|
||||||
|
$paths = array_keys(self::spec()['paths']);
|
||||||
|
$missing = [];
|
||||||
|
foreach (RouteMap::fromFile(self::JSON_API) as $route) {
|
||||||
|
$covered = isset(self::UNDOCUMENTED[$route]) || !empty(array_filter($paths, fn(string $path): bool => RouteMap::covers($path, $route)));
|
||||||
|
if (!$covered) {
|
||||||
|
$missing[] = $route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertSame([], $missing, 'routes of json_api.php missing in openapi.yaml');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheUndocumentedRoutesExist(): void
|
||||||
|
{
|
||||||
|
$this->assertSame([], array_values(array_diff(array_keys(self::UNDOCUMENTED), RouteMap::fromFile(self::JSON_API))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEveryDocumentedRouteExists(): void
|
||||||
|
{
|
||||||
|
$unknown = [];
|
||||||
|
foreach (self::operations() as [$path, $method, $operation]) {
|
||||||
|
$route = preg_replace_callback('/\{([^}]+)\}/', fn(array $m): string => self::exampleOf($operation, $m[1]), $path);
|
||||||
|
$action = explode('/', trim(preg_replace('#^/api/v1/#', '', $path), '/'))[0];
|
||||||
|
$body = match ($action) {
|
||||||
|
'edit' => ['items' => [], 'attr' => (object)[]],
|
||||||
|
'delete' => [],
|
||||||
|
default => (object)[],
|
||||||
|
};
|
||||||
|
$result = ($method === 'get') ? $this->get($route, ['allow_error' => true]) : $this->post($route, $body, ['allow_error' => true]);
|
||||||
|
$answer = $result->json();
|
||||||
|
if ($result->status === 404 && is_array($answer) && ($answer['type'] ?? '') === 'error') {
|
||||||
|
$unknown[] = strtoupper($method) . ' ' . $path . ': ' . ($answer['msg'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertSame([], $unknown, 'documented routes that json_api.php does not know');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMethodsFollowTheAction(): void
|
||||||
|
{
|
||||||
|
foreach (self::operations() as [$path, $method]) {
|
||||||
|
$action = explode('/', trim(preg_replace('#^/api/v1/#', '', $path), '/'))[0];
|
||||||
|
$this->assertSame(($action === 'get') ? 'get' : 'post', $method, $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOperationsAreComplete(): void
|
||||||
|
{
|
||||||
|
$tags = array_column(self::spec()['tags'], 'name');
|
||||||
|
$ids = [];
|
||||||
|
foreach (self::operations() as [$path, $method, $operation]) {
|
||||||
|
$where = strtoupper($method) . ' ' . $path;
|
||||||
|
$this->assertNotEmpty($operation['summary'] ?? null, "$where: summary");
|
||||||
|
$this->assertNotEmpty($operation['operationId'] ?? null, "$where: operationId");
|
||||||
|
$ids[] = $operation['operationId'];
|
||||||
|
$this->assertNotEmpty($operation['tags'] ?? [], "$where: tags");
|
||||||
|
$this->assertSame([], array_values(array_diff($operation['tags'] ?? [], $tags)), "$where: tags not in the tag list");
|
||||||
|
$this->assertArrayHasKey('200', $operation['responses'] ?? [], "$where: 200 response");
|
||||||
|
$this->assertArrayHasKey('401', $operation['responses'] ?? [], "$where: 401 response");
|
||||||
|
preg_match_all('/\{([^}]+)\}/', $path, $placeholders);
|
||||||
|
$declared = array_column(array_filter($operation['parameters'] ?? [], fn(array $p): bool => ($p['in'] ?? '') === 'path'), 'name');
|
||||||
|
sort($declared);
|
||||||
|
$expected = $placeholders[1];
|
||||||
|
sort($expected);
|
||||||
|
$this->assertSame($expected, $declared, "$where: path parameters");
|
||||||
|
}
|
||||||
|
$this->assertSame(array_values(array_unique($ids)), $ids, 'operationIds must be unique');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEveryTagIsUsed(): void
|
||||||
|
{
|
||||||
|
$used = array_merge(...array_map(fn(array $op): array => $op[2]['tags'] ?? [], self::operations()));
|
||||||
|
$this->assertSame([], array_values(array_diff(array_column(self::spec()['tags'], 'name'), $used)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReferencesResolve(): void
|
||||||
|
{
|
||||||
|
$refs = [];
|
||||||
|
$spec = self::spec();
|
||||||
|
array_walk_recursive($spec, function (mixed $value, mixed $key) use (&$refs): void {
|
||||||
|
if ($key === '$ref') {
|
||||||
|
$refs[] = $value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
foreach (array_unique($refs) as $ref) {
|
||||||
|
$node = self::spec();
|
||||||
|
foreach (explode('/', substr($ref, 2)) as $part) {
|
||||||
|
$this->assertIsArray($node, $ref);
|
||||||
|
$this->assertArrayHasKey(str_replace(['~1', '~0'], ['/', '~'], $part), $node, "unresolved $ref");
|
||||||
|
$node = $node[str_replace(['~1', '~0'], ['/', '~'], $part)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function exampleOf(array $operation, string $name): string
|
||||||
|
{
|
||||||
|
foreach ($operation['parameters'] ?? [] as $parameter) {
|
||||||
|
if (($parameter['name'] ?? null) === $name) {
|
||||||
|
$example = $parameter['example'] ?? $parameter['schema']['example'] ?? $parameter['schema']['enum'][0] ?? 'test';
|
||||||
|
return rawurlencode((string)$example);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'test';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api;
|
||||||
|
|
||||||
|
/** What json_api.php does for every route: methods, read-only keys, request bodies, answers, unknown routes */
|
||||||
|
class RequestHandlingTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public function testWritesNeedPost(): void
|
||||||
|
{
|
||||||
|
foreach (['add/alias', 'edit/alias', 'delete/alias'] as $route) {
|
||||||
|
$result = $this->request('GET', $route, ['post' => ['attr' => '{}', 'items' => '[]']]);
|
||||||
|
$this->assertApiError($result, 405, 'only POST method is allowed');
|
||||||
|
$this->assertNotCalled($result, 'mailbox');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadsNeedGet(): void
|
||||||
|
{
|
||||||
|
$this->assertApiError($this->post('get/domain/all'), 405, 'only GET method is allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSearchNeedsPost(): void
|
||||||
|
{
|
||||||
|
$this->assertApiError($this->get('search/domain'), 405, 'only POST method is allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadOnlyKeysCannotWrite(): void
|
||||||
|
{
|
||||||
|
foreach (['add/alias' => ['address' => 'a@example.org'], 'edit/alias' => ['items' => [1], 'attr' => []], 'delete/alias' => [1]] as $route => $body) {
|
||||||
|
$result = $this->post($route, $body, ['session' => self::API_RO]);
|
||||||
|
$this->assertApiError($result, 403, 'API read/write access denied');
|
||||||
|
$this->assertNotCalled($result, 'mailbox');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadOnlyKeysCanRead(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/alias/1', ['session' => self::API_RO]);
|
||||||
|
|
||||||
|
$this->assertSame(200, $result->status);
|
||||||
|
$this->assertCalled($result, 'mailbox', ['get', 'alias_details', '1']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWritesNeedALogin(): void
|
||||||
|
{
|
||||||
|
$this->assertApiError($this->post('add/alias', ['address' => 'a@example.org'], ['session' => self::NO_SESSION]), 403, 'API read/write access denied');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadsWithoutLoginAnswerNothing(): void
|
||||||
|
{
|
||||||
|
// a wrong API key ends in sessions.inc.php with 401; without key and session the read routes are skipped
|
||||||
|
$result = $this->get('get/domain/all', ['session' => self::NO_SESSION]);
|
||||||
|
|
||||||
|
$this->assertSame([200, ''], [$result->status, $result->body]);
|
||||||
|
$this->assertNotCalled($result, 'mailbox');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownRoutes(): void
|
||||||
|
{
|
||||||
|
$this->assertApiError($this->get('get/no-such-thing'), 404, 'route not found');
|
||||||
|
$this->assertApiError($this->post('add/no-such-thing', ['x' => 1]), 404, 'route not found');
|
||||||
|
$this->assertApiError($this->post('edit/no-such-thing', ['items' => [], 'attr' => []]), 404, 'route not found');
|
||||||
|
$this->assertApiError($this->post('delete/no-such-thing', [1]), 404, 'route not found');
|
||||||
|
$this->assertApiError($this->get('nothing/at/all'), 404, 'route not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInvalidJsonBody(): void
|
||||||
|
{
|
||||||
|
$this->assertApiError($this->post('add/alias', '{"address": '), 400, 'Request body doesn\'t contain valid json!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testJsonBodies(): void
|
||||||
|
{
|
||||||
|
// add: the body is attr
|
||||||
|
$add = $this->post('add/alias', ['address' => 'a@example.org', 'goto' => 'b@example.org']);
|
||||||
|
$this->assertCalled($add, 'mailbox', ['add', 'alias', ['address' => 'a@example.org', 'goto' => 'b@example.org']]);
|
||||||
|
|
||||||
|
// edit: {"items": ..., "attr": ...}
|
||||||
|
$edit = $this->post('edit/alias', ['items' => [3, 4], 'attr' => ['active' => '0']]);
|
||||||
|
$this->assertCalled($edit, 'mailbox', ['edit', 'alias', ['id' => [3, 4], 'active' => '0']]);
|
||||||
|
|
||||||
|
// delete: the body is the list of items
|
||||||
|
$delete = $this->post('delete/alias', [3, 4]);
|
||||||
|
$this->assertCalled($delete, 'mailbox', ['delete', 'alias', ['id' => [3, 4]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFormBodiesOfTheUi(): void
|
||||||
|
{
|
||||||
|
$result = $this->postForm('edit/alias', ['items' => '[3]', 'attr' => '{"active":"1","csrf_token":"t"}'], ['session' => self::ADMIN]);
|
||||||
|
|
||||||
|
$this->assertCalled($result, 'mailbox', ['edit', 'alias', ['id' => [3], 'active' => '1']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMissingAttributes(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(['type' => 'error', 'msg' => 'Cannot find attributes in post data'], $this->request('POST', 'add/alias')->json());
|
||||||
|
$this->assertSame(['type' => 'error', 'msg' => 'Cannot find attributes in post data'], $this->request('POST', 'delete/alias')->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWriteAnswers(): void
|
||||||
|
{
|
||||||
|
$messages = [self::message('success', ['alias_added', 'a@example.org'], ['mailbox', 'add', 'alias'])];
|
||||||
|
|
||||||
|
$done = $this->post('add/alias', ['address' => 'a@example.org'], ['script' => [self::answer('mailbox', ['add', 'alias'], true, $messages)]]);
|
||||||
|
$this->assertSame($messages, $done->json());
|
||||||
|
|
||||||
|
$silent = $this->post('add/alias', ['address' => 'a@example.org'], ['script' => [self::answer('mailbox', ['add', 'alias'], true)]]);
|
||||||
|
$this->assertSame(['type' => 'success', 'msg' => 'Task completed'], $silent->json());
|
||||||
|
|
||||||
|
$failed = $this->post('add/alias', ['address' => 'a@example.org'], ['script' => [self::answer('mailbox', ['add', 'alias'], false)]]);
|
||||||
|
$this->assertSame(['type' => 'error', 'msg' => 'Cannot add item'], $failed->json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadAnswers(): void
|
||||||
|
{
|
||||||
|
$found = $this->get('get/alias/1', ['script' => [self::answer('mailbox', ['get', 'alias_details'], ['id' => 1, 'address' => 'a@example.org'])]]);
|
||||||
|
$this->assertSame(['id' => 1, 'address' => 'a@example.org'], $found->json());
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/alias/1')->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRequestsFromABrowserPageAreRefused(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/domain/all', ['server' => ['HTTP_SEC_FETCH_DEST' => 'document']]);
|
||||||
|
|
||||||
|
$this->assertSame('', $result->body);
|
||||||
|
$this->assertSame(['cors'], $result->callNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The routes json_api.php answers, read from its nested switch statements over $action, $category, $object and
|
||||||
|
* $extra: "get/mailbox/all", "add/alias", "*" for a default case ("get/mailbox/*" = get/mailbox/<anything else>).
|
||||||
|
* Default cases that answer 404 are no routes. Only the most specific routes are listed (get/mailbox/all, not get/mailbox).
|
||||||
|
*/
|
||||||
|
final class RouteMap
|
||||||
|
{
|
||||||
|
private const ROUTE_VARS = ['$action', '$category', '$object', '$extra'];
|
||||||
|
|
||||||
|
/** @return list<string> */
|
||||||
|
public static function fromFile(string $file): array
|
||||||
|
{
|
||||||
|
$tokens = array_values(array_filter(token_get_all((string)file_get_contents($file)),
|
||||||
|
fn($t): bool => !is_array($t) || !in_array($t[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)));
|
||||||
|
$stack = [];
|
||||||
|
$depth = 0;
|
||||||
|
$pending = null;
|
||||||
|
$routes = [];
|
||||||
|
foreach ($tokens as $i => $token) {
|
||||||
|
$id = is_array($token) ? $token[0] : $token;
|
||||||
|
if ($id === T_SWITCH) {
|
||||||
|
$isVar = ($tokens[$i + 1] ?? null) === '(' && is_array($tokens[$i + 2] ?? null) && $tokens[$i + 2][0] === T_VARIABLE && ($tokens[$i + 3] ?? null) === ')';
|
||||||
|
$pending = ($isVar && in_array($tokens[$i + 2][1], self::ROUTE_VARS, true)) ? $tokens[$i + 2][1] : '';
|
||||||
|
} elseif ($id === '{' || $id === T_CURLY_OPEN || $id === T_DOLLAR_OPEN_CURLY_BRACES) {
|
||||||
|
$depth++;
|
||||||
|
if ($pending !== null) {
|
||||||
|
$stack[] = ['var' => $pending, 'depth' => $depth, 'label' => null];
|
||||||
|
$pending = null;
|
||||||
|
}
|
||||||
|
} elseif ($id === '}') {
|
||||||
|
if (!empty($stack) && end($stack)['depth'] === $depth) {
|
||||||
|
array_pop($stack);
|
||||||
|
}
|
||||||
|
$depth--;
|
||||||
|
} elseif (($id === T_CASE || $id === T_DEFAULT) && !empty($stack) && end($stack)['depth'] === $depth) {
|
||||||
|
$label = ($id === T_DEFAULT) ? '*' : self::literal($tokens[$i + 1] ?? null);
|
||||||
|
$stack[count($stack) - 1]['label'] = $label;
|
||||||
|
if (end($stack)['var'] === '' || $label === null || ($id === T_DEFAULT && self::answers404($tokens, $i))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$route = [];
|
||||||
|
foreach ($stack as $switch) {
|
||||||
|
if ($switch['var'] !== '') {
|
||||||
|
$route[] = $switch['label'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$routes[implode('/', $route)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$routes = array_keys($routes);
|
||||||
|
return array_values(array_filter($routes, function (string $route) use ($routes): bool {
|
||||||
|
foreach ($routes as $other) {
|
||||||
|
if (str_starts_with($other, $route . '/')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether $documented (an OpenAPI path like /api/v1/get/mailbox/{id}) covers $route: same leading segments, a
|
||||||
|
* {parameter} matches anything, trailing "*" of the route are optional (add/mailbox/* = add/mailbox)
|
||||||
|
*/
|
||||||
|
public static function covers(string $documented, string $route): bool
|
||||||
|
{
|
||||||
|
$doc = explode('/', trim(preg_replace('#^/?api/v1/#', '', $documented), '/'));
|
||||||
|
$segments = explode('/', $route);
|
||||||
|
while (count($segments) > count($doc) && end($segments) === '*') {
|
||||||
|
array_pop($segments);
|
||||||
|
}
|
||||||
|
if (count($segments) > count($doc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($segments as $i => $segment) {
|
||||||
|
$isParam = (bool)preg_match('/^\{.+\}$/', $doc[$i]);
|
||||||
|
if ($segment === '*' ? !$isParam : ($segment !== $doc[$i] && !$isParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function literal(mixed $token): ?string
|
||||||
|
{
|
||||||
|
return (is_array($token) && $token[0] === T_CONSTANT_ENCAPSED_STRING) ? substr($token[1], 1, -1) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A default case whose code (up to the next case of its switch) sets the status 404 */
|
||||||
|
private static function answers404(array $tokens, int $start): bool
|
||||||
|
{
|
||||||
|
$level = 0;
|
||||||
|
for ($i = $start + 1; $i < count($tokens); $i++) {
|
||||||
|
$id = is_array($tokens[$i]) ? $tokens[$i][0] : $tokens[$i];
|
||||||
|
if ($id === '{' || $id === T_CURLY_OPEN || $id === T_DOLLAR_OPEN_CURLY_BRACES) {
|
||||||
|
$level++;
|
||||||
|
} elseif ($id === '}') {
|
||||||
|
if ($level-- === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} elseif ($level === 0 && ($id === T_CASE || $id === T_DEFAULT)) {
|
||||||
|
return false;
|
||||||
|
} elseif ($id === T_STRING && $tokens[$i][1] === 'http_response_code' && is_array($tokens[$i + 2] ?? null) && $tokens[$i + 2][1] === '404') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Routing;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** add/edit/delete/get alias */
|
||||||
|
class AliasRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const SALES = ['id' => 12, 'address' => 'sales@example.org', 'goto' => 'anna@example.org,ben@example.org', 'active' => 1];
|
||||||
|
private const INFO = ['id' => 13, 'address' => 'info@example.org', 'goto' => 'anna@example.org', 'active' => 1];
|
||||||
|
private const SHOP = ['id' => 20, 'address' => 'shop@example.net', 'goto' => 'ben@example.org', 'active' => 0];
|
||||||
|
|
||||||
|
public function testAddPassesTheBody(): void
|
||||||
|
{
|
||||||
|
$attr = [
|
||||||
|
'address' => "sales@example.org, info@example.org",
|
||||||
|
'goto' => 'anna@example.org,ben@example.org',
|
||||||
|
'active' => '1',
|
||||||
|
'sogo_visible' => 1,
|
||||||
|
'internal' => '0',
|
||||||
|
'sender_allowed' => true,
|
||||||
|
'public_comment' => 'Sales team',
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this->post('add/alias', $attr + ['csrf_token' => 'x']);
|
||||||
|
|
||||||
|
$this->assertSame([['add', 'alias', $attr]], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditMergesItemsAndAttributes(): void
|
||||||
|
{
|
||||||
|
$list = $this->post('edit/alias', ['items' => [12, 'info@example.org'], 'attr' => ['goto_null' => '1', 'active' => '0']]);
|
||||||
|
$this->assertCalled($list, 'mailbox', ['edit', 'alias', ['id' => [12, 'info@example.org'], 'goto_null' => '1', 'active' => '0']]);
|
||||||
|
|
||||||
|
$single = $this->post('edit/alias', ['items' => 12, 'attr' => ['expand_alias' => true]]);
|
||||||
|
$this->assertCalled($single, 'mailbox', ['edit', 'alias', ['id' => [12], 'expand_alias' => true]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteTakesIdsOrAddresses(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/alias', [12, 'info@example.org']);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', 'alias', ['id' => [12, 'info@example.org']]]], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOneAliasByIdOrAddress(): void
|
||||||
|
{
|
||||||
|
$byId = $this->get('get/alias/12', ['script' => [self::answer('mailbox', ['get', 'alias_details', '12'], self::SALES)]]);
|
||||||
|
$this->assertSame(self::SALES, $byId->json());
|
||||||
|
|
||||||
|
$byAddress = $this->get('get/alias/sales@example.org');
|
||||||
|
$this->assertSame([['get', 'alias_details', 'sales@example.org']], $byAddress->callsTo('mailbox'));
|
||||||
|
$this->assertSame('{}', $byAddress->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllWalksTheDomainsAndAliasDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/alias/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com']),
|
||||||
|
self::answer('mailbox', ['get', 'alias_domains'], ['example.net']),
|
||||||
|
self::answer('mailbox', ['get', 'aliases', 'example.org'], [12, 13, 14]),
|
||||||
|
self::answer('mailbox', ['get', 'aliases', 'example.com'], []),
|
||||||
|
self::answer('mailbox', ['get', 'aliases', 'example.net'], [20]),
|
||||||
|
self::answer('mailbox', ['get', 'alias_details', 12], self::SALES),
|
||||||
|
self::answer('mailbox', ['get', 'alias_details', 13], self::INFO),
|
||||||
|
self::answer('mailbox', ['get', 'alias_details', 14], false),
|
||||||
|
self::answer('mailbox', ['get', 'alias_details', 20], self::SHOP),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([self::SALES, self::INFO, self::SHOP], $result->json());
|
||||||
|
$this->assertSame([
|
||||||
|
['get', 'domains'],
|
||||||
|
['get', 'alias_domains'],
|
||||||
|
['get', 'aliases', 'example.org'],
|
||||||
|
['get', 'alias_details', 12],
|
||||||
|
['get', 'alias_details', 13],
|
||||||
|
['get', 'alias_details', 14],
|
||||||
|
['get', 'aliases', 'example.com'],
|
||||||
|
['get', 'aliases', 'example.net'],
|
||||||
|
['get', 'alias_details', 20],
|
||||||
|
], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllOfTheNamedDomains(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/alias/all/example.org,example.net', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'aliases', 'example.org'], [12]),
|
||||||
|
self::answer('mailbox', ['get', 'aliases', 'example.net'], [20]),
|
||||||
|
self::answer('mailbox', ['get', 'alias_details', 12], self::SALES),
|
||||||
|
self::answer('mailbox', ['get', 'alias_details', 20], self::SHOP),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([self::SALES, self::SHOP], $result->json());
|
||||||
|
$this->assertSame([
|
||||||
|
['get', 'aliases', 'example.org'],
|
||||||
|
['get', 'alias_details', 12],
|
||||||
|
['get', 'aliases', 'example.net'],
|
||||||
|
['get', 'alias_details', 20],
|
||||||
|
], $result->callsTo('mailbox'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllWithoutAliases(): void
|
||||||
|
{
|
||||||
|
$noDomains = $this->get('get/alias/all', ['script' => [
|
||||||
|
self::answer('mailbox', ['get', 'domains'], []),
|
||||||
|
self::answer('mailbox', ['get', 'alias_domains'], []),
|
||||||
|
]]);
|
||||||
|
$this->assertSame('{}', $noDomains->body);
|
||||||
|
|
||||||
|
$noAliases = $this->get('get/alias/all/example.org', ['script' => [self::answer('mailbox', ['get', 'aliases'], false)]]);
|
||||||
|
$this->assertSame('{}', $noAliases->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Routing;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
|
||||||
|
/** add/edit/delete/get fwdhost */
|
||||||
|
class ForwardingHostRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private const HOSTS = [
|
||||||
|
['host' => '192.0.2.25', 'source' => 'relay.example.com', 'keep_spam' => 'yes'],
|
||||||
|
['host' => '2001:db8::25', 'source' => 'relay.example.com', 'keep_spam' => 'yes'],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function testAddPassesTheBody(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('add/fwdhost', ['hostname' => 'relay.example.com', 'filter_spam' => '1']);
|
||||||
|
|
||||||
|
$this->assertSame([['add', ['hostname' => 'relay.example.com', 'filter_spam' => '1']]], $result->callsTo('fwdhost'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEditNamesTheHostsFwdhost(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('edit/fwdhost', ['items' => ['192.0.2.25', '198.51.100.0/24'], 'attr' => ['keep_spam' => '0']]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', ['fwdhost' => ['192.0.2.25', '198.51.100.0/24'], 'keep_spam' => '0']]], $result->callsTo('fwdhost'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteNamesTheHostsForwardinghost(): void
|
||||||
|
{
|
||||||
|
$result = $this->post('delete/fwdhost', ['192.0.2.25', '2001:db8::25']);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', ['forwardinghost' => ['192.0.2.25', '2001:db8::25']]]], $result->callsTo('fwdhost'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllPassesTheList(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/fwdhost/all', ['script' => [self::answer('fwdhost', ['get'], self::HOSTS)]]);
|
||||||
|
|
||||||
|
$this->assertSame(self::HOSTS, $result->json());
|
||||||
|
$this->assertSame([['get']], $result->callsTo('fwdhost'));
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/fwdhost/all', ['script' => [self::answer('fwdhost', ['get'], [])]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOneHost(): void
|
||||||
|
{
|
||||||
|
$found = $this->get('get/fwdhost/192.0.2.25', ['script' => [self::answer('fwdhost', ['details', '192.0.2.25'], self::HOSTS[0])]]);
|
||||||
|
$this->assertSame(self::HOSTS[0], $found->json());
|
||||||
|
|
||||||
|
$this->assertSame('{}', $this->get('get/fwdhost/192.0.2.99', ['script' => [self::answer('fwdhost', ['details'], [])]])->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOneNetworkIsCutAtTheSlash(): void
|
||||||
|
{
|
||||||
|
$result = $this->get('get/fwdhost/198.51.100.0/24');
|
||||||
|
|
||||||
|
$this->assertSame([['details', '198.51.100.0']], $result->callsTo('fwdhost'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Routing;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
|
||||||
|
/** bcc, recipient_map and tls-policy-map: json_api.php handles the three alike */
|
||||||
|
class MapRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public static function maps(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'bcc' => ['bcc', 'bcc',
|
||||||
|
['local_dest' => 'example.org', 'bcc_dest' => 'archive@example.com', 'type' => 'rcpt', 'active' => '1'],
|
||||||
|
['id' => 3, 'local_dest' => '@example.org', 'bcc_dest' => 'archive@example.com', 'active' => 1, 'type' => 'rcpt', 'domain' => 'example.org']],
|
||||||
|
'recipient_map' => ['recipient_map', 'recipient_map',
|
||||||
|
['recipient_map_old' => 'old@example.org', 'recipient_map_new' => 'new@example.org', 'active' => 1],
|
||||||
|
['id' => 3, 'recipient_map_old' => 'old@example.org', 'recipient_map_new' => 'new@example.org', 'active' => 1]],
|
||||||
|
'tls-policy-map' => ['tls-policy-map', 'tls_policy_maps',
|
||||||
|
['dest' => 'example.com', 'policy' => 'encrypt', 'parameters' => 'protocols=>=TLSv1.2', 'active' => '1'],
|
||||||
|
['id' => 3, 'dest' => 'example.com', 'policy' => 'encrypt', 'parameters' => '', 'active' => 1]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('maps')]
|
||||||
|
public function testAddPassesTheBody(string $category, string $function, array $attr): void
|
||||||
|
{
|
||||||
|
$result = $this->post("add/$category", $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['add', $attr]], $result->callsTo($function));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('maps')]
|
||||||
|
public function testEditMergesItemsAndAttributes(string $category, string $function): void
|
||||||
|
{
|
||||||
|
$list = $this->post("edit/$category", ['items' => [3, '4'], 'attr' => ['active' => '0']]);
|
||||||
|
$this->assertSame([['edit', ['id' => [3, '4'], 'active' => '0']]], $list->callsTo($function));
|
||||||
|
|
||||||
|
$single = $this->post("edit/$category", ['items' => 3, 'attr' => ['active' => '1']]);
|
||||||
|
$this->assertSame([['edit', ['id' => [3], 'active' => '1']]], $single->callsTo($function));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('maps')]
|
||||||
|
public function testDeleteTakesIds(string $category, string $function): void
|
||||||
|
{
|
||||||
|
$result = $this->post("delete/$category", [3, 4]);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', ['id' => [3, 4]]]], $result->callsTo($function));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('maps')]
|
||||||
|
public function testGetAllCollectsTheDetailsOfEveryId(string $category, string $function, array $attr, array $details): void
|
||||||
|
{
|
||||||
|
$result = $this->get("get/$category/all", ['script' => [
|
||||||
|
self::answer($function, ['get'], [3, 4, 5]),
|
||||||
|
self::answer($function, ['details', 3], $details),
|
||||||
|
self::answer($function, ['details', 4], false),
|
||||||
|
self::answer($function, ['details', 5], ['id' => 5] + $details),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([$details, ['id' => 5] + $details], $result->json());
|
||||||
|
$this->assertSame([['get'], ['details', 3], ['details', 4], ['details', 5]], $result->callsTo($function));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('maps')]
|
||||||
|
public function testGetAllWithoutEntries(string $category, string $function): void
|
||||||
|
{
|
||||||
|
$result = $this->get("get/$category/all", ['script' => [self::answer($function, ['get'], [])]]);
|
||||||
|
|
||||||
|
$this->assertSame('{}', $result->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('maps')]
|
||||||
|
public function testGetOneAddsANullMember(string $category, string $function, array $attr, array $details): void
|
||||||
|
{
|
||||||
|
// json_api.php appends the undefined $details to the answer
|
||||||
|
$found = $this->get("get/$category/3", ['script' => [self::answer($function, ['details', '3'], $details)]]);
|
||||||
|
$this->assertSame($details + [0 => null], $found->json());
|
||||||
|
|
||||||
|
$missing = $this->get("get/$category/9", ['script' => [self::answer($function, ['details', '9'], false)]]);
|
||||||
|
$this->assertSame('{}', $missing->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Mailcow\Tests\Api\Routing;
|
||||||
|
|
||||||
|
use Mailcow\Tests\Api\ApiTestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
|
||||||
|
/** relayhost (sender-dependent transports) and transport (transport maps): json_api.php handles both alike */
|
||||||
|
class TransportRoutesTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
public static function transports(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'relayhost' => ['relayhost',
|
||||||
|
['hostname' => '[smtp.example.com]:587', 'username' => 'relay-user', 'password' => 'secret'],
|
||||||
|
['id' => 1, 'hostname' => '[smtp.example.com]:587', 'username' => 'relay-user', 'active' => 1, 'used_by_domains' => 'example.org']],
|
||||||
|
'transport' => ['transport',
|
||||||
|
['destination' => 'example.com, .example.com', 'nexthop' => '[mx.example.com]:25', 'is_mx_based' => '0', 'active' => '1'],
|
||||||
|
['id' => 1, 'is_mx_based' => 0, 'destination' => 'example.com', 'nexthop' => '[mx.example.com]:25', 'active' => 1]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('transports')]
|
||||||
|
public function testAddPassesTheBody(string $category, array $attr): void
|
||||||
|
{
|
||||||
|
$result = $this->post("add/$category", $attr);
|
||||||
|
|
||||||
|
$this->assertSame([['add', $attr]], $result->callsTo($category));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('transports')]
|
||||||
|
public function testEditMergesItemsAndAttributes(string $category): void
|
||||||
|
{
|
||||||
|
$result = $this->post("edit/$category", ['items' => [1, 2], 'attr' => ['active' => '0', 'password' => '']]);
|
||||||
|
|
||||||
|
$this->assertSame([['edit', ['id' => [1, 2], 'active' => '0', 'password' => '']]], $result->callsTo($category));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('transports')]
|
||||||
|
public function testDeleteTakesIds(string $category): void
|
||||||
|
{
|
||||||
|
$result = $this->post("delete/$category", ['1', 2]);
|
||||||
|
|
||||||
|
$this->assertSame([['delete', ['id' => ['1', 2]]]], $result->callsTo($category));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('transports')]
|
||||||
|
public function testGetAllCollectsTheDetailsOfEveryRow(string $category, array $attr, array $details): void
|
||||||
|
{
|
||||||
|
$result = $this->get("get/$category/all", ['script' => [
|
||||||
|
self::answer($category, ['get'], [['id' => 1, 'active' => 1], ['id' => 2, 'active' => 1], ['id' => 3, 'active' => 0]]),
|
||||||
|
self::answer($category, ['details', 1], $details),
|
||||||
|
self::answer($category, ['details', 2], false),
|
||||||
|
self::answer($category, ['details', 3], ['id' => 3] + $details),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$this->assertSame([$details, ['id' => 3] + $details], $result->json());
|
||||||
|
$this->assertSame([['get'], ['details', 1], ['details', 2], ['details', 3]], $result->callsTo($category));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('transports')]
|
||||||
|
public function testGetAllWithoutEntries(string $category): void
|
||||||
|
{
|
||||||
|
$none = $this->get("get/$category/all", ['script' => [self::answer($category, ['get'], [])]]);
|
||||||
|
$this->assertSame('{}', $none->body);
|
||||||
|
|
||||||
|
$noDetails = $this->get("get/$category/all", ['script' => [
|
||||||
|
self::answer($category, ['get'], [['id' => 1]]),
|
||||||
|
self::answer($category, ['details'], false),
|
||||||
|
]]);
|
||||||
|
$this->assertSame('{}', $noDetails->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('transports')]
|
||||||
|
public function testGetOne(string $category, array $attr, array $details): void
|
||||||
|
{
|
||||||
|
$found = $this->get("get/$category/1", ['script' => [self::answer($category, ['details', '1'], $details)]]);
|
||||||
|
$this->assertSame($details, $found->json());
|
||||||
|
|
||||||
|
$missing = $this->get("get/$category/9", ['script' => [self::answer($category, ['details', '9'], false)]]);
|
||||||
|
$this->assertSame('{}', $missing->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^11.5"
|
"phpunit/phpunit": "^11.5",
|
||||||
|
"symfony/yaml": "^7.3"
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
Generated
+235
-2
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "9a4f6f2c4beb80cf38c831d11167da8d",
|
"content-hash": "4211ddb7f0a4ed07b766e52d30177798",
|
||||||
"packages": [],
|
"packages": [],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
@@ -1721,6 +1721,239 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-10-20T05:08:20+00:00"
|
"time": "2024-10-20T05:08:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/deprecation-contracts",
|
||||||
|
"version": "v3.8.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||||
|
"reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/feb1edf4e546af3b85c5caebe8359a20db230ca3",
|
||||||
|
"reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"url": "https://github.com/symfony/contracts",
|
||||||
|
"name": "symfony/contracts"
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.8-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"function.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A generic function and convention to trigger deprecation notices",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.8.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-09-16T14:15:10+00:00",
|
||||||
|
"published-time": "2026-11-04T11:25:34+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"version": "v1.37.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
|
||||||
|
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-ctype": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ctype": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"url": "https://github.com/symfony/polyfill",
|
||||||
|
"name": "symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Ctype\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gert de Pagter",
|
||||||
|
"email": "BackEndTea@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for ctype functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"ctype",
|
||||||
|
"polyfill",
|
||||||
|
"portable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-04-10T16:19:22+00:00",
|
||||||
|
"published-time": "2026-04-26T14:44:50+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/yaml",
|
||||||
|
"version": "v7.4.23",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/yaml.git",
|
||||||
|
"reference": "1e601207ffe627a2e621faa9d661f1d32dcf1cec"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/yaml/zipball/1e601207ffe627a2e621faa9d661f1d32dcf1cec",
|
||||||
|
"reference": "1e601207ffe627a2e621faa9d661f1d32dcf1cec",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
|
"symfony/polyfill-ctype": "^1.8"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/console": "<6.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/console": "^6.4|^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"Resources/bin/yaml-lint"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Yaml\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Loads and dumps YAML files",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/yaml/tree/v7.4.23"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-11-04T08:27:22+00:00",
|
||||||
|
"published-time": "2026-11-04T11:28:41+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "theseer/tokenizer",
|
"name": "theseer/tokenizer",
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
@@ -1782,5 +2015,5 @@
|
|||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "8.2.29"
|
"php": "8.2.29"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.9.0"
|
"plugin-api-version": "2.11.0"
|
||||||
}
|
}
|
||||||
@@ -374,10 +374,12 @@ if ! $COMPOSE_COMMAND config -q; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\e[32mChecking for conflicting bridges...\e[0m"
|
echo -e "\e[32mChecking for conflicting bridges...\e[0m"
|
||||||
MAILCOW_BRIDGE=$($COMPOSE_COMMAND config | grep -i com.docker.network.bridge.name | cut -d':' -f2)
|
MAILCOW_BRIDGE=$($COMPOSE_COMMAND config | grep -i com.docker.network.bridge.name | cut -d':' -f2 | tr -d "[:space:]\"'")
|
||||||
|
if [[ -n "${MAILCOW_BRIDGE}" ]]; then
|
||||||
while read NAT_ID; do
|
while read NAT_ID; do
|
||||||
iptables -t nat -D POSTROUTING "$NAT_ID"
|
iptables -t nat -D POSTROUTING "$NAT_ID"
|
||||||
done < <(iptables -L -vn -t nat --line-numbers | grep "$IPV4_NETWORK" | grep -E 'MASQUERADE.*all' | grep -v "${MAILCOW_BRIDGE}" | cut -d' ' -f1)
|
done < <(iptables -L -vn -t nat --line-numbers | grep "$IPV4_NETWORK" | grep -E 'MASQUERADE.*all' | grep -vE "[[:space:]]!?${MAILCOW_BRIDGE}[[:space:]]" | cut -d' ' -f1)
|
||||||
|
fi
|
||||||
|
|
||||||
DIFF_DIRECTORY=update_diffs
|
DIFF_DIRECTORY=update_diffs
|
||||||
DIFF_FILE=${DIFF_DIRECTORY}/diff_before_update_$(date +"%Y-%m-%d-%H-%M-%S")
|
DIFF_FILE=${DIFF_DIRECTORY}/diff_before_update_$(date +"%Y-%m-%d-%H-%M-%S")
|
||||||
|
|||||||
Reference in new issue
Block a user