Compare commits

..
27 Commits
Author SHA1 Message Date
FreddleSpl0it 88fbaa459a Merge pull request #7594 from mailcow/staging
Update 2026-11
2026-11-05 11:02:42 +01:00
FreddleSpl0it 55982ac74b Merge pull request #7537 from mailcow/staging
Update 2026-10
2026-10-21 19:36:04 +02:00
FreddleSpl0it 81f6f7b002 Merge pull request #7506 from mailcow/staging
Update 2026-09a
2026-10-06 12:28:36 +02:00
FreddleSpl0it ca07d8d333 Merge pull request #7477 from mailcow/staging
Update 2026-09
2026-09-21 10:30:34 +02:00
FreddleSpl0it 02552ffefd Merge pull request #7427 from mailcow/staging
Update 2026-07b
2026-08-18 09:29:10 +02:00
FreddleSpl0it 06424670fa Merge pull request #7393 from mailcow/staging
update README.md sponsors
2026-08-05 09:00:52 +02:00
FreddleSpl0it 489db90512 Merge pull request #7391 from mailcow/staging
Update 2026-07a
2026-07-30 11:17:15 +02:00
FreddleSpl0it 641ed63782 Merge pull request #7353 from mailcow/staging
update README.md sponsors
2026-07-15 08:31:16 +02:00
FreddleSpl0it 96a70652c3 Merge pull request #7328 from mailcow/staging
Update 2026-07
2026-07-13 10:42:55 +02:00
FreddleSpl0it 2ac4b1deae Merge pull request #7260 from mailcow/staging
Update 2026-05c
2026-05-26 10:50:58 +02:00
FreddleSpl0it 1eb6d2e26c Merge pull request #7243 from mailcow/staging
Update 2025-05b
2026-05-21 08:33:36 +02:00
FreddleSpl0it 384e2f6ac1 Merge pull request #7227 from mailcow/staging
Update 2026-05a
2026-05-13 10:42:14 +02:00
FreddleSpl0it 32156a337a Merge pull request #7221 from mailcow/staging
Update 2026-05
2026-05-12 08:46:25 +02:00
FreddleSpl0it 281cf93db3 Merge pull request #7174 from mailcow/staging
Update 2026-03b
2026-03-31 09:57:16 +02:00
FreddleSpl0it f399c07c85 Merge pull request #7137 from mailcow/staging
Hotfix 2026-03a
2026-03-13 14:15:04 +01:00
FreddleSpl0it a693325fe6 Merge pull request #7135 from mailcow/staging
Update 2026-03a
2026-03-13 13:16:32 +01:00
FreddleSpl0it 9ad84eee92 Merge pull request #7113 from mailcow/staging
Hotfix 2026-03
2026-03-10 13:50:33 +01:00
FreddleSpl0it b59869b720 Merge pull request #7109 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:39:43 +01:00
FreddleSpl0it 7515bef66c Merge pull request #7104 from mailcow/staging
Hotfix 2026-03
2026-03-10 10:05:39 +01:00
FreddleSpl0it b84ba8ded1 Merge pull request #7101 from mailcow/staging
Update 2026-03
2026-03-10 08:08:29 +01:00
FreddleSpl0it 4845928e7a Merge pull request #7027 from mailcow/staging
[Hotfix] Update 2026-01
2026-01-29 10:31:23 +01:00
FreddleSpl0it 4ccfedd6b3 Merge pull request #7024 from mailcow/staging
🐄🛡️ January 2026 Update | Limited EAS/DAV Access and Restricted Alias Sending
2026-01-29 07:25:09 +01:00
Ashitaka e8d9315d4a Merge pull request #6905 from Ashitaka57/6646-pbkdf2-sha512-verify-hash
Support for PBKDF2-SHA512 hash algorithm in verify_hash() (FreeIPA compatibility) (issue 6646)
2025-12-12 14:08:21 +01:00
DerLinkman d977ddb501 backup: add image prefetch function to verify latest image is used 2025-12-12 14:07:57 +01:00
DerLinkman e76f5237ed ofelia: revert fixed cron syntax for sa-rules download 2025-12-12 14:07:47 +01:00
CopilotandDerLinkman c11ed5dd1e Prevent duplicate/plaintext login announcement rendering (#6963)
* Initial plan

* Fix duplicate login announcement display

Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 14:07:36 +01:00
DerLinkman 4ef65fc382 Merge pull request #6948 from mailcow/staging
2025-12
2025-12-09 13:29:15 +01:00
101 changed files with 7114 additions and 18407 deletions

No files matched your search

+21 -46
View File
@@ -3,11 +3,6 @@
# 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 ----
@@ -42,9 +37,6 @@ 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}"
@@ -200,31 +192,9 @@ 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
@@ -233,23 +203,19 @@ 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 [[ "$DETECTED_IPV6" != "true" && "${MANUAL_SETTING,,}" == "true" ]] && _has_public_ipv6; then # if user manually set it, check for mismatch
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
_set_enable_ipv6 false if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
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"
@@ -258,7 +224,16 @@ 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 -f tab mailbox status -u "$ACL_USER" guid "*" | tail -n +2 | cut -f1 | while IFS= read -r folder; do ' script = ('set -o pipefail; doveadm mailbox list -u "$ACL_USER" | while IFS= read -r folder; do '
'case "$folder" in Shared|Shared/*) continue;; esac; ' + action + ' || exit 1; done && doveadm acl recalc -u "$ACL_USER"') 'case "$folder" in 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)
+1 -1
View File
@@ -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//${REDISPASS}/***} failed to execute, exit code was ${EC}" echo "Command ${CMD} failed to execute, exit code was ${EC}"
fi fi
} }
source /source_env.sh source /source_env.sh
+1 -2
View File
@@ -3,8 +3,7 @@ LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
ENV PIP_BREAK_SYSTEM_PACKAGES=1 ENV PIP_BREAK_SYSTEM_PACKAGES=1
RUN apk upgrade --no-cache && \ RUN apk add --no-cache nginx \
apk add --no-cache nginx \
python3 \ python3 \
py3-pip && \ py3-pip && \
pip install --upgrade pip && \ pip install --upgrade pip && \
+1 -1
View File
@@ -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.5 VERSION=1.14.0
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 && \
+1 -26
View File
@@ -277,34 +277,9 @@ if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
} }
} }
"DBL" { "DBL" {
# full copy of the DBL rule from modules.d/rbl.conf, pointed at DQS: # override the defaults for DBL defined in modules.d/rbl.conf
# 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;
+2 -2
View File
@@ -116,8 +116,8 @@ if ($result === false){
exit; exit;
} }
if ($result) { if ($result) {
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $service . " from IP " . $client_ip); error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $client_ip);
set_sasl_log($post['username'], $client_ip, $service); set_sasl_log($post['username'], $client_ip, $post['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,12 +187,6 @@ 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;
+4
View File
@@ -1,3 +1,7 @@
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;
+18
View File
@@ -23,6 +23,24 @@ 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" {
+20
View File
@@ -5,6 +5,12 @@ 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";
@@ -145,6 +151,20 @@ 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
View File
@@ -1,5 +1,6 @@
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";
-2
View File
@@ -9,5 +9,3 @@ 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;
+6 -63
View File
@@ -362,61 +362,6 @@ 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',
@@ -445,8 +390,9 @@ 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', 'orig'}) local rcpts = task:get_recipients('smtp')
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)
@@ -471,9 +417,8 @@ rspamd_config:register_symbol({
-- Check if recipient has a tag (contains '+') -- Check if recipient has a tag (contains '+')
local tag = nil local tag = nil
local base_user, tag_part = rcpt_user:match('^([^%+]+)%+(.+)$') if tagged_rcpt ~= nil then
if base_user and tag_part then tag = tagged_rcpt
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
@@ -553,7 +498,8 @@ 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 ''
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?=' 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_value .. '] ' .. sbj)) .. '?='
task:set_milter_reply({ task:set_milter_reply({
remove_headers = { remove_headers = {
['Subject'] = 1, ['Subject'] = 1,
@@ -961,9 +907,6 @@ 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
+1 -1
View File
@@ -109,7 +109,7 @@ foreach ($rcpts as $rcpt) {
} }
// Skip if domain is excluded // Skip if domain is excluded
if (is_array($exclude_domains) && in_array($parsed_rcpt['domain'], $exclude_domains)) { if (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;
} }
-16
View File
@@ -14,19 +14,3 @@ 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);
}
+74 -1
View File
@@ -1,6 +1,79 @@
<!doctype html> <!doctype html>
<html lang="en-US"> <html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body> <body>
<script src="oauth2-redirect.js"></script> <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>
-1
View File
@@ -1 +0,0 @@
"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()}));
+6894 -13360
View File
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -1,2 +1,3 @@
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ /*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
!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 !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=/^.+(:|&colon;)/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
//# sourceMappingURL=swagger-ui-bundle.js.map
@@ -1,131 +0,0 @@
/*!
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
View File
@@ -1 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","mappings":"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 truncated {"version":3,"file":"swagger-ui-bundle.js","mappings":";CAAA,SAA2CA,EAAMC,GAC1B,iBAAZC,SAA0C,iBAAXC,OACxCA,OAAOD,QAAUD,IACQ,mBAAXG,QAAyBA,OAAOC,IAC9CD,OAAO,GAAIH,GACe,iBAAZC,QACdA,QAAyB,gBAAID,IAE7BD,EAAsB,gBAAIC,GAC3B,CATD,CASGK,MAAM,4CCPTJ,EAAQ,OAAc,EACtB,IAAIK,EAAuB,wCACvBC,EAAoB,mBACpBC,EAAsB,oBACtBC,EAAsB,qDACtBC,EAAiB,oBACjBC,EAA0B,CAAC,IAAK,KA+BpCV,EAAQ,EArBR,SAAqBW,GACjB,IAN0BC,EAMtBC,GANsBD,EAMcD,GAAO,GALxCC,EAAIE,QAAQR,GAAmB,SAAUS,EAAOC,GACnD,OAAOC,OAAOC,aAAaF,EAC/B,KAIKF,QAAQP,EAAqB,IAC7BO,QAAQN,EAAqB,IAC7BW,OACL,IAAKN,EACD,MAAO,cAEX,GAjBJ,SAAsCF,GAClC,OAAOD,EAAwBU,QAAQT,EAAI,KAAO,CACtD,CAeQU,CAA6BR,GAC7B,OAAOA,EAEX,IAAIS,EAAwBT,EAAaE,MAAMN,GAC/C,IAAKa,EACD,OAAOT,EAEX,IAAIU,EAAYD,EAAsB,GACtC,OAAIjB,EAAqBmB,KAAKD,GACnB,cAEJV,CACX,2JCnCA,SAASY,EAAQC,GAWf,OATED,EADoB,mBAAXE,QAAoD,iBAApBA,OAAOC,SACtC,SAAUF,GAClB,cAAcA,CAChB,EAEU,SAAUA,GAClB,OAAOA,GAAyB,mBAAXC,QAAyBD,EAAIG,cAAgBF,QAAUD,IAAQC,OAAOG,UAAY,gBAAkBJ,CAC3H,EAGKD,EAAQC,EACjB,CAQA,SAASK,EAAkBC,EAAQC,GACjC,IAAK,IAAIC,EAAI,EAAGA,EAAID,EAAME,OAAQD,IAAK,CACrC,IAAIE,EAAaH,EAAMC,GACvBE,EAAWC,WAAaD,EAAWC,aAAc,EACjDD,EAAWE,cAAe,EACtB,UAAWF,IAAYA,EAAWG,UAAW,GACjDC,OAAOC,eAAeT,EAAQI,EAAWM,IAAKN,EAChD,CACF,CAQA,SAASO,EAAgBjB,EAAKgB,EAAKE,GAYjC,OAXIF,KAAOhB,EACTc,OAAOC,eAAef,EAAKgB,EAAK,CAC9BE,MAAOA,EACPP,YAAY,EACZC,cAAc,EACdC,UAAU,IAGZb,EAAIgB,GAAOE,EAGNlB,CACT,CAEA,SAASmB,EAAQC,EAAQC,GACvB,IAAIC,EAAOR,OAAOQ,KAAKF,GAEvB,GAAIN,OAAOS,sBAAuB,CAChC,IAAIC,EAAUV,OAAOS,sBAAsBH,GACvCC,IAAgBG,EAAUA,EAAQC,QAAO,SAAUC,GACrD,OAAOZ,OAAOa,yBAAyBP,EAAQM,GAAKf,UACtD,KACAW,EAAKM,KAAKC,MAAMP,EAAME,EACxB,CAEA,OAAOF,CACT,CAqCA,SAASQ,EAAgBC,GAIvB,OAHAD,EAAkBhB,OAAOkB,eAAiBlB,OAAOmB,eAAiB,SAAyBF,GACzF,OAAOA,EAAEG,WAAapB,OAAOmB,eAAeF,EAC9C,EACOD,EAAgBC,EACzB,CAEA,SAASI,EAAgBJ,EAAGK,GAM1B,OALAD,EAAkBrB,OAAOkB,gBAAkB,SAAyBD,EAAGK,GAErE,OADAL,EAAEG,UAAYE,EACPL,CACT,EAEOI,EAAgBJ,EAAGK,EAC5B,CAUA,SAASC,EAA2BC,EAAMC,GACxC,OAAIA,GAAyB,iBAATA,GAAqC,mBAATA,EATlD,SAAgCD,GAC9B,QAAa,IAATA,EACF,MAAM,IAAIE,eAAe,6DAG3B,OAAOF,CACT,CAOSG,CAAuBH,GAHrBC,CAIX,CAEA,IAAIG,EAAU,CAAC,EASf,SAASC,EAAIC,EAAY5B,EAAK6B,GAC5B,OALF,SAAmBD,GACjB,OAAOA,OACT,CAGME,CAAUF,GACLC,EAVX,SAAmBD,GACjB,OAAsB,OAAfA,GAA+C,WAAxB7C,EAAQ6C,IAAsD,mBAAnBA,EAAWD,KAAgD,mBAAnBC,EAAWG,GAC9H,CAWMC,CAAUJ,GACLA,EAAWG,IAAI/B,GAAO4B,EAAWD,IAAI3B,GAAO6B,EAG9CI,eAAeV,KAAKK,EAAY5B,GAAO4B,EAAW5B,GAAO6B,CAClE,CACA,SAASK,EAAMN,EAAYO,EAASN,GAGlC,IAFA,IAAIrC,EAAI,EAEDA,IAAM2C,EAAQ1C,QAGnB,IAFAmC,EAAaD,EAAIC,EAAYO,EAAQ3C,KAAMkC,MAExBA,EACjB,OAAOG,EAIX,OAAOD,CACT,CAEA,SAASQ,EAAMC,GACb,IAAIC,EAAOC,UAAU9C,OAAS,QAAsB+C,IAAjBD,UAAU,GAAmBA,UAAU,GAAK,CAAC,EAC5EE,EAAOF,UAAU9C,OAAS,QAAsB+C,IAAjBD,UAAU,GAAmBA,UAAU,GAAK,CAAC,EAC5EG,EAKN,SAAuBJ,EAAMG,GAC3B,OAAO,SAAUE,GACf,GAAoB,iBAATA,EACT,OAAO,IAAAC,IAAGH,EAAKE,GAAOL,EAAKK,IACtB,GAAIE,MAAMC,QAAQH,GACvB,OAAO,IAAAC,IAAGV,EAAMO,EAAME,GAAOT,EAAMI,EAAMK,IAG3C,MAAM,IAAII,UAAU,0CAA4CJ,EAClE,CACF,CAfkBK,CAAcV,EAAMG,GAChCQ,EAAYZ,GAAUvC,OAAOQ,KAzGnC,SAAwBhB,GACtB,IAAK,IAAIE,EAAI,EAAGA,EAAI+C,UAAU9C,OAAQD,IAAK,CACzC,IAAI0D,EAAyB,MAAhBX,UAAU/C,GAAa+C,UAAU/C,GAAK,CAAC,EAEhDA,EAAI,EACNW,EAAQ+C,GAAQ,GAAMC,SAAQ,SAAUnD,GACtCC,EAAgBX,EAAQU,EAAKkD,EAAOlD,GACtC,IACSF,OAAOsD,0BAChBtD,OAAOuD,iBAAiB/D,EAAQQ,OAAOsD,0BAA0BF,IAEjE/C,EAAQ+C,GAAQC,SAAQ,SAAUnD,GAChCF,OAAOC,eAAeT,EAAQU,EAAKF,OAAOa,yBAAyBuC,EAAQlD,GAC7E,GAEJ,CAEA,OAAOV,CACT,CAuFwCgE,CAAe,CAAC,EAAGb,EAAM,CAAC,EAAGH,IACnE,OAAOW,EAAUM,MAAMb,EACzB,CA0CA,QA1BA,SAAUc,GAGR,SAASC,IAGP,OApLJ,SAAyBC,EAAUC,GACjC,KAAMD,aAAoBC,GACxB,MAAM,IAAIZ,UAAU,oCAExB,CA8KIa,CAAgBlG,KAAM+F,GAEfpC,EAA2B3D,KAAMoD,EAAgB2C,GAAwB5C,MAAMnD,KAAM6E,WAC9F,CArKF,IAAsBoB,EAAaE,EAAYC,EA+K7C,OAxHF,SAAmBC,EAAUC,GAC3B,GAA0B,mBAAfA,GAA4C,OAAfA,EACtC,MAAM,IAAIjB,UAAU,sDAGtBgB,EAAS3E,UAAYU,OAAOmE,OAAOD,GAAcA,EAAW5E,UAAW,CACrED,YAAa,CACXe,MAAO6D,EACPlE,UAAU,EACVD,cAAc,KAGdoE,GAAY7C,EAAgB4C,EAAUC,EAC5C,CA2FEE,CAAUT,EAAwBD,GA/JdG,EAuKPF,EAvKoBI,EAuKI,CAAC,CACpC7D,IAAK,wBACLE,MAAO,SAA+BiE,GACpC,IAAIC,EAAY7B,UAAU9C,OAAS,QAAsB+C,IAAjBD,UAAU,GAAmBA,UAAU,GAAK,CAAC,EACrF,OAAQH,EAAM1E,KAAK2G,cAAe3G,KAAK6B,MAAO4E,EAAW,mBAAqB/B,EAAM1E,KAAK4G,eAAgB5G,KAAK6G,MAAOH,EAAW,iBAClI,IA3KEP,GAAYxE,EAAkBsE,EAAYvE,UAAWyE,GACrDC,GAAazE,EAAkBsE,EAAaG,GA6KzCL,CACT,CAlBA,CAkBE,yDC5MF,MAAMe,EAAgBC,IACpB,MAAMC,EAAYD,EAAIrG,QAAQ,MAAO,KAAKA,QAAQ,MAAO,KAEzD,IACE,OAAOuG,mBAAmBD,EAC5B,CAAE,MACA,OAAOA,CACT,GAGa,MAAME,UAAcnB,EAAuBtE,cAAA,SAAAoD,WAAAtC,IAAA,qBAiBxC4E,IAC0B,IAAnCC,IAAAD,GAAGtD,KAAHsD,EAAY,kBACRL,EAAcK,EAAIzG,QAAQ,sBAAuB,MAEX,IAA1C0G,IAAAD,GAAGtD,KAAHsD,EAAY,yBACRL,EAAcK,EAAIzG,QAAQ,8BAA+B,UADlE,IAGD6B,IAAA,qBAEe8E,IACd,IAAI,cAAEC,GAAkBtH,KAAK6B,MAE7B,OAAOyF,EAAcC,eAAeF,EAAM,GAC3C,CAEDG,SACE,IAAI,aAAEC,EAAY,WAAEC,EAAU,cAAEJ,EAAa,OAAEK,EAAM,SAAEC,EAAQ,KAAE3C,EAAI,MAAE4C,EAAK,SAAEC,EAAQ,YAAEC,EAAW,gBACjGC,EAAe,iBAAEC,GAAoBjI,KAAK6B,MAC5C,MAAMqG,EAAcT,EAAa,eAC3BU,EAAaV,EAAa,cAC1BW,EAAiBX,EAAa,kBACpC,IAAIY,EAAO,SACPC,EAAQX,GAAUA,EAAO1D,IAAI,SAWjC,IARMgB,GAAQqD,IACZrD,EAAOjF,KAAKuI,aAAcD,KAGtBX,GAAUW,IACdX,EAAS3H,KAAKwI,aAAcvD,KAG1B0C,EACF,OAAOc,EAAAA,cAAA,QAAMC,UAAU,qBACfD,EAAAA,cAAA,QAAMC,UAAU,qBAAsBX,GAAe9C,GACrDwD,EAAAA,cAAA,OAAKE,IAALine truncated
+1 -1
View File
@@ -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"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 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
//# sourceMappingURL=swagger-ui-es-bundle-core.js.map //# sourceMappingURL=swagger-ui-es-bundle-core.js.map
@@ -1,35 +0,0 @@
/*!
* @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":";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 {"version":3,"file":"swagger-ui-es-bundle-core.js","mappings":"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 truncated
+2 -1
View File
@@ -1,2 +1,3 @@
/*! 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 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 (()=>{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=/^.+(:|&colon;)/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
//# sourceMappingURL=swagger-ui-es-bundle.js.map
@@ -1,131 +0,0 @@
/*!
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
View File
@@ -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
+2 -1
View File
@@ -1,2 +1,3 @@
/*! 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 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 !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=/^.+(:|&colon;)/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
//# sourceMappingURL=swagger-ui-standalone-preset.js.map
@@ -1,45 +0,0 @@
/*!
* 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 -1
View File
@@ -1,3 +1,3 @@
.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 .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
/*# sourceMappingURL=swagger-ui.css.map*/ /*# sourceMappingURL=swagger-ui.css.map*/
+1 -1
View File
@@ -1 +1 @@
{"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 {"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
+1 -1
View File
@@ -1,2 +1,2 @@
!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 !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
//# sourceMappingURL=swagger-ui.js.map //# sourceMappingURL=swagger-ui.js.map
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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' && $autodiscover_config['autodiscoverType'] == 'activesync' && getenv('SKIP_SOGO') != "y") { if (strtolower($_GET['Protocol']) == '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') {
+1 -1
View File
@@ -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 && $autodiscover_config['autodiscoverType'] == 'activesync') { if (strpos($data, 'autodiscover/outlook/responseschema') !== false) {
$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
+2 -25
View File
@@ -15,10 +15,6 @@ 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);
@@ -32,8 +28,7 @@ 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: 1em; line-height: 1.25em;
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;
@@ -44,29 +39,11 @@ 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"] {
/* centers the 1em checkbox in the 1.5em line, level with the expand control */ margin-top: 7px;
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;
-8
View File
@@ -139,13 +139,6 @@ 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;
@@ -350,7 +343,6 @@ 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);
+1 -1
View File
@@ -191,7 +191,7 @@
height: 2rem; height: 2rem;
width: 2rem; width: 2rem;
line-height: 2rem; line-height: 2rem;
margin-top: calc(-1rem - 1.5px); margin-top: -15px;
} }
li .dtr-data { li .dtr-data {
+2 -9
View File
@@ -500,10 +500,10 @@ function apppass_login($user, $pass, $extra = null, &$denied = false){
$key = strtolower($extra['service']) . '_access'; $key = strtolower($extra['service']) . '_access';
$mailbox_attributes = json_decode($row['mailbox_attributes'], true); $mailbox_attributes = json_decode($row['mailbox_attributes'], true);
// the protocols of the mailbox only limit direct user access, app passwords have their own // app passwords never grant a protocol the mailbox itself is denied
if ($row['active'] != '1' || $row['mailbox_active'] != '1' || $row['domain_active'] != '1' if ($row['active'] != '1' || $row['mailbox_active'] != '1' || $row['domain_active'] != '1'
|| ($mailbox_attributes['shared'] ?? '0') == '1' || ($mailbox_attributes['shared'] ?? '0') == '1'
|| ($extra['service'] != 'NONE' && $row[$key] != '1')) { || ($extra['service'] != 'NONE' && ($row[$key] != '1' || (isset($mailbox_attributes[$key]) && $mailbox_attributes[$key] != '1')))) {
// right password, but the app password does not allow this login // right password, but the app password does not allow this login
$denied = true; $denied = true;
continue; continue;
@@ -601,13 +601,6 @@ 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;
} }
-7
View File
@@ -35,13 +35,6 @@ 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);
+7 -31
View File
@@ -1394,18 +1394,6 @@ 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(
@@ -1428,11 +1416,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_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`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_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`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');
} }
@@ -1699,11 +1687,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_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`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_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`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');
} }
@@ -2432,7 +2420,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_error": case "ignore_ssl_errors":
$settings[$row["key"]] = boolval($row["value"]); $settings[$row["key"]] = boolval($row["value"]);
break; break;
default: default:
@@ -2456,7 +2444,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_error'] = !isset($settings['ignore_ssl_error']) ? false : $settings['ignore_ssl_error']; $settings['ignore_ssl_errors'] = !isset($settings['ignore_ssl_errors']) ? false : $settings['ignore_ssl_errors'];
} }
return $settings; return $settings;
break; break;
@@ -3118,17 +3106,7 @@ 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;
// only introspect a cached access_token that is not (about to be) expired, if (isset($iam_settings['access_token'])) {
// 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(
@@ -3179,8 +3157,6 @@ 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;
} }
+1 -44
View File
@@ -2524,10 +2524,6 @@ 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(
@@ -2696,10 +2692,6 @@ 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(
@@ -3178,10 +3170,6 @@ 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) {
} }
} }
@@ -4000,9 +3988,6 @@ 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
@@ -4938,9 +4923,6 @@ 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;
@@ -4954,14 +4936,10 @@ 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);
@@ -4999,16 +4977,7 @@ 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) {
$send_as = $sender_acl_row['send_as']; $mailboxdata['sender_acl'][] = $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`
@@ -5947,12 +5916,6 @@ 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,
));
}
} }
} }
@@ -6005,12 +5968,6 @@ 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,
));
}
} }
} }
+7 -14
View File
@@ -614,7 +614,7 @@ jQuery(function($){
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang_edit.ratelimit, title: 'RL',
data: 'rl', data: 'rl',
searchable: false, searchable: false,
orderable: false, orderable: false,
@@ -715,6 +715,10 @@ 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;
@@ -797,25 +801,16 @@ 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,
@@ -937,7 +932,6 @@ 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) {
@@ -1250,6 +1244,8 @@ 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>';
@@ -1336,9 +1332,6 @@ 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,
-4
View File
@@ -1087,18 +1087,14 @@ 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;
+1 -2
View File
@@ -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. mailbox size", "mailbox_quota": "Max. size of a mailbox",
"mailboxes": "Mailboxes", "mailboxes": "Mailboxes",
"max_aliases": "Max. aliases", "max_aliases": "Max. aliases",
"max_mailboxes": "Max. possible mailboxes", "max_mailboxes": "Max. possible mailboxes",
@@ -1429,7 +1429,6 @@
"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"
+5
View File
@@ -1074,6 +1074,7 @@
"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",
@@ -1332,6 +1333,10 @@
"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": {
+1
View File
@@ -95,6 +95,7 @@
"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"
+10 -16
View File
@@ -416,24 +416,18 @@ function recursiveBase64StrToArrayBuffer(obj) {
return navigator.credentials.create(json); return navigator.credentials.create(json);
}).then(function(cred) { }).then(function(cred) {
return { return {
clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON), id: cred.id,
rawId: arrayBufferToBase64(cred.rawId),
response: {
attestationObject: arrayBufferToBase64(cred.response.attestationObject), attestationObject: arrayBufferToBase64(cred.response.attestationObject),
key_id: keyId, clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON)
tfa_method: 'webauthn', },
confirm_password: '' type: cred.type
}; };
}).then(JSON.stringify).then(function(credentialData) { }).then(function(credData) {
// send the attestation to the JSON API, like the regular WebAuthn modal does, $('#setup_webauthn_register_data').val(JSON.stringify(credData));
// so that the credential is parsed and stored correctly $('#setup_webauthn_reg_form input[name=set_tfa]').val('1');
return window.fetch('/api/v1/add/webauthn-tfa-registration', {method: 'POST', body: credentialData, cache: 'no-cache'}); $('#setup_webauthn_reg_form').submit();
}).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');
}); });
-2
View File
@@ -97,11 +97,9 @@
<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
View File
@@ -68,7 +68,7 @@ services:
- redis - redis
clamd-mailcow: clamd-mailcow:
image: ghcr.io/mailcow/clamd:1.4.6-2 image: ghcr.io/mailcow/clamd:1.4.6-1
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.13-1 image: ghcr.io/mailcow/postfix:3.10.12-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.5 image: ghcr.io/mailcow/postfix-tlspol:1.14.0
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-3 image: ghcr.io/mailcow/nginx:1.30.6-2
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.16 image: ghcr.io/mailcow/dockerapi:2.15
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always
-82
View File
@@ -1,82 +0,0 @@
<?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());
}
}
-76
View File
@@ -1,76 +0,0 @@
<?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());
}
}
@@ -1,111 +0,0 @@
<?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');
}
}
-82
View File
@@ -1,82 +0,0 @@
<?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');
}
}
@@ -1,62 +0,0 @@
<?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'));
}
}
@@ -1,58 +0,0 @@
<?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');
}
}
@@ -1,94 +0,0 @@
<?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'));
}
}
-120
View File
@@ -1,120 +0,0 @@
<?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');
}
}
}
-43
View File
@@ -1,43 +0,0 @@
<?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'));
}
}
-150
View File
@@ -1,150 +0,0 @@
<?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);
}
}
-71
View File
@@ -1,71 +0,0 @@
<?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']]]);
}
}
-62
View File
@@ -1,62 +0,0 @@
<?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'));
}
}
@@ -1,57 +0,0 @@
<?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'));
}
}
@@ -1,68 +0,0 @@
<?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'));
}
}
-115
View File
@@ -1,115 +0,0 @@
<?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];
}
}
@@ -1,66 +0,0 @@
<?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]]]);
}
}
-152
View File
@@ -1,152 +0,0 @@
<?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]);
}
}
-34
View File
@@ -1,34 +0,0 @@
<?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');
}
}
-70
View File
@@ -1,70 +0,0 @@
<?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());
}
}
@@ -1,56 +0,0 @@
<?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]);
}
}
@@ -1,59 +0,0 @@
<?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());
}
}
-78
View File
@@ -1,78 +0,0 @@
<?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'));
}
}
@@ -1,31 +0,0 @@
<?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());
}
}
-118
View File
@@ -1,118 +0,0 @@
<?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');
}
}
-266
View File
@@ -1,266 +0,0 @@
<?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;
}
@@ -1,3 +0,0 @@
<?php
// Stand-in for data/web/inc/lib/ssp.class.php in the API harness: the class SSP is a recorder (runner.php autoloader).
@@ -1,11 +0,0 @@
<?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;
}
@@ -1,52 +0,0 @@
<?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]]]);
}
}
@@ -1,84 +0,0 @@
<?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);
}
}
@@ -1,193 +0,0 @@
<?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());
}
}
@@ -1,91 +0,0 @@
<?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];
}
}
@@ -1,78 +0,0 @@
<?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);
}
}
@@ -1,52 +0,0 @@
<?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'));
}
}
@@ -1,49 +0,0 @@
<?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']]]);
}
}
@@ -1,136 +0,0 @@
<?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());
}
}
@@ -1,111 +0,0 @@
<?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());
}
}
-194
View File
@@ -1,194 +0,0 @@
<?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());
}
}
-155
View File
@@ -1,155 +0,0 @@
<?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';
}
}
-131
View File
@@ -1,131 +0,0 @@
<?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());
}
}
-115
View File
@@ -1,115 +0,0 @@
<?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;
}
}
-116
View File
@@ -1,116 +0,0 @@
<?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);
}
}
@@ -1,62 +0,0 @@
<?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'));
}
}
-86
View File
@@ -1,86 +0,0 @@
<?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);
}
}
@@ -1,85 +0,0 @@
<?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);
}
}
-113
View File
@@ -1,113 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Auth;
use PDO;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
require_once __DIR__ . '/../Support/legacy_auth_functions.php';
require_once __DIR__ . '/../../../data/web/inc/functions.auth.inc.php';
// Legacy apppass_login() with its own query, on an in-memory SQLite database
class AppPasswordLoginTest extends TestCase
{
private const PROTOCOLS = ['imap_access', 'smtp_access', 'pop3_access', 'sieve_access', 'eas_access', 'dav_access'];
private PDO $pdo;
protected function setUp(): void
{
$this->pdo = new PDO('sqlite::memory:');
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->sqliteCreateFunction('regexp', fn ($pattern, $value) => preg_match('/' . $pattern . '/', (string)$value) === 1, 2);
$this->pdo->exec("CREATE TABLE `domain` (`domain` TEXT PRIMARY KEY, `active` INTEGER NOT NULL DEFAULT 1)");
$this->pdo->exec("CREATE TABLE `mailbox` (`username` TEXT PRIMARY KEY, `domain` TEXT, `kind` TEXT NOT NULL DEFAULT '',
`active` INTEGER NOT NULL DEFAULT 1, `attributes` TEXT)");
$this->pdo->exec("CREATE TABLE `app_passwd` (`id` INTEGER PRIMARY KEY, `name` TEXT, `mailbox` TEXT, `password` TEXT,
`active` INTEGER NOT NULL DEFAULT 1, " . implode(', ', array_map(fn ($p) => "`$p` INTEGER NOT NULL DEFAULT 0", self::PROTOCOLS)) . ")");
$GLOBALS['pdo'] = $this->pdo;
$_SESSION = [];
}
protected function tearDown(): void
{
unset($GLOBALS['pdo']);
$_SESSION = [];
}
public static function logins(): array
{
$off = array_fill_keys(self::PROTOCOLS, '0');
return [
// [mailbox, domain active, app password, service, password, result, denied]
'imap' => [[], 1, ['imap_access' => 1], 'imap', 'app-secret', 'user', false],
'imap, mailbox imap off' => [['attributes' => $off], 1, ['imap_access' => 1, 'smtp_access' => 1], 'imap', 'app-secret', 'user', false],
'smtp, mailbox smtp off' => [['attributes' => $off], 1, ['imap_access' => 1, 'smtp_access' => 1], 'smtp', 'app-secret', 'user', false],
'eas, mailbox eas off' => [['attributes' => $off], 1, ['eas_access' => 1], 'EAS', 'app-secret', 'user', false],
'dav, mailbox dav off' => [['attributes' => $off], 1, ['dav_access' => 1], 'DAV', 'app-secret', 'user', false],
// denied: right password, login not allowed
'protocol not in app password' => [[], 1, ['imap_access' => 1, 'smtp_access' => 1], 'pop3', 'app-secret', false, true],
'app password inactive' => [[], 1, ['imap_access' => 1, 'active' => 0], 'imap', 'app-secret', false, true],
'mailbox inactive' => [['active' => 0], 1, ['imap_access' => 1], 'imap', 'app-secret', false, true],
'mailbox login disabled' => [['active' => 2], 1, ['imap_access' => 1], 'imap', 'app-secret', false, true],
'domain inactive' => [[], 0, ['imap_access' => 1], 'imap', 'app-secret', false, true],
'shared mailbox' => [['attributes' => ['shared' => '1']], 1, ['imap_access' => 1], 'imap', 'app-secret', false, true],
// failed: counted by netfilter
'wrong password' => [[], 1, ['imap_access' => 1], 'imap', 'wrong', false, false],
'resource mailbox' => [['kind' => 'location'], 1, ['imap_access' => 1], 'imap', 'app-secret', false, false],
];
}
#[DataProvider('logins')]
public function testLogin(array $mailbox, int $domainActive, array $appPasswd, string $service, string $password, string|false $result, bool $denied): void
{
$this->addMailbox($mailbox, $domainActive);
$id = $this->addAppPasswd($appPasswd);
$isDenied = false;
$this->assertSame($result, apppass_login('user@example.org', $password, ['service' => $service, 'is_internal' => true], $isDenied));
$this->assertSame($denied, $isDenied);
if ($result !== false) {
$this->assertEquals($id, $_SESSION['app_passwd_id']);
}
}
public function testUiLoginWithoutServiceIsRefused(): void
{
$this->addMailbox([], 1);
$this->addAppPasswd(array_fill_keys(self::PROTOCOLS, 1));
$this->assertFalse(apppass_login('user@example.org', 'app-secret', ['is_internal' => false]));
}
public function testOtherAppPasswordOfTheMailboxIsTried(): void
{
$this->addMailbox(['attributes' => array_fill_keys(self::PROTOCOLS, '0')], 1);
$this->addAppPasswd(['eas_access' => 1], 'other-secret');
$id = $this->addAppPasswd(['imap_access' => 1]);
$this->assertSame('user', apppass_login('user@example.org', 'app-secret', ['service' => 'imap', 'is_internal' => true]));
$this->assertEquals($id, $_SESSION['app_passwd_id']);
}
private function addMailbox(array $mailbox, int $domainActive): void
{
$attributes = array_merge(array_fill_keys(self::PROTOCOLS, '1'), ['shared' => '0'], $mailbox['attributes'] ?? []);
$this->pdo->prepare("INSERT INTO `domain` (`domain`, `active`) VALUES ('example.org', ?)")->execute([$domainActive]);
$this->pdo->prepare("INSERT INTO `mailbox` (`username`, `domain`, `kind`, `active`, `attributes`) VALUES ('user@example.org', 'example.org', ?, ?, ?)")
->execute([$mailbox['kind'] ?? '', $mailbox['active'] ?? 1, json_encode($attributes)]);
}
private function addAppPasswd(array $appPasswd, string $password = 'app-secret'): int
{
$row = array_merge(array_fill_keys(self::PROTOCOLS, 0), ['active' => 1], $appPasswd);
$hash = '{BLF-CRYPT}' . password_hash($password, PASSWORD_BCRYPT, ['cost' => 4]);
$this->pdo->prepare("INSERT INTO `app_passwd` (`name`, `mailbox`, `password`, " . implode(', ', array_map(fn ($c) => "`$c`", array_keys($row))) . ")
VALUES ('test', 'user@example.org', ?, " . implode(', ', array_fill(0, count($row), '?')) . ")")
->execute(array_merge([$hash], array_values($row)));
return (int)$this->pdo->lastInsertId();
}
}
@@ -1,11 +0,0 @@
<?php
// Stand-in for verify_hash() of data/web/inc/functions.inc.php, used by AppPasswordLoginTest only:
// app passwords are stored as {BLF-CRYPT} (bcrypt), the default scheme of hash_password().
if (!function_exists('verify_hash')) {
function verify_hash($hash, $password)
{
return str_starts_with($hash, '{BLF-CRYPT}') && password_verify($password, substr($hash, strlen('{BLF-CRYPT}')));
}
}
+1 -2
View File
@@ -4,8 +4,7 @@
"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": {
+2 -235
View File
@@ -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": "4211ddb7f0a4ed07b766e52d30177798", "content-hash": "9a4f6f2c4beb80cf38c831d11167da8d",
"packages": [], "packages": [],
"packages-dev": [ "packages-dev": [
{ {
@@ -1721,239 +1721,6 @@
], ],
"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",
@@ -2015,5 +1782,5 @@
"platform-overrides": { "platform-overrides": {
"php": "8.2.29" "php": "8.2.29"
}, },
"plugin-api-version": "2.11.0" "plugin-api-version": "2.9.0"
} }
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff. Show more