mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-11-11 02:45:23 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03ce9fe879 | ||
|
|
c304040140 | ||
|
|
7a1b68c5e2 | ||
|
|
ce2062db5f | ||
|
|
46c9ec90b0 | ||
|
|
4484cfcd42 |
No files matched your search
@@ -3,11 +3,6 @@
|
||||
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||
# 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
|
||||
get_ipv6_support() {
|
||||
# ---- helper: probe external IPv6 connectivity without DNS ----
|
||||
@@ -42,9 +37,6 @@ get_ipv6_support() {
|
||||
if _probe_ipv6_connectivity; then
|
||||
DETECTED_IPV6=true
|
||||
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
|
||||
DETECTED_IPV6=false
|
||||
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
|
||||
}
|
||||
|
||||
_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
|
||||
configure_ipv6() {
|
||||
# detect manual override if mailcow.conf is present
|
||||
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
|
||||
elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
|
||||
@@ -233,23 +203,19 @@ configure_ipv6() {
|
||||
MANUAL_SETTING=""
|
||||
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
|
||||
|
||||
if [[ "$DETECTED_IPV6" != "true" && "${MANUAL_SETTING,,}" == "true" ]] && _has_public_ipv6; then
|
||||
echo -e "${YELLOW}Keeping ENABLE_IPV6=true, the host has a public IPv6 address.${NC}"
|
||||
DETECTED_IPV6=true
|
||||
fi
|
||||
|
||||
# if user manually set it, check for mismatch
|
||||
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 "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"
|
||||
@@ -258,7 +224,16 @@ configure_ipv6() {
|
||||
fi
|
||||
|
||||
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"
|
||||
}
|
||||
@@ -586,8 +586,8 @@ class DockerApi:
|
||||
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")
|
||||
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 '
|
||||
'case "$folder" in Shared|Shared/*) continue;; esac; ' + action + ' || exit 1; done && doveadm acl recalc -u "$ACL_USER"')
|
||||
script = ('set -o pipefail; doveadm mailbox list -u "$ACL_USER" | while IFS= read -r folder; do '
|
||||
'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],
|
||||
environment={ 'ACL_USER': user, 'ACL_ID': id, 'ACL_RIGHTS': ' '.join(rights) })
|
||||
return self.exec_run_handler('generic', acl_return)
|
||||
|
||||
@@ -4,7 +4,7 @@ catch_non_zero() {
|
||||
${CMD} > /dev/null
|
||||
EC=$?
|
||||
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
|
||||
}
|
||||
source /source_env.sh
|
||||
|
||||
@@ -3,8 +3,7 @@ LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
|
||||
RUN apk upgrade --no-cache && \
|
||||
apk add --no-cache nginx \
|
||||
RUN apk add --no-cache nginx \
|
||||
python3 \
|
||||
py3-pip && \
|
||||
pip install --upgrade pip && \
|
||||
|
||||
@@ -4,7 +4,7 @@ WORKDIR /src
|
||||
ENV CGO_ENABLED=0 \
|
||||
GO111MODULE=on \
|
||||
NOOPT=1 \
|
||||
VERSION=1.14.5
|
||||
VERSION=1.14.0
|
||||
|
||||
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
||||
cd /src/postfix-tlspol && \
|
||||
|
||||
@@ -26,6 +26,7 @@ RUN groupadd -g 102 postfix \
|
||||
postfix \
|
||||
postfix-mysql \
|
||||
postfix-pcre \
|
||||
python3 \
|
||||
redis-tools \
|
||||
sasl2-bin \
|
||||
sudo \
|
||||
@@ -46,6 +47,7 @@ COPY postfix.sh /opt/postfix.sh
|
||||
COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham
|
||||
COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam
|
||||
COPY whitelist_forwardinghosts.sh /usr/local/bin/whitelist_forwardinghosts.sh
|
||||
COPY mailcow-policy.py /usr/local/bin/mailcow-policy.py
|
||||
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
@@ -53,6 +55,7 @@ RUN chmod +x /opt/postfix.sh \
|
||||
/usr/local/bin/rspamd-pipe-ham \
|
||||
/usr/local/bin/rspamd-pipe-spam \
|
||||
/usr/local/bin/whitelist_forwardinghosts.sh \
|
||||
/usr/local/bin/mailcow-policy.py \
|
||||
/usr/local/sbin/stop-supervisor.sh
|
||||
RUN rm -rf /tmp/* /var/tmp/*
|
||||
|
||||
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/python3
|
||||
# Policy service of the mail policies (mailcow-policy in master.cf, spawned by Postfix): reads the policy requests
|
||||
# of smtpd and asks the mailcow UI for the action (nginx:8081/mailpolicy.php). Without an answer the recipient is
|
||||
# deferred; smtpd only asks while policies or internal aliases exist (mysql_mail_policy_maps.cf).
|
||||
|
||||
import http.client
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
from urllib.parse import urlencode
|
||||
|
||||
# one line: DUNNO or a rejection with code and text
|
||||
valid_action = re.compile(rb'DUNNO|[45]\d\d [45]\.\d{1,3}\.\d{1,3} [\x20-\x7e]{1,200}')
|
||||
unavailable = b'DEFER_IF_PERMIT Mail policy check temporarily unavailable'
|
||||
# address of nginx, resolved once per process (a lookup costs more than the check)
|
||||
nginx = None
|
||||
|
||||
def check(request):
|
||||
global nginx
|
||||
body = urlencode(sorted(request.items())).encode()
|
||||
try:
|
||||
if nginx is None:
|
||||
nginx = socket.gethostbyname('nginx')
|
||||
# http.client sets TCP_NODELAY, so the body is not held back until the headers are acknowledged
|
||||
# below the timeout of Postfix (main.cf), so an answer always arrives
|
||||
conn = http.client.HTTPConnection(nginx, 8081, timeout=8)
|
||||
try:
|
||||
conn.request('POST', '/mailpolicy.php', body, {'Host': 'nginx', 'Content-Type': 'application/x-www-form-urlencoded'})
|
||||
response = conn.getresponse()
|
||||
action = response.read()
|
||||
finally:
|
||||
conn.close()
|
||||
except (OSError, http.client.HTTPException):
|
||||
# nginx may have a new address after a restart
|
||||
nginx = None
|
||||
return unavailable
|
||||
return action if response.status == 200 and valid_action.fullmatch(action) else unavailable
|
||||
|
||||
def main():
|
||||
request = {}
|
||||
# attributes as bytes: addresses may contain any 8-bit characters
|
||||
for line in sys.stdin.buffer:
|
||||
line = line.rstrip(b'\r\n')
|
||||
if line:
|
||||
name, separator, value = line.partition(b'=')
|
||||
if separator:
|
||||
request[name] = value
|
||||
continue
|
||||
sys.stdout.buffer.write(b'action=' + check(request) + b'\n\n')
|
||||
sys.stdout.buffer.flush()
|
||||
request = {}
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -15,6 +15,21 @@ until dig +short mailcow.email > /dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Wait for updated schema (DB_VERSION from docker-compose.yml), the maps read tables the init_db of php-fpm creates on updates
|
||||
DBV_NOW=$(mariadb --ssl=false --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN 2>/dev/null)
|
||||
DBV_WAIT=0
|
||||
while [[ "${DBV_NOW}" != "${DB_VERSION}" ]] && [[ ${DBV_WAIT} -lt 60 ]]; do
|
||||
echo "Waiting for schema update..."
|
||||
sleep 5
|
||||
DBV_WAIT=$((DBV_WAIT + 1))
|
||||
DBV_NOW=$(mariadb --ssl=false --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN 2>/dev/null)
|
||||
done
|
||||
if [[ "${DBV_NOW}" != "${DB_VERSION}" ]]; then
|
||||
echo -e "\e[31mDB schema is ${DBV_NOW}, expected ${DB_VERSION} - starting anyway, check the php-fpm log (init_db)\e[0m"
|
||||
else
|
||||
echo "DB schema is ${DBV_NOW}"
|
||||
fi
|
||||
|
||||
cat <<EOF > /etc/aliases
|
||||
# Autogenerated by mailcow
|
||||
null: /dev/null
|
||||
@@ -224,6 +239,19 @@ query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM transports
|
||||
LIMIT 1;
|
||||
EOF
|
||||
|
||||
# Mail policies: while policies or internal aliases exist, smtpd asks the policy service (mailcow_mail_policy in main.cf)
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_mail_policy_maps.cf
|
||||
# Autogenerated by mailcow
|
||||
user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = unix:/var/run/mysqld/mysqld.sock
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT 'mailcow_mail_policy' FROM DUAL WHERE '%s' != ''
|
||||
AND (EXISTS (SELECT 1 FROM mail_policy_member JOIN mail_policy ON mail_policy.id = mail_policy_member.policy_id
|
||||
WHERE mail_policy.active = '1')
|
||||
OR EXISTS (SELECT 1 FROM alias WHERE internal = '1' AND active IN ('1', '2')));
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf
|
||||
# Autogenerated by mailcow
|
||||
user = ${DBUSER}
|
||||
|
||||
@@ -277,34 +277,9 @@ if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
|
||||
}
|
||||
}
|
||||
"DBL" {
|
||||
# full copy of the DBL rule from modules.d/rbl.conf, pointed at DQS:
|
||||
# the override include replaces the stock rule instead of merging into it
|
||||
# override the defaults for DBL defined in modules.d/rbl.conf
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||
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" {
|
||||
ignore_defaults = true;
|
||||
|
||||
@@ -116,8 +116,8 @@ if ($result === false){
|
||||
exit;
|
||||
}
|
||||
if ($result) {
|
||||
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $service . " from IP " . $client_ip);
|
||||
set_sasl_log($post['username'], $client_ip, $service);
|
||||
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $client_ip);
|
||||
set_sasl_log($post['username'], $client_ip, $post['service']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,6 +199,9 @@ plugin {
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
sieve_vacation_send_from_recipient = yes
|
||||
sieve_redirect_envelope_from = recipient
|
||||
# Notifications are sent with the address of the mailbox (not :from or postmaster), so mail policies apply;
|
||||
# the old notify extension always sends as postmaster and is on by default, so sieve_extensions disables it
|
||||
sieve_notify_mailto_envelope_from = recipient
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
@@ -215,7 +218,7 @@ plugin {
|
||||
quota_warning2 = storage=80%% quota-warning 80 %u
|
||||
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve
|
||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
|
||||
sieve_extensions = +notify +imapflags +vacation-seconds +editheader
|
||||
sieve_extensions = -notify +imapflags +vacation-seconds +editheader
|
||||
sieve_max_script_size = 1M
|
||||
sieve_max_redirects = 100
|
||||
sieve_max_actions = 101
|
||||
|
||||
@@ -94,11 +94,11 @@ location / {
|
||||
}
|
||||
|
||||
location /qhandler {
|
||||
rewrite ^/qhandler/([^/]+)/(.+?)/?$ /qhandler.php?action=$1&hash=$2;
|
||||
rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2;
|
||||
}
|
||||
|
||||
location /edit {
|
||||
rewrite ^/edit/([^/]+)/(.+?)/?$ /edit.php?$1=$2;
|
||||
rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2;
|
||||
}
|
||||
|
||||
location ~ ^/api/v1/(.*)$ {
|
||||
@@ -187,12 +187,6 @@ location /sogo-auth-verify {
|
||||
}
|
||||
|
||||
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_set $user $upstream_http_x_user;
|
||||
auth_request_set $auth $upstream_http_x_auth;
|
||||
|
||||
@@ -50,6 +50,7 @@ postscreen_greet_wait = 3s
|
||||
postscreen_non_smtp_command_enable = no
|
||||
postscreen_pipelining_enable = no
|
||||
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_mail_policy_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
|
||||
$sender_dependent_default_transport_maps,
|
||||
@@ -90,6 +91,7 @@ smtpd_hard_error_limit = ${stress?1}${stress:5}
|
||||
smtpd_helo_required = yes
|
||||
smtpd_proxy_timeout = 600s
|
||||
smtpd_recipient_restrictions = check_recipient_mx_access proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
|
||||
check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_mail_policy_maps.cf,
|
||||
permit_sasl_authenticated,
|
||||
permit_mynetworks,
|
||||
check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
|
||||
@@ -170,5 +172,11 @@ parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks
|
||||
# This Option is added to correctly set the X-Original-To Header when mails are send to lmtp (dovecot)
|
||||
lmtp_destination_recipient_limit=1
|
||||
|
||||
# Mail policies: while policies or internal aliases exist, mysql_mail_policy_maps.cf returns this class and smtpd
|
||||
# asks the policy service for every recipient (mailcow-policy in master.cf); no answer defers the recipient
|
||||
smtpd_restriction_classes = mailcow_mail_policy
|
||||
mailcow_mail_policy = check_policy_service { unix:private/mailcow-policy, timeout=10s, try_limit=1, default_action=DEFER_IF_PERMIT }
|
||||
mailcow-policy_time_limit = 3600s
|
||||
|
||||
# DO NOT EDIT ANYTHING BELOW #
|
||||
# Overrides #
|
||||
@@ -117,10 +117,14 @@ smtp_sender_cleanup unix n - y - 0 cleanup
|
||||
127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh
|
||||
# end whitelist_fwd
|
||||
|
||||
# mail policies: policy service of smtpd, asks the mailcow UI (mailcow_mail_policy in main.cf)
|
||||
mailcow-policy unix - n n - 0 spawn user=nobody argv=/usr/local/bin/mailcow-policy.py
|
||||
|
||||
# start watchdog-specific
|
||||
# logs to local7 (hidden)
|
||||
# logs to local7 (hidden), no mail policy check
|
||||
589 inet n - n - - smtpd
|
||||
-o smtpd_client_restrictions=permit_mynetworks,reject
|
||||
-o smtpd_recipient_restrictions=permit_mynetworks,reject
|
||||
-o syslog_name=watchdog
|
||||
-o syslog_facility=local7
|
||||
-o smtpd_milters=
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
// Mail policy check for one recipient, asked by the Postfix policy service (mailcow-policy in master.cf)
|
||||
// Request: POST with the attributes of the Postfix policy request; answer: the action (DUNNO or the rejection)
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
|
||||
// Do not show errors, we log to using error_log
|
||||
ini_set('error_reporting', 0);
|
||||
// Init database
|
||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
try {
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("MAILPOLICY: " . $e . PHP_EOL);
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_POST['request']) || $_POST['request'] !== 'smtpd_access_policy') {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
$value = fn(string $key): string => is_string($_POST[$key] ?? null) ? $_POST[$key] : '';
|
||||
|
||||
try {
|
||||
$policy = \Mailcow\MailPolicy\MailPolicyModule::create($pdo, \Mailcow\Core\Context::system());
|
||||
echo $policy->api->check($value('sender'), $value('recipient'), $value('sasl_username'), (int)$value('server_port'), $value('helo_name'));
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
// the policy service answers with a temporary error
|
||||
error_log("MAILPOLICY: " . $e->getMessage() . PHP_EOL);
|
||||
http_response_code(500);
|
||||
}
|
||||
@@ -469,35 +469,5 @@ while ($row = array_shift($rows)) {
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
// Start internal aliases
|
||||
|
||||
$stmt = $pdo->query("SELECT `id`, `address`, `domain` FROM `alias` WHERE `active` = '1' AND `internal` = '1'");
|
||||
$aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($alias = array_shift($aliases)) {
|
||||
// build allowed_domains regex and add target domain and alias domains
|
||||
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `active` = '1' AND `target_domain` = :target_domain");
|
||||
$stmt->execute(array(':target_domain' => $alias['domain']));
|
||||
$allowed_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$allowed_domains = array_map(function($item) {
|
||||
return str_replace('.', '\.', $item['alias_domain']);
|
||||
}, $allowed_domains);
|
||||
$allowed_domains[] = str_replace('.', '\.', $alias['domain']);
|
||||
$allowed_domains = implode('|', $allowed_domains);
|
||||
?>
|
||||
internal_alias_<?=$alias['id'];?> {
|
||||
priority = 10;
|
||||
rcpt = "<?=$alias['address'];?>";
|
||||
from = "/^((?!.*@(<?=$allowed_domains;?>)).)*$/";
|
||||
apply "default" {
|
||||
MAILCOW_INTERNAL_ALIAS = 9999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_INTERNAL_ALIAS"
|
||||
]
|
||||
}
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
MX_IMPLICIT {
|
||||
expression = "MX_GOOD & MX_MISSING";
|
||||
score = -0.01;
|
||||
}
|
||||
VIRUS_FOUND {
|
||||
expression = "CLAM_VIRUS & !MAILCOW_WHITE";
|
||||
score = 2000.0;
|
||||
|
||||
@@ -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" {
|
||||
symbols = {
|
||||
"IP_REPUTATION_HAM" {
|
||||
|
||||
@@ -5,6 +5,12 @@ 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 {
|
||||
type = "content";
|
||||
map = "${LOCAL_CONFDIR}/custom/bulk_header.map";
|
||||
@@ -145,6 +151,20 @@ BAD_LANG {
|
||||
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 {
|
||||
type = "user";
|
||||
map = "redis://SMTP_LIMITED_ACCESS";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
timeout = 8.0;
|
||||
symbol_bad_mx = "MX_INVALID";
|
||||
symbol_no_mx = "MX_MISSING";
|
||||
symbol_good_mx = "MX_GOOD";
|
||||
expire = 86400;
|
||||
key_prefix = "rmx";
|
||||
|
||||
@@ -9,5 +9,3 @@ disable_monitoring = true;
|
||||
# instead of silently accepting the message without further processing.
|
||||
soft_reject_on_timeout = true;
|
||||
local_addrs = /etc/rspamd/custom/mailcow_networks.map;
|
||||
# Must not be lower than message_size_limit in postfix main.cf
|
||||
max_message = 134217728;
|
||||
@@ -362,61 +362,6 @@ rspamd_config:register_symbol({
|
||||
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({
|
||||
name = 'TLS_HEADER',
|
||||
type = 'postfilter',
|
||||
@@ -445,8 +390,9 @@ rspamd_config:register_symbol({
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local redis_params = rspamd_parse_redis_server('taghandler')
|
||||
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 tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
||||
|
||||
local function remove_moo_tag()
|
||||
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 '+')
|
||||
local tag = nil
|
||||
local base_user, tag_part = rcpt_user:match('^([^%+]+)%+(.+)$')
|
||||
if base_user and tag_part then
|
||||
tag = tag_part
|
||||
if tagged_rcpt ~= nil then
|
||||
tag = tagged_rcpt
|
||||
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
|
||||
end
|
||||
|
||||
@@ -553,7 +498,8 @@ rspamd_config:register_symbol({
|
||||
else
|
||||
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
|
||||
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({
|
||||
remove_headers = {
|
||||
['Subject'] = 1,
|
||||
@@ -961,9 +907,6 @@ rspamd_config:register_symbol({
|
||||
|
||||
if not seen_cte and rewrite.need_rewrite_ct then
|
||||
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 of headers
|
||||
|
||||
@@ -109,7 +109,7 @@ foreach ($rcpts as $rcpt) {
|
||||
}
|
||||
|
||||
// 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);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||
|
||||
$js_minifier->add('/web/js/site/imapsync.js');
|
||||
$js_minifier->add('/web/js/site/groups.js');
|
||||
$js_minifier->add('/web/js/site/mail-policy.js');
|
||||
$js_minifier->add('/web/js/site/mailbox.js');
|
||||
$js_minifier->add('/web/js/presets/sieveMailbox.js');
|
||||
$js_minifier->add('/web/js/site/pwgen.js');
|
||||
@@ -46,6 +47,7 @@ $template_data = [
|
||||
'lang_edit' => json_encode($lang['edit']),
|
||||
'lang_datatables' => json_encode($lang['datatables']),
|
||||
'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(),
|
||||
'mail_policy_groups' => \Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->policies->groupChoices($domains),
|
||||
];
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
|
||||
@@ -14,19 +14,3 @@ body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* mailcow: inline code in descriptions in the text colour instead of bold purple */
|
||||
.swagger-ui .markdown code,
|
||||
.swagger-ui .renderedMarkdown code {
|
||||
color: inherit;
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
html.dark-mode .swagger-ui .markdown code,
|
||||
html.dark-mode .swagger-ui .renderedMarkdown code {
|
||||
color: inherit;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
@@ -1,6 +1,79 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<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>
|
||||
</html>
|
||||
@@ -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()}));
|
||||
+7963
-14061
File diff suppressed because it is too large.
Load diff
@@ -1,2 +1,3 @@
|
||||
/*! 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=/^.+(:|:)/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 @@
|
||||
{"version":3,"file":"swagger-ui-bundle.js","mappings":"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 truncated
|
||||
{"version":3,"file":"swagger-ui-bundle.js","mappings":"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 truncated
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! For license information please see swagger-ui-es-bundle-core.js.LICENSE.txt */
|
||||
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
|
||||
@@ -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
|
||||
@@ -1,2 +1,3 @@
|
||||
/*! 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=/^.+(:|:)/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 @@
|
||||
{"version":3,"file":"swagger-ui-es-bundle.js","mappings":";UACIA,EADAC,gCCCJC,OAAOC,eAAeC,EAAS,aAAc,CAAEC,OAAO,IACtDD,EAAQE,UAAYF,EAAQG,wBAA0BH,EAAQI,eAAiBJ,EAAQK,oBAAsBL,EAAQM,oBAAsBN,EAAQO,kBAAoBP,EAAQQ,0BAAuB,EACtMR,EAAQQ,qBAAuB,wCAC/BR,EAAQO,kBAAoB,mBAC5BP,EAAQM,oBAAsB,oBAC9BN,EAAQK,oBAAsB,qDAC9BL,EAAQI,eAAiB,oBACzBJ,EAAQG,wBAA0B,CAAC,IAAK,KACxCH,EAAQE,UAAY,4CCPpBF,EAAQ,OAAc,EACtB,IAAIS,EAAc,EAAQ,OA4C1BT,EAAQ,EAjCR,SAASU,YAAYC,GACjB,IAAKA,EACD,OAAOF,EAAYP,UAEvB,IAAIU,EAVsBC,EAWtBC,EAAaH,EACjB,GAKIC,GAJAE,GAbsBD,EAaYC,EAZhBD,EAAIE,QAAQN,EAAYJ,oBAAqB,IAC5CU,QAAQN,EAAYF,mBAAmB,SAAUS,EAAOC,GAC3E,OAAOC,OAAOC,aAAaF,EAC/B,KAUSF,QAAQN,EAAYH,oBAAqB,IACzCS,QAAQN,EAAYJ,oBAAqB,IACzCe,QAEUJ,MAAMP,EAAYJ,sBACzBS,EAAWE,MAAMP,EAAYF,oBAC7BO,EAAWE,MAAMP,EAAYH,2BAChCM,GAAiBA,EAAcS,OAAS,GACjD,IAAIC,EAAeR,EACnB,IAAKQ,EACD,OAAOb,EAAYP,UAEvB,GA9BJ,SAASqB,6BAA6BZ,GAClC,OAAOF,EAAYN,wBAAwBqB,QAAQb,EAAI,KAAO,CAClE,CA4BQY,CAA6BD,GAC7B,OAAOA,EAEX,IAAIG,EAAwBH,EAAaN,MAAMP,EAAYL,gBAC3D,IAAKqB,EACD,OAAOH,EAEX,IAAII,EAAYD,EAAsB,GACtC,OAAIhB,EAAYD,qBAAqBmB,KAAKD,GAC/BjB,EAAYP,UAEhBoB,CACX,8BC5CAtB,EAAQ4B,WAuCR,SAASA,WAAYC,GACnB,IAAIC,EAAOC,QAAQF,GACfG,EAAWF,EAAK,GAChBG,EAAkBH,EAAK,GAC3B,OAAuC,GAA9BE,EAAWC,GAAuB,EAAKA,CAClD,EA3CAjC,EAAQkC,YAiDR,SAASA,YAAaL,GACpB,IAAIM,EAcAC,EAbAN,EAAOC,QAAQF,GACfG,EAAWF,EAAK,GAChBG,EAAkBH,EAAK,GAEvBO,EAAM,IAAIC,EAVhB,SAASC,YAAaV,EAAKG,EAAUC,GACnC,OAAuC,GAA9BD,EAAWC,GAAuB,EAAKA,CAClD,CAQoBM,CAAYV,EAAKG,EAAUC,IAEzCO,EAAU,EAGVC,EAAMR,EAAkB,EACxBD,EAAW,EACXA,EAGJ,IAAKI,EAAI,EAAGA,EAAIK,EAAKL,GAAK,EACxBD,EACGO,EAAUb,EAAIc,WAAWP,KAAO,GAChCM,EAAUb,EAAIc,WAAWP,EAAI,KAAO,GACpCM,EAAUb,EAAIc,WAAWP,EAAI,KAAO,EACrCM,EAAUb,EAAIc,WAAWP,EAAI,IAC/BC,EAAIG,KAAcL,GAAO,GAAM,IAC/BE,EAAIG,KAAcL,GAAO,EAAK,IAC9BE,EAAIG,KAAmB,IAANL,EAGK,IAApBF,IACFE,EACGO,EAAUb,EAAIc,WAAWP,KAAO,EAChCM,EAAUb,EAAIc,WAAWP,EAAI,KAAO,EACvCC,EAAIG,KAAmB,IAANL,GAGK,IAApBF,IACFE,EACGO,EAAUb,EAAIc,WAAWP,KAAO,GAChCM,EAAUb,EAAIc,WAAWP,EAAI,KAAO,EACpCM,EAAUb,EAAIc,WAAWP,EAAI,KAAO,EACvCC,EAAIG,KAAcL,GAAO,EAAK,IAC9BE,EAAIG,KAAmB,IAANL,GAGnB,OAAOE,CACT,EA5FArC,EAAQ4C,cAkHR,SAASA,cAAeC,GAQtB,IAPA,IAAIV,EACAM,EAAMI,EAAMxB,OACZyB,EAAaL,EAAM,EACnBM,EAAQ,GACRC,EAAiB,MAGZZ,EAAI,EAAGa,EAAOR,EAAMK,EAAYV,EAAIa,EAAMb,GAAKY,EACtDD,EAAMG,KAAKC,YAAYN,EAAOT,EAAIA,EAAIY,EAAkBC,EAAOA,EAAQb,EAAIY,IAI1D,IAAfF,GACFX,EAAMU,EAAMJ,EAAM,GAClBM,EAAMG,KACJE,EAAOjB,GAAO,GACdiB,EAAQjB,GAAO,EAAK,IACpB,OAEsB,IAAfW,IACTX,GAAOU,EAAMJ,EAAM,IAAM,GAAKI,EAAMJ,EAAM,GAC1CM,EAAMG,KACJE,EAAOjB,GAAO,IACdiB,EAAQjB,GAAO,EAAK,IACpBiB,EAAQjB,GAAO,EAAK,IACpB,MAIJ,OAAOY,EAAMM,KAAK,GACpB,EA1IA,IALA,IAAID,EAAS,GACTV,EAAY,GACZJ,EAA4B,oBAAfgB,WAA6BA,WAAaC,MAEvDC,EAAO,mEACFpB,EAAI,EAAsBA,EAAboB,KAAwBpB,EAC5CgB,EAAOhB,GAAKoB,EAAKpB,GACjBM,EAAUc,EAAKb,WAAWP,IAAMA,EAQlC,SAASL,QAASF,GAChB,IAAIY,EAAMZ,EAAIR,OAEd,GAAIoB,EAAM,EAAI,EACZ,MAAM,IAAIgB,MAAM,kDAKlB,IAAIzB,EAAWH,EAAIL,QAAQ,KAO3B,OANkB,IAAdQ,IAAiBA,EAAWS,GAMzB,CAACT,EAJcA,IAAaS,EAC/B,EACA,EAAKT,EAAW,EAGtB,CAmEA,SAASmB,YAAaN,EAAOa,EAAOC,GAGlC,IAFA,IAAIxB,EARoByB,EASpBC,EAAS,GACJzB,EAAIsB,EAAOtB,EAAIuB,EAAKvB,GAAK,EAChCD,GACIU,EAAMT,IAAM,GAAM,WAClBS,EAAMT,EAAI,IAAM,EAAK,QACP,IAAfS,EAAMT,EAAI,IACbyB,EAAOX,KAdFE,GADiBQ,EAeMzB,IAdT,GAAK,IACxBiB,EAAOQ,GAAO,GAAK,IACnBR,EAAOQ,GAAO,EAAI,IAClBR,EAAa,GAANQ,IAaT,OAAOC,EAAOR,KAAK,GACrB,CAlGAX,EAAU,IAAIC,WAAW,IAAM,GAC/BD,EAAU,IAAIC,WAAW,IAAM,iCCT/B,MAAMmB,EAAS,EAAQ,OACjBC,EAAU,EAAQ,KAClBC,EACe,mBAAXC,QAAkD,mBAAlBA,OAAY,IAChDA,OAAY,IAAE,8BACd,KAENjE,EAAQkE,OAASA,OACjBlE,EAAQmE,WAyTR,SAASA,WAAY9C,IACdA,GAAUA,IACbA,EAAS,GAEX,OAAO6C,OAAOE,OAAO/C,EACvB,EA7TArB,EAAQqE,kBAAoB,GAE5B,MAAMC,EAAe,WAwDrB,SAASC,aAAclD,GACrB,GAAIA,EAASiD,EACX,MAAM,IAAIE,WAAW,cAAgBnD,EAAS,kCAGhD,MAAMoD,EAAM,IAAInB,WAAWjC,GAE3B,OADAvB,OAAO4E,eAAeD,EAAKP,OAAOS,WAC3BF,CACT,CAYA,SAASP,OAAQU,EAAKC,EAAkBxD,GAEtC,GAAmB,iBAARuD,EAAkB,CAC3B,GAAgC,iBAArBC,EACT,MAAM,IAAIC,UACR,sEAGJ,OAAOC,YAAYH,EACrB,CACA,OAAOI,KAAKJ,EAAKC,EAAkBxD,EACrC,CAIA,SAAS2D,KAAM/E,EAAO4E,EAAkBxD,GACtC,GAAqB,iBAAVpB,EACT,OAqHJ,SAASgF,WAAYC,EAAQC,GACH,iBAAbA,GAAsC,KAAbA,IAClCA,EAAW,QAGb,IAAKjB,OAAOkB,WAAWD,GACrB,MAAM,IAAIL,UAAU,qBAAuBK,GAG7C,MAAM9D,EAAwC,EAA/BO,WAAWsD,EAAQC,GAClC,IAAIV,EAAMF,aAAalD,GAEvB,MAAMgE,EAASZ,EAAIa,MAAMJ,EAAQC,GAE7BE,IAAWhE,IAIboD,EAAMA,EAAIc,MAAM,EAAGF,IAGrB,OAAOZ,CACT,CA3IWQ,CAAWhF,EAAO4E,GAG3B,GAAIW,YAAYC,OAAOxF,GACrB,OAkJJ,SAASyF,cAAeC,GACtB,GAAIC,WAAWD,EAAWrC,YAAa,CACrC,MAAMuC,EAAO,IAAIvC,WAAWqC,GAC5B,OAAOG,gBAAgBD,EAAKE,OAAQF,EAAKG,WAAYH,EAAKjE,WAC5D,CACA,OAAOqE,cAAcN,EACvB,CAxJWD,CAAczF,GAGvB,GAAa,MAATA,EACF,MAAM,IAAI6E,UACR,yHACiD7E,GAIrD,GAAI2F,WAAW3F,EAAOuF,cACjBvF,GAAS2F,WAAW3F,EAAM8F,OAAQP,aACrC,OAAOM,gBAAgB7F,EAAO4E,EAAkBxD,GAGlD,GAAiC,oBAAtB6E,oBACNN,WAAW3F,EAAOiG,oBAClBjG,GAAS2F,WAAW3F,EAAM8F,OAAQG,oBACrC,OAAOJ,gBAAgB7F,EAAO4E,EAAkBxD,GAGlD,GAAqB,iBAAVpB,EACT,MAAM,IAAI6E,UACR,yEAIJ,MAAMqB,EAAUlG,EAAMkG,SAAWlG,EAAMkG,UACvC,GAAe,MAAXA,GAAmBA,IAAYlG,EACjC,OAAOiE,OAAOc,KAAKmB,EAAStB,EAAkBxD,GAGhD,MAAM+E,EAkJR,SAASC,WAAYC,GACnB,GAAIpC,OAAOqC,SAASD,GAAM,CACxB,MAAM7D,EAA4B,EAAtB+D,QAAQF,EAAIjF,QAClBoD,EAAMF,aAAa9B,GAEzB,OAAmB,IAAfgC,EAAIpD,QAIRiF,EAAIT,KAAKpBLine truncated
|
||||
{"version":3,"file":"swagger-ui-es-bundle.js","mappings":"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 truncated
|
||||
@@ -1,2 +1,3 @@
|
||||
/*! 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=/^.+(:|:)/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,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*/
|
||||
@@ -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,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
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"swagger-ui.js","mappings":"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 truncated
|
||||
{"version":3,"file":"swagger-ui.js","mappings":"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 truncated
|
||||
@@ -8,7 +8,7 @@ if(file_exists('inc/vars.local.inc.php')) {
|
||||
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
||||
|
||||
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'] . '"}';
|
||||
}
|
||||
elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') {
|
||||
|
||||
@@ -31,7 +31,7 @@ error_reporting(0);
|
||||
|
||||
$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';
|
||||
if ($autodiscover_config['useEASforOutlook'] == 'yes' &&
|
||||
// Office for macOS does not support EAS
|
||||
|
||||
@@ -15,10 +15,6 @@ table.dataTable>tbody>tr.child ul.dtr-details>li {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.129);
|
||||
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>th.dtr-control:before:hover {
|
||||
background-color: var(--mc-dtr-hover-bg);
|
||||
@@ -32,8 +28,7 @@ table.dataTable td.dt-control:before {
|
||||
color: var(--mc-dtr-color);
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
line-height: 1em;
|
||||
margin-top: calc(-0.5em - 1.5px);
|
||||
line-height: 1.25em;
|
||||
border-radius: 0px;
|
||||
box-shadow: none;
|
||||
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 {
|
||||
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 {
|
||||
vertical-align: middle;
|
||||
}
|
||||
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: 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;
|
||||
margin-top: 7px;
|
||||
}
|
||||
td.dtr-col-lg {
|
||||
min-width: 350px;
|
||||
|
||||
@@ -139,13 +139,6 @@ body {
|
||||
display: inline-block;
|
||||
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 {
|
||||
padding: .25rem .4rem;
|
||||
font-size: .875rem;
|
||||
@@ -350,7 +343,6 @@ code {
|
||||
}
|
||||
.tag-badge .bi {
|
||||
font-size: 12px;
|
||||
line-height: 0;
|
||||
}
|
||||
.tag-badge.btn-badge:hover {
|
||||
filter: brightness(0.9);
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
line-height: 2rem;
|
||||
margin-top: calc(-1rem - 1.5px);
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
li .dtr-data {
|
||||
|
||||
@@ -11,6 +11,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||
|
||||
$js_minifier->add('/web/js/site/imapsync.js');
|
||||
$js_minifier->add('/web/js/site/groups.js');
|
||||
$js_minifier->add('/web/js/site/mail-policy.js');
|
||||
$js_minifier->add('/web/js/site/mailbox.js');
|
||||
$js_minifier->add('/web/js/presets/sieveMailbox.js');
|
||||
$js_minifier->add('/web/js/site/pwgen.js');
|
||||
@@ -45,6 +46,7 @@ $template_data = [
|
||||
'lang_edit' => json_encode($lang['edit']),
|
||||
'lang_datatables' => json_encode($lang['datatables']),
|
||||
'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(),
|
||||
'mail_policy_groups' => \Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->policies->groupChoices($domains),
|
||||
];
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
|
||||
@@ -115,6 +115,17 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'mbox_templates' => ($_SESSION['mailcow_cc_role'] == 'admin') ? mailbox('get', 'mailbox_templates') : [],
|
||||
];
|
||||
}
|
||||
elseif (isset($_GET['mail-policy']) &&
|
||||
is_numeric($_GET['mail-policy'])) {
|
||||
$mail_policy = \Mailcow\MailPolicy\MailPolicyModule::fromGlobals();
|
||||
$result = $mail_policy->api->getPolicy($_GET['mail-policy']);
|
||||
$template = 'edit/mail-policy.twig';
|
||||
// Choices: mailboxes and groups of the domain of the policy
|
||||
$template_data = [
|
||||
'mailboxes' => !empty($result) ? mailbox('get', 'mailboxes', $result['domain']) : [],
|
||||
'mail_policy_groups' => !empty($result) ? $mail_policy->policies->groupChoices([$result['domain']]) : [],
|
||||
];
|
||||
}
|
||||
elseif (isset($_GET['api-key']) &&
|
||||
is_numeric($_GET['api-key'])) {
|
||||
$result = \Mailcow\ApiKey\ApiKeyModule::fromGlobals()->api->getKey($_GET['api-key']);
|
||||
|
||||
@@ -74,6 +74,7 @@ $globalVariables = [
|
||||
'lang_danger' => json_encode($lang['danger']),
|
||||
'lang_syncjobs' => json_encode($lang['syncjobs']),
|
||||
'lang_groups' => json_encode($lang['groups']),
|
||||
'lang_mail_policy' => json_encode($lang['mail_policy'] ?? []),
|
||||
'docker_timeout' => $DOCKER_TIMEOUT,
|
||||
'session_lifetime' => (int)$SESSION_LIFETIME,
|
||||
'csrf_token' => $_SESSION['CSRF']['TOKEN'],
|
||||
|
||||
@@ -601,13 +601,6 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
|
||||
|
||||
// validate mailcow_password
|
||||
$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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -35,13 +35,6 @@ function fwdhost($_action, $_data = null) {
|
||||
);
|
||||
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) {
|
||||
try {
|
||||
$redis->hSet('WHITELISTED_FWD_HOST', $host, $source);
|
||||
|
||||
@@ -1394,18 +1394,6 @@ function set_tfa($_data) {
|
||||
case "webauthn":
|
||||
$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`)
|
||||
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
|
||||
$stmt->execute(array(
|
||||
@@ -1428,11 +1416,11 @@ function set_tfa($_data) {
|
||||
// Block TFA removal if force_tfa policy is active
|
||||
$is_forced_tfa = false;
|
||||
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));
|
||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||
} 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));
|
||||
$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
|
||||
$is_forced_tfa = false;
|
||||
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));
|
||||
$is_forced_tfa = ($stmt_check->fetchColumn() == '1');
|
||||
} 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));
|
||||
$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_tls":
|
||||
case "login_provisioning":
|
||||
case "ignore_ssl_error":
|
||||
case "ignore_ssl_errors":
|
||||
$settings[$row["key"]] = boolval($row["value"]);
|
||||
break;
|
||||
default:
|
||||
@@ -2456,7 +2444,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
if ($settings["authsource"] == "ldap"){
|
||||
$settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl'];
|
||||
$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;
|
||||
break;
|
||||
@@ -3118,17 +3106,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
case "get-keycloak-admin-token":
|
||||
// get access_token for service account of mailcow client
|
||||
if ($iam_settings['authsource'] !== 'keycloak') return false;
|
||||
// only introspect a cached access_token that is not (about to be) expired,
|
||||
// introspecting an expired token makes Keycloak log INTROSPECT_TOKEN_ERROR
|
||||
$token_not_expired = false;
|
||||
if (!empty($iam_settings['access_token'])) {
|
||||
$token_parts = explode('.', $iam_settings['access_token']);
|
||||
if (count($token_parts) === 3) {
|
||||
$token_payload = json_decode(base64_decode(strtr($token_parts[1], '-_', '+/')), true);
|
||||
$token_not_expired = is_array($token_payload) && isset($token_payload['exp']) && intval($token_payload['exp']) > time() + 30;
|
||||
}
|
||||
}
|
||||
if ($token_not_expired) {
|
||||
if (isset($iam_settings['access_token'])) {
|
||||
// check if access_token is valid
|
||||
$url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect";
|
||||
$req = http_build_query(array(
|
||||
@@ -3179,8 +3157,6 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
':key' => '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'];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2432,6 +2432,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
if ($address != $is_now['address']) {
|
||||
// Send-as grants of groups follow the address
|
||||
\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->addressRenamed($is_now['address'], $address);
|
||||
\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->addressRenamed($is_now['address'], $address);
|
||||
}
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -2524,10 +2525,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => $domain,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
));
|
||||
}
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -2696,10 +2693,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => $domain,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
));
|
||||
}
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -3178,10 +3171,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':username' => $username,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
@@ -3307,7 +3296,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
'mailbox_group_imap_grant' => ['owner'],
|
||||
'mailbox_group_sogo_grant' => ['owner'],
|
||||
'mailbox_group_sogo_acl' => ['c_uid'],
|
||||
'mailbox_group_subscription' => ['username', 'owner']
|
||||
'mailbox_group_subscription' => ['username', 'owner'],
|
||||
'mail_policy_member' => ['username']
|
||||
];
|
||||
foreach ($tables as $table => $columns) {
|
||||
foreach ($columns as $column) {
|
||||
@@ -3323,6 +3313,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->sources->onAccountRenamed($old_username, $new_username);
|
||||
// Group memberships and send-as grants follow, group aliases are rebuilt
|
||||
\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->hooks->onMailboxRenamed($old_username, $new_username);
|
||||
// Address entries of mail policies follow
|
||||
\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->addressRenamed($old_username, $new_username);
|
||||
|
||||
// Update c_uid, c_name and mail in _sogo_static_view table
|
||||
$pdo->prepare("UPDATE _sogo_static_view SET c_uid = :new_username, c_name = :new_username2, mail = :new_username3 WHERE c_uid = :old_username")
|
||||
@@ -4000,9 +3992,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
|
||||
continue;
|
||||
}
|
||||
// Keep old array for backward compatibility
|
||||
$data['fixed_sender_aliases'][] = $row['address'];
|
||||
// Split into allowed/blocked for proper display
|
||||
@@ -4938,9 +4927,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
else if ($SaslLogs['service'] == 'SSO') {
|
||||
$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) {
|
||||
$last_imap_login = 0;
|
||||
@@ -4954,14 +4940,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
|
||||
$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_smtp_login'] = $last_smtp_login;
|
||||
$mailboxdata['last_pop3_login'] = $last_pop3_login;
|
||||
$mailboxdata['last_sso_login'] = $last_sso_login;
|
||||
$mailboxdata['last_sogo_login'] = $last_sogo_login;
|
||||
|
||||
if (!isset($_extra) || $_extra != 'reduced') {
|
||||
$rl = ratelimit('get', 'mailbox', $_data);
|
||||
@@ -4999,16 +4981,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(':username' => $_data));
|
||||
$sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($sender_acl_rows as $sender_acl_row) {
|
||||
$send_as = $sender_acl_row['send_as'];
|
||||
if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $send_as != '*') {
|
||||
$in_scope = (substr($send_as, 0, 1) == '@')
|
||||
? hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr($send_as, 1))
|
||||
: hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $send_as);
|
||||
if (!$in_scope) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$mailboxdata['sender_acl'][] = $send_as;
|
||||
$mailboxdata['sender_acl'][] = $sender_acl_row['send_as'];
|
||||
}
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `tag_name`
|
||||
@@ -5947,12 +5920,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => $domain,
|
||||
':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 +5972,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':username' => $username,
|
||||
':tag_name' => $tag,
|
||||
));
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -491,6 +491,86 @@ function init_db_schema()
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
// Mail policies: whom the assigned mailboxes and groups may send to and receive from (Mailcow\MailPolicy)
|
||||
"mail_policy" => array(
|
||||
"cols" => array(
|
||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||
"domain" => "VARCHAR(255) NOT NULL",
|
||||
"name" => "VARCHAR(100) NOT NULL",
|
||||
"description" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
||||
// all, internal or list; the entries are JSON lists of addresses, domains and group:<id>
|
||||
"send_mode" => "VARCHAR(16) NOT NULL DEFAULT 'all'",
|
||||
"send_allow" => "MEDIUMTEXT NOT NULL",
|
||||
"send_block" => "MEDIUMTEXT NOT NULL",
|
||||
"receive_mode" => "VARCHAR(16) NOT NULL DEFAULT 'all'",
|
||||
"receive_allow" => "MEDIUMTEXT NOT NULL",
|
||||
"receive_block" => "MEDIUMTEXT NOT NULL",
|
||||
"reject_text" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("id")
|
||||
),
|
||||
"unique" => array(
|
||||
"domain_name" => array("domain", "name")
|
||||
),
|
||||
"fkey" => array(
|
||||
"fk_mail_policy_domain" => array(
|
||||
"col" => "domain",
|
||||
"ref" => "domain.domain",
|
||||
"delete" => "CASCADE",
|
||||
"update" => "NO ACTION"
|
||||
)
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
// a mailbox or a group (its members and its address) a policy is assigned to
|
||||
"mail_policy_member" => array(
|
||||
"cols" => array(
|
||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||
"policy_id" => "INT NOT NULL",
|
||||
"username" => "VARCHAR(255) NULL DEFAULT NULL",
|
||||
"group_id" => "INT NULL DEFAULT NULL"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("id")
|
||||
),
|
||||
"unique" => array(
|
||||
"policy_mailbox" => array("policy_id", "username"),
|
||||
"policy_group" => array("policy_id", "group_id")
|
||||
),
|
||||
"key" => array(
|
||||
"username" => array("username"),
|
||||
"group_id" => array("group_id")
|
||||
),
|
||||
"fkey" => array(
|
||||
"fk_mail_policy_member_policy" => array(
|
||||
"col" => "policy_id",
|
||||
"ref" => "mail_policy.id",
|
||||
"delete" => "CASCADE",
|
||||
"update" => "NO ACTION"
|
||||
),
|
||||
"fk_mail_policy_member_mailbox" => array(
|
||||
"col" => "username",
|
||||
"ref" => "mailbox.username",
|
||||
"delete" => "CASCADE",
|
||||
"update" => "NO ACTION"
|
||||
),
|
||||
"fk_mail_policy_member_group" => array(
|
||||
"col" => "group_id",
|
||||
"ref" => "mailbox_group.id",
|
||||
"delete" => "CASCADE",
|
||||
"update" => "NO ACTION"
|
||||
)
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"mailbox_group_imap_grant" => array(
|
||||
"cols" => array(
|
||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||
@@ -703,7 +783,8 @@ function init_db_schema()
|
||||
"group_id" => array("group_id")
|
||||
),
|
||||
"key" => array(
|
||||
"domain" => array("domain")
|
||||
"domain" => array("domain"),
|
||||
"internal" => array("internal", "active")
|
||||
),
|
||||
"fkey" => array(
|
||||
"fk_alias_mailbox_group" => array(
|
||||
@@ -1053,7 +1134,8 @@ function init_db_schema()
|
||||
"domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"alias_external_goto" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"mailbox_groups" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||
"mailbox_groups" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"mail_policies" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\MailPolicy\Controller;
|
||||
|
||||
use Mailcow\Core\AccessDenied;
|
||||
use Mailcow\Core\ApiResponse;
|
||||
use Mailcow\Core\UserError;
|
||||
use Mailcow\MailPolicy\Service\PolicyEngine;
|
||||
use Mailcow\MailPolicy\Service\PolicyService;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* HTTP API adapter for json_api.php and edit.php (routes `mail-policy`), and the answer to the Postfix policy
|
||||
* service (data/conf/rspamd/dynmaps/mailpolicy.php). Messages are logged as ['mail_policy', action, 'policy', data].
|
||||
*/
|
||||
class MailPolicyApiController
|
||||
{
|
||||
public function __construct(private PolicyService $policies, private PolicyEngine $engine, private ApiResponse $response)
|
||||
{
|
||||
}
|
||||
|
||||
/** POST add/mail-policy */
|
||||
public function addPolicy(array $attr): bool
|
||||
{
|
||||
return $this->handle('add', $attr, function (array $log) use ($attr): bool {
|
||||
$policy = $this->policies->create($attr);
|
||||
return $this->response->success(['mail_policy_added', htmlspecialchars($policy->name)], $log);
|
||||
});
|
||||
}
|
||||
|
||||
/** POST edit/mail-policy: one message per id */
|
||||
public function editPolicies(?array $items, array $attr): bool
|
||||
{
|
||||
$data = array_merge(['id' => $items], $attr);
|
||||
return $this->handle('edit', $data, function (array $log) use ($data, $attr): bool {
|
||||
$this->policies->requireEditor();
|
||||
return $this->batch((array)($data['id'] ?? []), $log,
|
||||
fn(int $id): array => ['mail_policy_modified', htmlspecialchars($this->policies->update($id, $attr)->name)]);
|
||||
});
|
||||
}
|
||||
|
||||
/** POST delete/mail-policy: one message per id */
|
||||
public function deletePolicies(array $items): bool
|
||||
{
|
||||
return $this->handle('delete', ['id' => $items], function (array $log) use ($items): bool {
|
||||
$this->policies->requireEditor();
|
||||
return $this->batch($items, $log, fn(int $id): array => ['mail_policy_removed', htmlspecialchars($this->policies->delete($id)->name)]);
|
||||
});
|
||||
}
|
||||
|
||||
/** GET mail-policy/all[/<domain,domain>] */
|
||||
public function getPolicies(?string $domains = null): array
|
||||
{
|
||||
$list = array_values(array_filter(array_map('trim', explode(',', (string)$domains)), 'strlen'));
|
||||
return array_values(array_filter(array_map(fn(int $id): array => $this->getPolicy($id), $this->policies->visibleIds($list))));
|
||||
}
|
||||
|
||||
/** GET mail-policy/<id>, also edit.php */
|
||||
public function getPolicy(mixed $id): array
|
||||
{
|
||||
$id = $this->toId($id);
|
||||
$policy = ($id === null) ? null : $this->policies->get($id);
|
||||
return ($policy === null) ? [] : $this->policies->toArray($policy);
|
||||
}
|
||||
|
||||
/** GET mail-policy/test/<sender>/<recipient>[/login]: would the mail be accepted? Sender "-" = null sender; [] when not allowed to ask */
|
||||
public function test(string $sender, string $recipient, bool $loggedIn): array
|
||||
{
|
||||
try {
|
||||
return $this->policies->test(($sender === '-') ? '' : $sender, $recipient, $loggedIn);
|
||||
} catch (UserError) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** functions.mailbox.inc.php: a mailbox or alias was renamed */
|
||||
public function addressRenamed(string $old, string $new): void
|
||||
{
|
||||
$this->policies->addressRenamed($old, $new);
|
||||
}
|
||||
|
||||
/** Answer for the Postfix policy service: DUNNO or the rejection */
|
||||
public function check(string $sender, string $recipient, string $login, int $port, string $helo = ''): string
|
||||
{
|
||||
return $this->engine->check($sender, $recipient, $login, $port, $helo)->action();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one write call: $work gets the log entry and returns the result for process_*_return().
|
||||
* A UserError becomes a danger message, a database error stops the call with one `mysql_error` message.
|
||||
*/
|
||||
private function handle(string $action, mixed $data, callable $work): bool
|
||||
{
|
||||
$log = ['mail_policy', $action, 'policy', $data];
|
||||
try {
|
||||
$result = $work($log);
|
||||
} catch (UserError $e) {
|
||||
$result = $this->response->error($e, $log);
|
||||
} catch (PDOException $e) {
|
||||
$result = $this->response->danger(['mysql_error', $e->getMessage()], $log);
|
||||
}
|
||||
$this->response->flush();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** One message per id: the success message returned by $work(id), or the UserError */
|
||||
private function batch(array $ids, array $log, callable $work): bool
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
try {
|
||||
$this->response->success($work($this->requireId($id)), $log);
|
||||
} catch (UserError $e) {
|
||||
$this->response->error($e, $log);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Integer id from request input ("12", 12); null for anything else */
|
||||
private function toId(mixed $id): ?int
|
||||
{
|
||||
return (is_numeric($id) && (float)$id == (int)(float)$id) ? (int)(float)$id : null;
|
||||
}
|
||||
|
||||
/** @throws AccessDenied for ids that cannot exist */
|
||||
private function requireId(mixed $id): int
|
||||
{
|
||||
return $this->toId($id) ?? throw new AccessDenied();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\MailPolicy;
|
||||
|
||||
use Mailcow\Core\ApiResponse;
|
||||
use Mailcow\Core\Context;
|
||||
use Mailcow\MailPolicy\Controller\MailPolicyApiController;
|
||||
use Mailcow\MailPolicy\Repository\LookupRepository;
|
||||
use Mailcow\MailPolicy\Repository\PolicyRepository;
|
||||
use Mailcow\MailPolicy\Service\PolicyEngine;
|
||||
use Mailcow\MailPolicy\Service\PolicyService;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Composition root of the mail policies: whom mailboxes and groups may send to and receive from, checked by
|
||||
* Postfix at RCPT TO through the policy service (mailcow-policy in master.cf, data/conf/rspamd/dynmaps/mailpolicy.php).
|
||||
*
|
||||
* process_get_return(MailPolicyModule::fromGlobals()->api->getPolicies());
|
||||
* echo MailPolicyModule::create($pdo, Context::system())->api->check($sender, $recipient, $login, $port); // policy service
|
||||
*/
|
||||
class MailPolicyModule
|
||||
{
|
||||
public function __construct(
|
||||
public readonly PolicyService $policies,
|
||||
public readonly PolicyEngine $engine,
|
||||
public readonly MailPolicyApiController $api
|
||||
) {
|
||||
}
|
||||
|
||||
public static function create(PDO $pdo, Context $context): self
|
||||
{
|
||||
$engine = new PolicyEngine(new LookupRepository($pdo));
|
||||
$policies = new PolicyService(new PolicyRepository($pdo), $engine, $context);
|
||||
return new self($policies, $engine, new MailPolicyApiController($policies, $engine, new ApiResponse()));
|
||||
}
|
||||
|
||||
/** Legacy entry point for procedural callers: reads $pdo and $_SESSION */
|
||||
public static function fromGlobals(?Context $context = null): self
|
||||
{
|
||||
return self::create($GLOBALS['pdo'], $context ?? Context::fromSession($_SESSION ?? []));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\MailPolicy\Model;
|
||||
|
||||
/**
|
||||
* Result of the policy check for one recipient: allowed, refused by the send or receive part of a policy, or refused
|
||||
* because the recipient is written in a form the check cannot follow (routing through another address).
|
||||
*/
|
||||
class Decision
|
||||
{
|
||||
private const TEXT = [
|
||||
'send' => 'Sending to this recipient is not allowed by the mail policy of the sender',
|
||||
'receive' => 'This recipient does not accept mail from this sender',
|
||||
'address' => 'This form of the recipient address is not allowed by the mail policy',
|
||||
];
|
||||
|
||||
private function __construct(public readonly bool $allowed, public readonly string $side = '', public readonly ?Policy $policy = null)
|
||||
{
|
||||
}
|
||||
|
||||
public static function allow(): self
|
||||
{
|
||||
return new self(true);
|
||||
}
|
||||
|
||||
/** $side send or receive; without $policy the default text is used */
|
||||
public static function deny(string $side, ?Policy $policy = null): self
|
||||
{
|
||||
return new self(false, $side, $policy);
|
||||
}
|
||||
|
||||
/** The recipient routes through another address (user%domain, domain!user, a second @) */
|
||||
public static function routedAddress(): self
|
||||
{
|
||||
return new self(false, 'address');
|
||||
}
|
||||
|
||||
/** Text of the rejection: the text of the policy or the default */
|
||||
public function text(): string
|
||||
{
|
||||
return ($this->policy !== null && $this->policy->rejectText !== '') ? $this->policy->rejectText : self::defaultText($this->side);
|
||||
}
|
||||
|
||||
public static function defaultText(string $side): string
|
||||
{
|
||||
return self::TEXT[$side] ?? '';
|
||||
}
|
||||
|
||||
/** Answer for the Postfix policy protocol */
|
||||
public function action(): string
|
||||
{
|
||||
return $this->allowed ? 'DUNNO' : '550 5.7.1 ' . $this->text();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\MailPolicy\Model;
|
||||
|
||||
/**
|
||||
* A mail policy (table `mail_policy`): whom its mailboxes and groups may send to and receive from.
|
||||
*
|
||||
* Each direction has a mode: `all` (only the blocked entries are refused), `internal` (the own domain and its
|
||||
* alias domains, plus the allowed entries) or `list` (only the allowed entries). Blocked entries are always
|
||||
* refused. Entries are addresses, domains or `group:<id>`. A sender counts as internal only when it is logged in
|
||||
* to this mailcow (SASL) with a mailbox of the domain, never because of its address.
|
||||
*/
|
||||
class Policy
|
||||
{
|
||||
public const MODES = ['all', 'internal', 'list'];
|
||||
|
||||
public ?int $id = null;
|
||||
public string $domain = '';
|
||||
public string $name = '';
|
||||
public string $description = '';
|
||||
public string $sendMode = 'all';
|
||||
/** @var string[] */
|
||||
public array $sendAllow = [];
|
||||
/** @var string[] */
|
||||
public array $sendBlock = [];
|
||||
public string $receiveMode = 'all';
|
||||
/** @var string[] */
|
||||
public array $receiveAllow = [];
|
||||
/** @var string[] */
|
||||
public array $receiveBlock = [];
|
||||
/** Text of the rejection, '' = default text */
|
||||
public string $rejectText = '';
|
||||
public int $active = 1;
|
||||
/** @var string[] usernames the policy is assigned to */
|
||||
public array $mailboxes = [];
|
||||
/** @var int[] groups the policy is assigned to: their members and their address */
|
||||
public array $groups = [];
|
||||
public ?string $created = null;
|
||||
public ?string $modified = null;
|
||||
|
||||
/** Row of `mail_policy`; assignments are set by the repository */
|
||||
public static function fromRow(array $row): self
|
||||
{
|
||||
$policy = new self();
|
||||
$policy->id = (int)$row['id'];
|
||||
$policy->domain = (string)$row['domain'];
|
||||
$policy->name = (string)$row['name'];
|
||||
$policy->description = (string)($row['description'] ?? '');
|
||||
$policy->sendMode = (string)$row['send_mode'];
|
||||
$policy->sendAllow = self::list($row['send_allow'] ?? null);
|
||||
$policy->sendBlock = self::list($row['send_block'] ?? null);
|
||||
$policy->receiveMode = (string)$row['receive_mode'];
|
||||
$policy->receiveAllow = self::list($row['receive_allow'] ?? null);
|
||||
$policy->receiveBlock = self::list($row['receive_block'] ?? null);
|
||||
$policy->rejectText = (string)($row['reject_text'] ?? '');
|
||||
$policy->active = (int)$row['active'];
|
||||
$policy->created = $row['created'] ?? null;
|
||||
$policy->modified = $row['modified'] ?? null;
|
||||
return $policy;
|
||||
}
|
||||
|
||||
/** Column values for `mail_policy` */
|
||||
public function toRow(): array
|
||||
{
|
||||
return [
|
||||
'domain' => $this->domain,
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'send_mode' => $this->sendMode,
|
||||
'send_allow' => json_encode($this->sendAllow),
|
||||
'send_block' => json_encode($this->sendBlock),
|
||||
'receive_mode' => $this->receiveMode,
|
||||
'receive_allow' => json_encode($this->receiveAllow),
|
||||
'receive_block' => json_encode($this->receiveBlock),
|
||||
'reject_text' => $this->rejectText,
|
||||
'active' => $this->active,
|
||||
];
|
||||
}
|
||||
|
||||
/** Entry of the API and the edit page */
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'domain' => $this->domain,
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'send_mode' => $this->sendMode,
|
||||
'send_allow' => $this->sendAllow,
|
||||
'send_block' => $this->sendBlock,
|
||||
'receive_mode' => $this->receiveMode,
|
||||
'receive_allow' => $this->receiveAllow,
|
||||
'receive_block' => $this->receiveBlock,
|
||||
'reject_text' => $this->rejectText,
|
||||
'active' => $this->active,
|
||||
'mailboxes' => $this->mailboxes,
|
||||
'groups' => $this->groups,
|
||||
'created' => $this->created,
|
||||
'modified' => $this->modified,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return string[] entries of a JSON list column */
|
||||
private static function list(mixed $json): array
|
||||
{
|
||||
$list = is_string($json) ? json_decode($json, true) : null;
|
||||
return is_array($list) ? array_values(array_filter($list, 'is_string')) : [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\MailPolicy\Repository;
|
||||
|
||||
use Mailcow\Core\Repository;
|
||||
use Mailcow\MailPolicy\Model\Policy;
|
||||
|
||||
/**
|
||||
* Read-only lookups of the policy check: mailboxes, aliases, alias domains, groups and the active policies of a
|
||||
* mailbox or group. Addresses are passed in lower case.
|
||||
*/
|
||||
class LookupRepository extends Repository
|
||||
{
|
||||
/** @return array{username: string, domain: string}|null a regular mailbox that is not disabled */
|
||||
public function mailbox(string $username): ?array
|
||||
{
|
||||
$row = $this->query("SELECT `username`, `domain` FROM `mailbox` WHERE `username` = :username AND `kind` = '' AND `active` != '0'",
|
||||
['username' => $username])->fetch();
|
||||
return is_array($row) ? ['username' => strtolower((string)$row['username']), 'domain' => strtolower((string)$row['domain'])] : null;
|
||||
}
|
||||
|
||||
/** Target domain of an active alias domain */
|
||||
public function aliasDomainTarget(string $domain): ?string
|
||||
{
|
||||
$target = $this->query("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'",
|
||||
['domain' => $domain])->fetchColumn();
|
||||
return is_string($target) ? strtolower($target) : null;
|
||||
}
|
||||
|
||||
/** A domain or alias domain Postfix delivers itself (as mysql_virtual_domains_maps.cf: active, no backup MX) */
|
||||
public function isLocalDomain(string $domain): bool
|
||||
{
|
||||
return (bool)$this->query("SELECT 1 FROM `alias_domain` WHERE `alias_domain` = :d1 AND `active` = '1'
|
||||
UNION SELECT 1 FROM `domain` WHERE `domain` = :d2 AND `active` = '1' AND `backupmx` = '0'",
|
||||
['d1' => $domain, 'd2' => $domain])->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stored spelling of a mailbox, alias or temporary alias address. MySQL compares without case and accents, so
|
||||
* Postfix delivers mail for chéf@ to chef@.
|
||||
*/
|
||||
public function storedAddress(string $address): ?string
|
||||
{
|
||||
$stored = $this->query("SELECT `username` FROM `mailbox` WHERE `username` = :a1 UNION SELECT `address` FROM `alias` WHERE `address` = :a2
|
||||
UNION SELECT `address` FROM `spamalias` WHERE `address` = :a3 LIMIT 1", ['a1' => $address, 'a2' => $address, 'a3' => $address])->fetchColumn();
|
||||
return is_string($stored) ? strtolower($stored) : null;
|
||||
}
|
||||
|
||||
/** Stored spelling of a domain or alias domain, see storedAddress() */
|
||||
public function storedDomain(string $domain): ?string
|
||||
{
|
||||
$stored = $this->query("SELECT `domain` FROM `domain` WHERE `domain` = :d1 UNION SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain` = :d2 LIMIT 1",
|
||||
['d1' => $domain, 'd2' => $domain])->fetchColumn();
|
||||
return is_string($stored) ? strtolower($stored) : null;
|
||||
}
|
||||
|
||||
/** Active group with this distribution address */
|
||||
public function groupByAddress(string $address): ?int
|
||||
{
|
||||
$id = $this->query("SELECT `id` FROM `mailbox_group` WHERE `address` = :address AND `active` = '1'", ['address' => $address])->fetchColumn();
|
||||
return ($id === false) ? null : (int)$id;
|
||||
}
|
||||
|
||||
public function groupAddress(int $id): ?string
|
||||
{
|
||||
$address = $this->query("SELECT `address` FROM `mailbox_group` WHERE `id` = :id AND `active` = '1'", ['id' => $id])->fetchColumn();
|
||||
return is_string($address) && $address !== '' ? strtolower($address) : null;
|
||||
}
|
||||
|
||||
/** Member of an active group */
|
||||
public function isGroupMember(int $groupId, string $username): bool
|
||||
{
|
||||
return (bool)$this->query("SELECT 1 FROM `mailbox_group_member` `gm` JOIN `mailbox_group` `g` ON `g`.`id` = `gm`.`group_id`
|
||||
WHERE `gm`.`group_id` = :id AND `gm`.`username` = :username AND `g`.`active` = '1'",
|
||||
['id' => $groupId, 'username' => $username])->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* What Postfix' virtual alias maps find for this key, in their order: an alias (also the own alias of a mailbox
|
||||
* and the alias of a group address) or a temporary alias.
|
||||
*
|
||||
* @return array{kind: string, domain?: string, internal?: bool, owner?: string}|null
|
||||
*/
|
||||
public function aliasTarget(string $address): ?array
|
||||
{
|
||||
$alias = $this->query("SELECT `domain`, `internal` FROM `alias` WHERE `address` = :address AND `active` IN ('1', '2')",
|
||||
['address' => $address])->fetch();
|
||||
if (is_array($alias)) {
|
||||
return ['kind' => 'alias', 'domain' => strtolower((string)$alias['domain']), 'internal' => $alias['internal'] == '1'];
|
||||
}
|
||||
$owner = $this->query("SELECT `goto` FROM `spamalias` WHERE `address` = :address AND (`validity` >= UNIX_TIMESTAMP() OR `permanent` != 0)",
|
||||
['address' => $address])->fetchColumn();
|
||||
return is_string($owner) ? ['kind' => 'temporary', 'owner' => strtolower($owner)] : null;
|
||||
}
|
||||
|
||||
/** An active catch-all alias of the domain has the internal flag (applies to the whole domain) */
|
||||
public function internalCatchAll(string $domain): bool
|
||||
{
|
||||
return (bool)$this->query("SELECT 1 FROM `alias` WHERE `address` = :address AND `internal` = '1' AND `active` IN ('1', '2')",
|
||||
['address' => '@' . $domain])->fetchColumn();
|
||||
}
|
||||
|
||||
/** An active policy with members restricts sending */
|
||||
public function hasSendRules(): bool
|
||||
{
|
||||
return (bool)$this->query("SELECT 1 FROM `mail_policy` `p` JOIN `mail_policy_member` `pm` ON `pm`.`policy_id` = `p`.`id`
|
||||
WHERE `p`.`active` = '1' AND (`p`.`send_mode` != 'all' OR `p`.`send_block` != '[]') LIMIT 1")->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Policy[] active policies of the mailbox: assigned to it or to an active group it is a member of; only
|
||||
* policies of its own domain, so a group with members of other domains cannot restrict them
|
||||
*/
|
||||
public function policiesForMailbox(string $username): array
|
||||
{
|
||||
return $this->policies("SELECT DISTINCT `p`.* FROM `mail_policy` `p`
|
||||
JOIN `mail_policy_member` `pm` ON `pm`.`policy_id` = `p`.`id`
|
||||
JOIN `mailbox` `m` ON `m`.`username` = :u1 AND `m`.`domain` = `p`.`domain`
|
||||
WHERE `p`.`active` = '1' AND (`pm`.`username` = :u2 OR `pm`.`group_id` IN (
|
||||
SELECT `gm`.`group_id` FROM `mailbox_group_member` `gm` JOIN `mailbox_group` `g` ON `g`.`id` = `gm`.`group_id`
|
||||
WHERE `gm`.`username` = :u3 AND `g`.`active` = '1'))
|
||||
ORDER BY `p`.`id`", ['u1' => $username, 'u2' => $username, 'u3' => $username]);
|
||||
}
|
||||
|
||||
/** @return Policy[] active policies assigned to the group */
|
||||
public function policiesForGroup(int $groupId): array
|
||||
{
|
||||
return $this->policies("SELECT DISTINCT `p`.* FROM `mail_policy` `p`
|
||||
JOIN `mail_policy_member` `pm` ON `pm`.`policy_id` = `p`.`id`
|
||||
WHERE `p`.`active` = '1' AND `pm`.`group_id` = :id
|
||||
ORDER BY `p`.`id`", ['id' => $groupId]);
|
||||
}
|
||||
|
||||
/** @return Policy[] */
|
||||
private function policies(string $sql, array $params): array
|
||||
{
|
||||
return array_map(fn(array $row): Policy => Policy::fromRow($row), $this->query($sql, $params)->fetchAll());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\MailPolicy\Repository;
|
||||
|
||||
use Mailcow\Core\Context;
|
||||
use Mailcow\Core\Repository;
|
||||
use Mailcow\MailPolicy\Model\Policy;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Tables `mail_policy` and `mail_policy_member` (the mailboxes and groups a policy is assigned to), plus the
|
||||
* lookups the editing needs. Assignments go with their mailbox, group or domain (foreign keys, ON DELETE CASCADE).
|
||||
*/
|
||||
class PolicyRepository extends Repository
|
||||
{
|
||||
public function find(int $id): ?Policy
|
||||
{
|
||||
$row = $this->query("SELECT * FROM `mail_policy` WHERE `id` = :id", ['id' => $id])->fetch();
|
||||
return is_array($row) ? $this->withMembers(Policy::fromRow($row)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $domains limit to these domains, [] = all visible
|
||||
* @return int[] ids of the policies visible to the caller, ordered by domain and name
|
||||
*/
|
||||
public function visibleIds(Context $context, array $domains = []): array
|
||||
{
|
||||
if ($context->hasAdminRights()) {
|
||||
[$visible, $params] = ['1=1', []];
|
||||
} elseif ($context->isDomainAdmin()) {
|
||||
[$visible, $params] = ["`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :vis_da)",
|
||||
['vis_da' => $context->username]];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
$sql = "SELECT `id` FROM `mail_policy` WHERE $visible";
|
||||
if (!empty($domains)) {
|
||||
$names = [];
|
||||
foreach (array_values($domains) as $i => $domain) {
|
||||
$names[] = ':dom' . $i;
|
||||
$params['dom' . $i] = $domain;
|
||||
}
|
||||
$sql .= " AND `domain` IN (" . implode(', ', $names) . ")";
|
||||
}
|
||||
return array_map('intval', $this->query($sql . " ORDER BY `domain`, `name`", $params)->fetchAll(PDO::FETCH_COLUMN));
|
||||
}
|
||||
|
||||
public function nameExists(string $domain, string $name, ?int $exceptId = null): bool
|
||||
{
|
||||
return (bool)$this->query("SELECT 1 FROM `mail_policy` WHERE `domain` = :domain AND `name` = :name AND `id` != :id",
|
||||
['domain' => $domain, 'name' => $name, 'id' => $exceptId ?? 0])->fetchColumn();
|
||||
}
|
||||
|
||||
/** Inserts or updates the policy with its assignments; returns its id */
|
||||
public function save(Policy $policy): int
|
||||
{
|
||||
return $this->transaction(function () use ($policy): int {
|
||||
if ($policy->id === null) {
|
||||
$policy->id = $this->insertRow('mail_policy', $policy->toRow());
|
||||
} else {
|
||||
$this->updateRow('mail_policy', $policy->id, $policy->toRow());
|
||||
$this->query("DELETE FROM `mail_policy_member` WHERE `policy_id` = :id", ['id' => $policy->id]);
|
||||
}
|
||||
foreach ($policy->mailboxes as $username) {
|
||||
$this->insertRow('mail_policy_member', ['policy_id' => $policy->id, 'username' => $username]);
|
||||
}
|
||||
foreach ($policy->groups as $groupId) {
|
||||
$this->insertRow('mail_policy_member', ['policy_id' => $policy->id, 'group_id' => $groupId]);
|
||||
}
|
||||
return $policy->id;
|
||||
});
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$this->query("DELETE FROM `mail_policy` WHERE `id` = :id", ['id' => $id]);
|
||||
}
|
||||
|
||||
/** Address entries of all lists follow a renamed mailbox or alias */
|
||||
public function renameAddress(string $old, string $new): void
|
||||
{
|
||||
$this->transaction(function () use ($old, $new): void {
|
||||
$columns = ['send_allow', 'send_block', 'receive_allow', 'receive_block'];
|
||||
foreach ($this->query("SELECT `id`, `" . implode('`, `', $columns) . "` FROM `mail_policy`")->fetchAll() as $row) {
|
||||
$changed = [];
|
||||
foreach ($columns as $column) {
|
||||
$entries = json_decode((string)$row[$column], true);
|
||||
if (is_array($entries) && in_array($old, $entries, true)) {
|
||||
$changed[$column] = json_encode(array_values(array_unique(array_map(fn(string $entry): string => ($entry === $old) ? $new : $entry, $entries))));
|
||||
}
|
||||
}
|
||||
if ($changed !== []) {
|
||||
$this->updateRow('mail_policy', (int)$row['id'], $changed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** A primary domain (policies never belong to alias domains) */
|
||||
public function isDomain(string $domain): bool
|
||||
{
|
||||
return (bool)$this->query("SELECT 1 FROM `domain` WHERE `domain` = :domain", ['domain' => $domain])->fetchColumn();
|
||||
}
|
||||
|
||||
/** Domain of a regular mailbox, null if there is none */
|
||||
public function mailboxDomain(string $username): ?string
|
||||
{
|
||||
$domain = $this->query("SELECT `domain` FROM `mailbox` WHERE `username` = :username AND `kind` = ''", ['username' => $username])->fetchColumn();
|
||||
return is_string($domain) ? $domain : null;
|
||||
}
|
||||
|
||||
public function groupDomain(int $id): ?string
|
||||
{
|
||||
$domain = $this->query("SELECT `domain` FROM `mailbox_group` WHERE `id` = :id", ['id' => $id])->fetchColumn();
|
||||
return is_string($domain) ? $domain : null;
|
||||
}
|
||||
|
||||
/** @return array[] id, name, domain, address of the groups in these domains (choices of the forms) */
|
||||
public function groupsIn(array $domains): array
|
||||
{
|
||||
if (empty($domains)) {
|
||||
return [];
|
||||
}
|
||||
$names = [];
|
||||
$params = [];
|
||||
foreach (array_values($domains) as $i => $domain) {
|
||||
$names[] = ':dom' . $i;
|
||||
$params['dom' . $i] = $domain;
|
||||
}
|
||||
return $this->query("SELECT `id`, `name`, `domain`, `address` FROM `mailbox_group` WHERE `domain` IN (" . implode(', ', $names) . ")
|
||||
ORDER BY `domain`, `name`", $params)->fetchAll();
|
||||
}
|
||||
|
||||
private function withMembers(Policy $policy): Policy
|
||||
{
|
||||
foreach ($this->query("SELECT `username`, `group_id` FROM `mail_policy_member` WHERE `policy_id` = :id ORDER BY `username`, `group_id`",
|
||||
['id' => $policy->id])->fetchAll() as $row) {
|
||||
if ($row['username'] !== null) {
|
||||
$policy->mailboxes[] = (string)$row['username'];
|
||||
} elseif ($row['group_id'] !== null) {
|
||||
$policy->groups[] = (int)$row['group_id'];
|
||||
}
|
||||
}
|
||||
return $policy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\MailPolicy\Service;
|
||||
|
||||
use Mailcow\MailPolicy\Model\Decision;
|
||||
use Mailcow\MailPolicy\Model\Policy;
|
||||
use Mailcow\MailPolicy\Repository\LookupRepository;
|
||||
|
||||
/**
|
||||
* Policy check of one recipient, asked by Postfix at RCPT TO (data/conf/rspamd/dynmaps/mailpolicy.php).
|
||||
*
|
||||
* Send part: the policies of the logged-in mailbox (assigned to it or to its groups) and of the mailbox the
|
||||
* sender address belongs to. Receive part: what Postfix delivers the recipient to, looked up in the order of its
|
||||
* virtual alias maps (address, address without +tag, mailbox of an alias domain): the policies of the mailbox, of
|
||||
* the group address or of the owner of a temporary alias, and the `internal` flag of the alias (a catch-all alias
|
||||
* with the flag covers its whole domain). Mail sent to an alias or group reaches its members without their own
|
||||
* policies. Every policy must allow the mail.
|
||||
*
|
||||
* Internal: a recipient in the domain of the policy or one of its alias domains; a sender logged in with a
|
||||
* mailbox of that domain. Entries of a local domain or address count for logged-in senders only, so they cannot
|
||||
* be forged; block entries also match the envelope sender. External entries are compared with the envelope sender.
|
||||
* Addresses are compared the way Postfix finds them: lower case, without trailing dot, in their stored spelling
|
||||
* (MySQL ignores accents). Recipients routed through localhost (user%domain@localhost, domain!user@localhost,
|
||||
* user@domain@localhost) are refused, Postfix would deliver them to the routed address.
|
||||
*
|
||||
* Dovecot submits Sieve redirects, vacation replies and notifications on port 588 without login, SOGo sends
|
||||
* reminders with a service login: mail with the address of a mailbox counts as sent by that mailbox. Other mail of
|
||||
* these services (bounces redirected with the null sender, Sieve rejects) has no known sender: it is checked like
|
||||
* mail from outside and does not leave the mailcow while any policy restricts sending. Quota warnings pass.
|
||||
*/
|
||||
class PolicyEngine
|
||||
{
|
||||
/** Ports of mailcow's own deliveries: quarantine release (590), BCC and archive copies (591) */
|
||||
private const INTERNAL_PORTS = [590, 591];
|
||||
/** Submission of SOGo and Dovecot, without login reachable from mailcow's own network only */
|
||||
private const SERVICE_PORT = 588;
|
||||
/** Domain of the Dovecot master login of mailcow's services (SOGo reminders) */
|
||||
private const SERVICE_LOGIN_DOMAIN = 'mailcow.local';
|
||||
/** HELO of the quota warning copies of Dovecot (quota_notify.py), configured by admins */
|
||||
private const QUOTA_HELO = 'quotanotification';
|
||||
/** Recipient without domain every server accepts (RFC 5321) */
|
||||
private const POSTMASTER = 'postmaster';
|
||||
/** Postfix delivers these destinations itself (mydestination), user%domain and domain!user route from there */
|
||||
private const LOCAL_DESTINATIONS = ['localhost', 'localhost.localdomain'];
|
||||
|
||||
/** Lookups of the current check */
|
||||
private array $cache = [];
|
||||
|
||||
public function __construct(private LookupRepository $lookup)
|
||||
{
|
||||
}
|
||||
|
||||
/** $login: SASL username, '' if not logged in; $port: smtpd port of the request, 0 if unknown; $helo: HELO name */
|
||||
public function check(string $sender, string $recipient, string $login, int $port = 0, string $helo = ''): Decision
|
||||
{
|
||||
$this->cache = [];
|
||||
$recipient = self::address($recipient);
|
||||
if (in_array($port, self::INTERNAL_PORTS, true) || $recipient === '' || $recipient === self::POSTMASTER) {
|
||||
return Decision::allow();
|
||||
}
|
||||
if (self::isRouted($recipient)) {
|
||||
return Decision::routedAddress();
|
||||
}
|
||||
$recipient = $this->stored($recipient);
|
||||
$sender = $this->stored(self::address($sender));
|
||||
$login = self::address($login);
|
||||
$serviceLogin = self::domainOf($login) === self::SERVICE_LOGIN_DOMAIN;
|
||||
$account = null;
|
||||
if ($login !== '' && !$serviceLogin) {
|
||||
$account = $this->cached('mailbox', $login);
|
||||
} elseif ($serviceLogin || $port === self::SERVICE_PORT) {
|
||||
if ($port === self::SERVICE_PORT && !$serviceLogin && strtolower($helo) === self::QUOTA_HELO) {
|
||||
return Decision::allow();
|
||||
}
|
||||
// without a mailbox sender the sending mailbox is unknown
|
||||
$account = $this->senderMailbox($sender);
|
||||
if ($account === null && !$this->cached('isLocalDomain', self::domainOf($recipient)) && $this->lookup->hasSendRules()) {
|
||||
return Decision::deny('send');
|
||||
}
|
||||
}
|
||||
foreach ($this->sendingPolicies($account, $sender) as $policy) {
|
||||
if (!$this->allows($policy->sendMode, $policy->sendAllow, $policy->sendBlock, $this->isInternalRecipient($recipient, $policy->domain),
|
||||
fn(string $entry): bool => $this->recipientMatches($entry, $recipient))) {
|
||||
return Decision::deny('send', $policy);
|
||||
}
|
||||
}
|
||||
foreach ($this->receivingPolicies($recipient) as $policy) {
|
||||
if (!$this->allows($policy->receiveMode, $policy->receiveAllow, $policy->receiveBlock,
|
||||
$account !== null && $account['domain'] === $policy->domain,
|
||||
fn(string $entry, bool $block): bool => $this->senderMatches($entry, $sender, $account, $block))) {
|
||||
return Decision::deny('receive', $policy);
|
||||
}
|
||||
}
|
||||
return Decision::allow();
|
||||
}
|
||||
|
||||
/** Blocked entries first; then the mode decides. $matches($entry, $block) */
|
||||
private function allows(string $mode, array $allow, array $block, bool $internal, callable $matches): bool
|
||||
{
|
||||
foreach ($block as $entry) {
|
||||
if ($matches($entry, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return match ($mode) {
|
||||
'all' => true,
|
||||
'internal' => $internal || $this->any($allow, $matches),
|
||||
'list' => $this->any($allow, $matches),
|
||||
// an unknown mode lets nothing through
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
private function any(array $entries, callable $matches): bool
|
||||
{
|
||||
foreach ($entries as $entry) {
|
||||
if ($matches($entry, false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return array{username: string, domain: string}|null mailbox of the sender address */
|
||||
private function senderMailbox(string $sender): ?array
|
||||
{
|
||||
return str_contains($sender, '@') ? $this->cached('mailbox', $this->resolve(self::untagged($sender))) : null;
|
||||
}
|
||||
|
||||
/** @return Policy[] of the logged-in mailbox and of the mailbox it sends as */
|
||||
private function sendingPolicies(?array $account, string $sender): array
|
||||
{
|
||||
if ($account === null) {
|
||||
return [];
|
||||
}
|
||||
$policies = $this->lookup->policiesForMailbox($account['username']);
|
||||
$sentAs = $this->senderMailbox($sender);
|
||||
if ($sentAs !== null && $sentAs['username'] !== $account['username']) {
|
||||
$policies = array_merge($policies, $this->lookup->policiesForMailbox($sentAs['username']));
|
||||
}
|
||||
return self::unique($policies);
|
||||
}
|
||||
|
||||
/** @return Policy[] of what Postfix delivers the recipient to, see the class comment */
|
||||
private function receivingPolicies(string $recipient): array
|
||||
{
|
||||
// the address, then without +tag; an alias domain maps to the mailbox in its target domain and Postfix
|
||||
// looks up the result again with the +tag
|
||||
[$address, $target] = $this->aliasTarget([$recipient, self::untagged($recipient)]);
|
||||
$resolved = $this->resolve($recipient);
|
||||
if ($target === null && $resolved !== $recipient) {
|
||||
[$address, $target] = $this->aliasTarget([$resolved, self::untagged($resolved)]);
|
||||
}
|
||||
$address ??= self::untagged($resolved);
|
||||
if (($target['kind'] ?? '') === 'temporary') {
|
||||
$address = $target['owner'];
|
||||
}
|
||||
$policies = ($this->cached('mailbox', $address) !== null) ? $this->lookup->policiesForMailbox($address) : [];
|
||||
$group = $this->lookup->groupByAddress($address);
|
||||
if ($group !== null) {
|
||||
$policies = array_merge($policies, $this->lookup->policiesForGroup($group));
|
||||
}
|
||||
// the `internal` flag of an alias or group: only senders of its domain
|
||||
if (!empty($target['internal'])) {
|
||||
$policies[] = $this->internalRule($target['domain']);
|
||||
}
|
||||
foreach (array_unique([self::domainOf($recipient), $this->resolveDomain(self::domainOf($recipient))]) as $domain) {
|
||||
if ($this->lookup->internalCatchAll($domain)) {
|
||||
$policies[] = $this->internalRule($domain);
|
||||
}
|
||||
}
|
||||
return self::unique($policies);
|
||||
}
|
||||
|
||||
/** @return array{0: ?string, 1: ?array} the first key Postfix' virtual alias maps find, with what they find */
|
||||
private function aliasTarget(array $keys): array
|
||||
{
|
||||
foreach (array_unique($keys) as $key) {
|
||||
$target = $this->lookup->aliasTarget($key);
|
||||
if ($target !== null) {
|
||||
return [$key, $target];
|
||||
}
|
||||
}
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
/** Receive rule of the `internal` flag */
|
||||
private function internalRule(string $domain): Policy
|
||||
{
|
||||
$policy = new Policy();
|
||||
$policy->domain = $this->resolveDomain($domain);
|
||||
$policy->receiveMode = 'internal';
|
||||
return $policy;
|
||||
}
|
||||
|
||||
private function isInternalRecipient(string $recipient, string $domain): bool
|
||||
{
|
||||
return $this->resolveDomain(self::domainOf($recipient)) === $domain;
|
||||
}
|
||||
|
||||
private function recipientMatches(string $entry, string $recipient): bool
|
||||
{
|
||||
$address = $this->resolve(self::untagged($recipient));
|
||||
if (str_starts_with($entry, 'group:')) {
|
||||
$id = (int)substr($entry, 6);
|
||||
return $this->cached('groupAddress', $id) === $address
|
||||
|| ($this->cached('mailbox', $address) !== null && $this->cached('isGroupMember', $id, $address));
|
||||
}
|
||||
if (str_contains($entry, '@')) {
|
||||
return $this->resolve($entry) === $address;
|
||||
}
|
||||
return $this->resolveDomain(self::domainOf($address)) === $this->resolveDomain($entry);
|
||||
}
|
||||
|
||||
/** $block: a block entry, which also matches the envelope sender (refusing a forged sender is no risk) */
|
||||
private function senderMatches(string $entry, string $sender, ?array $account, bool $block): bool
|
||||
{
|
||||
$address = $this->resolve(self::untagged($sender));
|
||||
// the sender address of a local entry only counts when logged in: Postfix checked that the login may use it
|
||||
$useAddress = $account !== null || $block;
|
||||
if (str_starts_with($entry, 'group:')) {
|
||||
$id = (int)substr($entry, 6);
|
||||
return ($account !== null && $this->cached('isGroupMember', $id, $account['username']))
|
||||
|| ($useAddress && ($this->cached('groupAddress', $id) === $address
|
||||
|| ($this->cached('mailbox', $address) !== null && $this->cached('isGroupMember', $id, $address))));
|
||||
}
|
||||
if (str_contains($entry, '@')) {
|
||||
return $this->cached('isLocalDomain', self::domainOf($entry))
|
||||
? ($account !== null && $account['username'] === $this->resolve($entry)) || ($useAddress && $address === $this->resolve($entry))
|
||||
: self::untagged($sender) === $entry;
|
||||
}
|
||||
return $this->cached('isLocalDomain', $entry)
|
||||
? ($account !== null && $account['domain'] === $this->resolveDomain($entry))
|
||||
|| ($useAddress && $this->resolveDomain(self::domainOf($address)) === $this->resolveDomain($entry))
|
||||
: self::domainOf($sender) === $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stored spelling of an address of this mailcow: Postfix finds chéf@ as chef@ (MySQL ignores accents). Only
|
||||
* addresses with non-ASCII characters can differ, as they are already in lower case.
|
||||
*/
|
||||
private function stored(string $address): string
|
||||
{
|
||||
if (!preg_match('/[\x80-\xff]/', $address) || !str_contains($address, '@')) {
|
||||
return $address;
|
||||
}
|
||||
foreach (array_unique([$address, self::untagged($address)]) as $key) {
|
||||
$stored = $this->cached('storedAddress', $key);
|
||||
if ($stored !== null) {
|
||||
return $stored;
|
||||
}
|
||||
}
|
||||
$domain = $this->cached('storedDomain', self::domainOf($address));
|
||||
return ($domain === null) ? $address : substr($address, 0, strrpos($address, '@') + 1) . $domain;
|
||||
}
|
||||
|
||||
/** Address in an alias domain as the address in its target domain */
|
||||
private function resolve(string $address): string
|
||||
{
|
||||
$domain = self::domainOf($address);
|
||||
$target = ($domain !== '') ? $this->cached('aliasDomainTarget', $domain) : null;
|
||||
return ($target === null) ? $address : substr($address, 0, strrpos($address, '@') + 1) . $target;
|
||||
}
|
||||
|
||||
private function resolveDomain(string $domain): string
|
||||
{
|
||||
return ($domain === '') ? '' : ($this->cached('aliasDomainTarget', $domain) ?? $domain);
|
||||
}
|
||||
|
||||
/** A lookup, done once per check: long lists ask the same domains and groups again */
|
||||
private function cached(string $method, int|string ...$args): mixed
|
||||
{
|
||||
$key = $method . "\0" . implode("\0", $args);
|
||||
if (!array_key_exists($key, $this->cache)) {
|
||||
$this->cache[$key] = $this->lookup->$method(...$args);
|
||||
}
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
/** @param Policy[] $policies @return Policy[] without repeated policies */
|
||||
private static function unique(array $policies): array
|
||||
{
|
||||
$unique = [];
|
||||
foreach ($policies as $policy) {
|
||||
$unique[$policy->id ?? 'internal:' . $policy->domain] = $policy;
|
||||
}
|
||||
return array_values($unique);
|
||||
}
|
||||
|
||||
/** Lower case, without angle brackets, quotes and trailing dot of the domain, as Postfix resolves it; '' = null sender */
|
||||
private static function address(string $value): string
|
||||
{
|
||||
$value = strtolower(trim($value, " \t\r\n<>"));
|
||||
$at = strrpos($value, '@');
|
||||
return ($at === false) ? $value : str_replace('"', '', substr($value, 0, $at)) . '@' . rtrim(substr($value, $at + 1), '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Postfix would deliver the recipient elsewhere: no domain, or routing through a local destination
|
||||
* (localhost, the IP of the server) with %, ! or a second @ in the local part
|
||||
*/
|
||||
private static function isRouted(string $address): bool
|
||||
{
|
||||
$at = strrpos($address, '@');
|
||||
if ($at === false || $at === 0 || $at === strlen($address) - 1) {
|
||||
return true;
|
||||
}
|
||||
$domain = substr($address, $at + 1);
|
||||
return (in_array($domain, self::LOCAL_DESTINATIONS, true) || str_starts_with($domain, '['))
|
||||
&& strpbrk(substr($address, 0, $at), '@%!') !== false;
|
||||
}
|
||||
|
||||
private static function untagged(string $address): string
|
||||
{
|
||||
$at = strrpos($address, '@');
|
||||
$pos = ($at === false) ? false : strpos(substr($address, 0, $at), '+');
|
||||
return ($pos === false || $pos === 0) ? $address : substr($address, 0, $pos) . substr($address, $at);
|
||||
}
|
||||
|
||||
private static function domainOf(string $address): string
|
||||
{
|
||||
$at = strrpos($address, '@');
|
||||
return ($at === false) ? '' : substr($address, $at + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\MailPolicy\Service;
|
||||
|
||||
use Mailcow\Core\AccessDenied;
|
||||
use Mailcow\Core\Context;
|
||||
use Mailcow\Core\Input;
|
||||
use Mailcow\Core\UserError;
|
||||
use Mailcow\MailPolicy\Model\Decision;
|
||||
use Mailcow\MailPolicy\Model\Policy;
|
||||
use Mailcow\MailPolicy\Repository\PolicyRepository;
|
||||
|
||||
/**
|
||||
* Use cases of mail policies.
|
||||
*
|
||||
* Read: admins all policies (also read-only API keys); domain admins with the `mail_policies` ACL the policies
|
||||
* of their domains.
|
||||
* Edit: admins; domain admins with the ACL for policies of their domains. A policy is assigned to mailboxes and
|
||||
* groups of its own domain; `group:<id>` entries name groups of that domain. Groups need the `mailbox_groups`
|
||||
* ACL as well: without it a domain admin sees no groups and keeps the group entries and assignments.
|
||||
*/
|
||||
class PolicyService
|
||||
{
|
||||
private const MAX_ENTRIES = 500;
|
||||
/** The reject text goes into the SMTP reply: printable ASCII */
|
||||
private const MAX_REJECT_TEXT = 200;
|
||||
private const LISTS = ['send_allow' => 'sendAllow', 'send_block' => 'sendBlock', 'receive_allow' => 'receiveAllow', 'receive_block' => 'receiveBlock'];
|
||||
|
||||
public function __construct(private PolicyRepository $repo, private PolicyEngine $engine, private Context $context)
|
||||
{
|
||||
}
|
||||
|
||||
public function canRead(): bool
|
||||
{
|
||||
return $this->context->hasAdminRights()
|
||||
|| ($this->context->isDomainAdmin() && $this->context->hasAcl('mail_policies'));
|
||||
}
|
||||
|
||||
/** @throws AccessDenied unless the caller may edit policies at all */
|
||||
public function requireEditor(): void
|
||||
{
|
||||
if (!$this->canRead() || $this->context->isReadOnlyApi()) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
}
|
||||
|
||||
public function get(int $id): ?Policy
|
||||
{
|
||||
$policy = $this->canRead() ? $this->repo->find($id) : null;
|
||||
return ($policy !== null && $this->visible($policy)) ? $policy : null;
|
||||
}
|
||||
|
||||
/** @return int[] */
|
||||
public function visibleIds(array $domains = []): array
|
||||
{
|
||||
return $this->canRead() ? $this->repo->visibleIds($this->context, $domains) : [];
|
||||
}
|
||||
|
||||
public function canEdit(Policy $policy): bool
|
||||
{
|
||||
return $this->canRead() && !$this->context->isReadOnlyApi() && $this->visible($policy);
|
||||
}
|
||||
|
||||
public function toArray(Policy $policy): array
|
||||
{
|
||||
return $policy->toArray() + ['can_edit' => (int)$this->canEdit($policy)];
|
||||
}
|
||||
|
||||
/** @throws UserError */
|
||||
public function create(array $input): Policy
|
||||
{
|
||||
$this->requireEditor();
|
||||
$policy = new Policy();
|
||||
$policy->domain = $this->domainFor($input['domain'] ?? null);
|
||||
$this->apply($policy, $input);
|
||||
$this->repo->save($policy);
|
||||
return $policy;
|
||||
}
|
||||
|
||||
/** Attributes missing in $input keep their value @throws UserError */
|
||||
public function update(int $id, array $input): Policy
|
||||
{
|
||||
$policy = $this->editable($id);
|
||||
$this->apply($policy, $input);
|
||||
$this->repo->save($policy);
|
||||
return $policy;
|
||||
}
|
||||
|
||||
/** @throws UserError */
|
||||
public function delete(int $id): Policy
|
||||
{
|
||||
$policy = $this->editable($id);
|
||||
$this->repo->delete($id);
|
||||
return $policy;
|
||||
}
|
||||
|
||||
/** Address entries of the lists follow a renamed mailbox or alias; called after the rename, without permission check */
|
||||
public function addressRenamed(string $old, string $new): void
|
||||
{
|
||||
$this->repo->renameAddress(strtolower($old), strtolower($new));
|
||||
}
|
||||
|
||||
/** @return array[] id, name, domain, address of the groups in these domains (choices of the forms) */
|
||||
public function groupChoices(array $domains): array
|
||||
{
|
||||
return ($this->canRead() && $this->canUseGroups()) ? $this->repo->groupsIn($domains) : [];
|
||||
}
|
||||
|
||||
private function canUseGroups(): bool
|
||||
{
|
||||
return $this->context->hasAdminRights() || $this->context->hasAcl('mailbox_groups');
|
||||
}
|
||||
|
||||
/**
|
||||
* "Test a rule": would the mail be accepted? A sender that is a mailbox counts as logged in when $loggedIn.
|
||||
*
|
||||
* @return array{allowed: int, side: string, policy: ?string, internal_alias: int, text: string}
|
||||
* @throws UserError
|
||||
*/
|
||||
public function test(mixed $sender, mixed $recipient, bool $loggedIn): array
|
||||
{
|
||||
if (!$this->canRead()) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
$sender = strtolower((string)Input::text($sender));
|
||||
$recipient = strtolower((string)Input::text($recipient));
|
||||
if (($sender !== '' && !filter_var($sender, FILTER_VALIDATE_EMAIL)) || !filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new UserError('mail_policy_test_invalid');
|
||||
}
|
||||
// domain admins test mail of their domains; a sender of another domain counts as not logged in
|
||||
$ownSender = $this->context->hasAdminRights() || $this->context->canAccessDomain(self::domainOf($sender));
|
||||
if (!$ownSender && !$this->context->canAccessDomain(self::domainOf($recipient))) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
$login = ($loggedIn && $ownSender && $this->repo->mailboxDomain($sender) !== null) ? $sender : '';
|
||||
$decision = $this->engine->check($sender, $recipient, $login);
|
||||
$policy = $decision->policy;
|
||||
$hidden = $policy !== null && $policy->id !== null && !$this->visible($policy);
|
||||
return [
|
||||
'allowed' => (int)$decision->allowed,
|
||||
'side' => $decision->side,
|
||||
'policy' => ($policy !== null && $policy->id !== null && !$hidden) ? $policy->name : null,
|
||||
'internal_alias' => (int)($policy !== null && $policy->id === null),
|
||||
'logged_in' => (int)($login !== ''),
|
||||
// the text of a policy of another domain stays hidden
|
||||
'text' => $decision->allowed ? '' : ($hidden ? Decision::defaultText($decision->side) : $decision->text()),
|
||||
];
|
||||
}
|
||||
|
||||
private function visible(Policy $policy): bool
|
||||
{
|
||||
return $this->context->hasAdminRights() || $this->context->canAccessDomain($policy->domain);
|
||||
}
|
||||
|
||||
/** @throws UserError */
|
||||
private function editable(int $id): Policy
|
||||
{
|
||||
$this->requireEditor();
|
||||
$policy = $this->get($id);
|
||||
if ($policy === null || !$this->canEdit($policy)) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
return $policy;
|
||||
}
|
||||
|
||||
/** @throws UserError */
|
||||
private function domainFor(mixed $value): string
|
||||
{
|
||||
$domain = Input::text($value);
|
||||
$domain = ($domain === null || $domain === '') ? '' : (string)idn_to_ascii(strtolower($domain), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
if ($domain !== '' && !$this->context->hasAdminRights() && !$this->context->canAccessDomain($domain)) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
if ($domain === '' || !$this->repo->isDomain($domain)) {
|
||||
throw new UserError('domain_not_found', [htmlspecialchars((string)Input::text($value))]);
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/** @throws UserError */
|
||||
private function apply(Policy $policy, array $input): void
|
||||
{
|
||||
if (array_key_exists('name', $input) || $policy->id === null) {
|
||||
$name = Input::text($input['name'] ?? null);
|
||||
if ($name === null || $name === '' || mb_strlen($name) > 100 || preg_match('/[\x00-\x1f\x7f]/', $name)) {
|
||||
throw new UserError('mail_policy_name_invalid');
|
||||
}
|
||||
if ($this->repo->nameExists($policy->domain, $name, $policy->id)) {
|
||||
throw new UserError('object_exists', [htmlspecialchars($name)]);
|
||||
}
|
||||
$policy->name = $name;
|
||||
}
|
||||
if (array_key_exists('description', $input)) {
|
||||
$description = Input::text($input['description']);
|
||||
if ($description === null || mb_strlen($description) > 255 || preg_match('/[\x00-\x1f\x7f]/', $description)) {
|
||||
throw new UserError('mail_policy_description_invalid');
|
||||
}
|
||||
$policy->description = $description;
|
||||
}
|
||||
foreach (['send_mode' => 'sendMode', 'receive_mode' => 'receiveMode'] as $key => $property) {
|
||||
if (array_key_exists($key, $input)) {
|
||||
$mode = Input::text($input[$key]);
|
||||
$policy->$property = in_array($mode, Policy::MODES, true) ? $mode : throw new UserError('mail_policy_mode_invalid');
|
||||
}
|
||||
}
|
||||
$groupAccess = $this->canUseGroups();
|
||||
foreach (self::LISTS as $key => $property) {
|
||||
// the UI sends the groups of a list separately (<list>_groups); the API may name them as group:<id>
|
||||
if (!array_key_exists($key, $input) && !array_key_exists($key . '_groups', $input)) {
|
||||
continue;
|
||||
}
|
||||
$entries = array_key_exists($key, $input) ? self::values($input[$key]) : self::withoutGroups($policy->$property);
|
||||
$kept = [];
|
||||
if (!$groupAccess) {
|
||||
// without access to groups the group entries stay (also when sent back unchanged); the form shows no group choices
|
||||
$kept = array_values(array_diff($policy->$property, self::withoutGroups($policy->$property)));
|
||||
$entries = array_values(array_diff(array_map('strtolower', $entries), $kept));
|
||||
} elseif (array_key_exists($key . '_groups', $input)) {
|
||||
$entries = array_merge(self::withoutGroups($entries), array_map(fn(string $id): string => 'group:' . $id, self::values($input[$key . '_groups'])));
|
||||
}
|
||||
$entries = array_values(array_unique(array_merge($this->entries($entries, $policy->domain, $groupAccess), $kept)));
|
||||
if (count($entries) > self::MAX_ENTRIES) {
|
||||
throw new UserError('mail_policy_entries_exceeded', [self::MAX_ENTRIES]);
|
||||
}
|
||||
$policy->$property = $entries;
|
||||
}
|
||||
if (array_key_exists('reject_text', $input)) {
|
||||
$text = Input::text($input['reject_text']);
|
||||
if ($text === null || strlen($text) > self::MAX_REJECT_TEXT || preg_match('/[^\x20-\x7e]/', $text)) {
|
||||
throw new UserError('mail_policy_reject_text_invalid');
|
||||
}
|
||||
$policy->rejectText = $text;
|
||||
}
|
||||
if (array_key_exists('active', $input)) {
|
||||
$policy->active = (int)self::flag($input['active']);
|
||||
}
|
||||
if (array_key_exists('mailboxes', $input)) {
|
||||
$policy->mailboxes = [];
|
||||
foreach (self::limited($input['mailboxes']) as $username) {
|
||||
$username = strtolower($username);
|
||||
if ($this->repo->mailboxDomain($username) !== $policy->domain) {
|
||||
throw new UserError('mail_policy_member_invalid', [htmlspecialchars($username)]);
|
||||
}
|
||||
$policy->mailboxes[$username] = $username;
|
||||
}
|
||||
$policy->mailboxes = array_values($policy->mailboxes);
|
||||
}
|
||||
// without access to groups the assignments stay; only sending them back unchanged is accepted
|
||||
if (array_key_exists('groups', $input) && !$groupAccess && self::values($input['groups']) !== []) {
|
||||
$sent = array_map('intval', self::values($input['groups']));
|
||||
$stored = $policy->groups;
|
||||
sort($sent);
|
||||
sort($stored);
|
||||
if (array_values(array_unique($sent)) !== $stored) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
}
|
||||
if (array_key_exists('groups', $input) && $groupAccess) {
|
||||
$policy->groups = [];
|
||||
foreach (self::limited($input['groups']) as $value) {
|
||||
$id = Input::intInRange($value, 1, PHP_INT_MAX);
|
||||
if ($id === null || $this->repo->groupDomain($id) !== $policy->domain) {
|
||||
throw new UserError('mail_policy_member_invalid', [htmlspecialchars($value)]);
|
||||
}
|
||||
$policy->groups[$id] = $id;
|
||||
}
|
||||
$policy->groups = array_values($policy->groups);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entries of a list: addresses, domains (a leading @ is dropped) and group:<id> of a group of the policy's
|
||||
* domain, lower case, without repeats.
|
||||
*
|
||||
* @param string[] $values
|
||||
* @return string[]
|
||||
* @throws UserError
|
||||
*/
|
||||
private function entries(array $values, string $domain, bool $groupAccess): array
|
||||
{
|
||||
$entries = [];
|
||||
foreach (self::limited(array_map('strtolower', $values)) as $entry) {
|
||||
if (str_starts_with($entry, 'group:')) {
|
||||
$id = Input::intInRange(substr($entry, 6), 1, PHP_INT_MAX);
|
||||
if (!$groupAccess || $id === null || $this->repo->groupDomain($id) !== $domain) {
|
||||
throw new UserError('mail_policy_entry_invalid', [htmlspecialchars($entry)]);
|
||||
}
|
||||
$entry = 'group:' . $id;
|
||||
} elseif (str_contains(ltrim($entry, '@'), '@')) {
|
||||
// in the form the engine compares: domain without the final dot, never a +tag or an IP literal
|
||||
$at = strrpos($entry, '@');
|
||||
$local = substr($entry, 0, $at);
|
||||
$entry = $local . '@' . rtrim(substr($entry, $at + 1), '.');
|
||||
if (!filter_var($entry, FILTER_VALIDATE_EMAIL) || str_contains(substr($local, 1), '+') || str_contains($entry, '@[')) {
|
||||
throw new UserError('mail_policy_entry_invalid', [htmlspecialchars($entry)]);
|
||||
}
|
||||
} else {
|
||||
$entry = rtrim(ltrim($entry, '@'), '.');
|
||||
if (!str_contains($entry, '.') || !filter_var($entry, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
|
||||
throw new UserError('mail_policy_entry_invalid', [htmlspecialchars($entry)]);
|
||||
}
|
||||
}
|
||||
$entries[$entry] = $entry;
|
||||
}
|
||||
return array_values($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Values of a list field without repeats, at most MAX_ENTRIES (checked before anything is looked up)
|
||||
*
|
||||
* @return string[]
|
||||
* @throws UserError
|
||||
*/
|
||||
private static function limited(mixed $value): array
|
||||
{
|
||||
$values = array_values(array_unique(self::values($value)));
|
||||
if (count($values) > self::MAX_ENTRIES) {
|
||||
throw new UserError('mail_policy_entries_exceeded', [self::MAX_ENTRIES]);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/** @return string[] entries without group:<id> */
|
||||
private static function withoutGroups(array $entries): array
|
||||
{
|
||||
return array_values(array_filter($entries, fn(string $entry): bool => !str_starts_with(strtolower($entry), 'group:')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Values of a list field: an array (multi-select, JSON) or a string separated by line breaks, commas or spaces.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private static function values(mixed $value): array
|
||||
{
|
||||
$values = is_array($value) ? $value : preg_split('/[\s,;]+/', (string)Input::text($value));
|
||||
return array_values(array_filter(array_map(fn(mixed $v): string => is_scalar($v) ? trim((string)$v) : '', $values), 'strlen'));
|
||||
}
|
||||
|
||||
/** Checkbox or API flag; a hidden "0" + checkbox pair arrives as ["0", "1"] when checked */
|
||||
private static function flag(mixed $value): bool
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$value = end($value);
|
||||
}
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
private static function domainOf(string $address): string
|
||||
{
|
||||
$at = strrpos($address, '@');
|
||||
return ($at === false) ? '' : substr($address, $at + 1);
|
||||
}
|
||||
}
|
||||
@@ -195,12 +195,12 @@ class GroupService
|
||||
{
|
||||
$domain = Input::text($value);
|
||||
$domain = ($domain === null || $domain === '') ? '' : (string)idn_to_ascii(strtolower($domain), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
if ($domain !== '' && !$this->context->hasAdminRights() && !$this->context->canAccessDomain($domain)) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
if ($domain === '' || !$this->lookup->isDomain($domain)) {
|
||||
throw new UserError('domain_not_found', [htmlspecialchars((string)Input::text($value))]);
|
||||
}
|
||||
if (!$this->context->hasAdminRights() && !$this->context->canAccessDomain($domain)) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
@@ -304,6 +304,9 @@ class GroupService
|
||||
$new = array_values(array_diff(array_unique($add), array_map('strval', array_keys($members))));
|
||||
$regular = $this->lookup->regularMailboxes($new);
|
||||
foreach ($new as $username) {
|
||||
if ($this->foreignDomain($username)) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
if (!in_array($username, $regular, true)) {
|
||||
throw new UserError('group_member_invalid', [htmlspecialchars($username)]);
|
||||
}
|
||||
@@ -337,12 +340,12 @@ class GroupService
|
||||
// The group's own address is covered by sender_allowed
|
||||
$valid = ($address !== null && $address !== $group->address && $this->lookup->aliasExists($address));
|
||||
}
|
||||
if ($this->foreignDomain($address)) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
if (!$valid) {
|
||||
throw new UserError('group_send_as_invalid', [htmlspecialchars($value)]);
|
||||
}
|
||||
if (!$this->context->hasAdminRights() && !$this->context->canAccessDomain(self::domainOf((string)$address))) {
|
||||
throw new AccessDenied();
|
||||
}
|
||||
$sendAs[] = (string)$address;
|
||||
}
|
||||
$sendAs = array_values(array_unique($sendAs));
|
||||
@@ -505,4 +508,11 @@ class GroupService
|
||||
{
|
||||
return substr($address, strrpos($address, '@') + 1);
|
||||
}
|
||||
|
||||
/** A domain admin names an address or @domain outside their domains: denied before anything is looked up */
|
||||
private function foreignDomain(?string $address): bool
|
||||
{
|
||||
$domain = ($address !== null && str_contains($address, '@')) ? self::domainOf($address) : '';
|
||||
return $domain !== '' && !$this->context->hasAdminRights() && !$this->context->canAccessDomain($domain);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Mail policies (admin/domainadmin mailbox page): list, domain choices of the add modal, test of a rule. Loaded via $js_minifier.
|
||||
var mailcowMailPolicy = (function($) {
|
||||
'use strict';
|
||||
|
||||
// lang.mail_policy of the mailbox page, '' if missing
|
||||
function L(key) {
|
||||
var src = (typeof lang !== 'undefined' && lang && lang.mail_policy) ? lang.mail_policy : {};
|
||||
return (src[key] !== undefined && src[key] !== null) ? src[key] : '';
|
||||
}
|
||||
function esc(s) {
|
||||
return escapeHtml(s === null || s === undefined ? '' : s);
|
||||
}
|
||||
|
||||
// mode of one direction with the number of allowed and blocked entries
|
||||
function sideCell(mode, allow, block) {
|
||||
var html = esc(L('summary_' + mode));
|
||||
var counts = [];
|
||||
if (mode !== 'all' && allow.length) counts.push('+' + allow.length + ' ' + esc(L('summary_allowed')));
|
||||
if (block.length) counts.push(block.length + ' ' + esc(L('summary_blocked')));
|
||||
return html + (counts.length ? '<div class="small text-muted">' + counts.join(', ') + '</div>' : '');
|
||||
}
|
||||
|
||||
function assignedCell(item) {
|
||||
var list = (item.mailboxes || []).slice(0, 3).map(esc);
|
||||
if ((item.mailboxes || []).length > 3) list.push('<span class="text-muted">+' + (item.mailboxes.length - 3) + '</span>');
|
||||
if ((item.groups || []).length) list.push('<i class="bi bi-people"></i> ' + esc(L('groups')) + ': ' + item.groups.length);
|
||||
return list.length ? list.join('<br>') : '<span class="text-muted">' + esc(L('assigned_none')) + '</span>';
|
||||
}
|
||||
|
||||
function drawTable() {
|
||||
if ($.fn.DataTable.isDataTable('#mail_policy_table')) {
|
||||
$('#mail_policy_table').DataTable().columns.adjust().responsive.recalc();
|
||||
return;
|
||||
}
|
||||
$('#mail_policy_table').DataTable({
|
||||
responsive: true,
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order: [[3, 'asc']],
|
||||
ajax: {
|
||||
type: "GET",
|
||||
url: "/api/v1/get/mail-policy/all",
|
||||
dataSrc: function(json) {
|
||||
if (!Array.isArray(json)) return [];
|
||||
$.each(json, function(i, item) {
|
||||
item.chkbox = item.can_edit ?
|
||||
'<input type="checkbox" class="form-check-input" data-id="mail_policy" name="multi_select" value="' + encodeURIComponent(item.id) + '" />' :
|
||||
'<input type="checkbox" class="form-check-input" disabled />';
|
||||
item.name_display = esc(item.name) + (item.description ? '<div class="small text-muted">' + esc(item.description) + '</div>' : '');
|
||||
item.send_display = sideCell(item.send_mode, item.send_allow || [], item.send_block || []);
|
||||
item.receive_display = sideCell(item.receive_mode, item.receive_allow || [], item.receive_block || []);
|
||||
item.assigned_display = assignedCell(item);
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/mail-policy/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + esc(item.can_edit ? lang.edit : L('view')) + '</a>' +
|
||||
(item.can_edit ? '<a href="#" data-action="delete_selected" data-id="single-mail_policy" data-api-url="delete/mail-policy" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + esc(lang.remove) + '</a>' : '') +
|
||||
'</div>';
|
||||
});
|
||||
return json;
|
||||
},
|
||||
error: function() {
|
||||
mailcow_alert_box(L('list_error'), 'danger');
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
// placeholder, so checkbox will not block child row toggle
|
||||
title: '',
|
||||
data: null,
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
defaultContent: '',
|
||||
responsivePriority: 1
|
||||
},
|
||||
{ title: '', data: 'chkbox', searchable: false, orderable: false, defaultContent: '', responsivePriority: 2 },
|
||||
{ title: 'ID', data: 'id', defaultContent: '', responsivePriority: 6 },
|
||||
{ title: L('name'), data: 'name_display', defaultContent: '', responsivePriority: 2 },
|
||||
{ title: L('domain'), data: 'domain', defaultContent: '', responsivePriority: 5 },
|
||||
{ title: L('send'), data: 'send_display', defaultContent: '', responsivePriority: 3 },
|
||||
{ title: L('receive'), data: 'receive_display', defaultContent: '', responsivePriority: 3 },
|
||||
{ title: L('assign'), data: 'assigned_display', defaultContent: '', responsivePriority: 4 },
|
||||
{
|
||||
title: lang.active,
|
||||
data: 'active',
|
||||
defaultContent: '',
|
||||
responsivePriority: 6,
|
||||
render: function(d) { return d == 1 ? '<i class="bi bi-check-lg"></i>' : '<i class="bi bi-x-lg"></i>'; }
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
orderable: false,
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 3,
|
||||
defaultContent: ''
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// add modal: mailboxes and groups of the chosen domain only
|
||||
function filterDomain($form) {
|
||||
var domain = $form.find('select.mail-policy-domain-select').val();
|
||||
$form.find('select.mail-policy-mailboxes, select.mail-policy-groups').each(function() {
|
||||
$(this).find('option').each(function() {
|
||||
var other = $(this).data('domain') !== domain;
|
||||
$(this).prop('hidden', other);
|
||||
if (other) $(this).prop('selected', false);
|
||||
});
|
||||
$(this).selectpicker('refresh');
|
||||
});
|
||||
}
|
||||
|
||||
function test(e) {
|
||||
e.preventDefault();
|
||||
var sender = $.trim($('#mail_policy_test_sender').val());
|
||||
var recipient = $.trim($('#mail_policy_test_recipient').val());
|
||||
if (!recipient) return;
|
||||
// an empty sender (bounces) is sent as "-"
|
||||
var url = '/api/v1/get/mail-policy/test/' + encodeURIComponent(sender || '-') + '/' + encodeURIComponent(recipient) +
|
||||
($('#mail_policy_test_login').is(':checked') ? '/login' : '');
|
||||
$.get(url, function(r) {
|
||||
var html;
|
||||
if (!r || r.allowed === undefined) {
|
||||
html = '<div class="alert alert-danger py-2">' + esc(L('test_invalid')) + '</div>';
|
||||
} else if (r.allowed == 1) {
|
||||
html = '<div class="alert alert-success py-2">' + esc(L('test_allowed')) + '</div>';
|
||||
} else {
|
||||
var reason = r.internal_alias == 1 ? L('test_denied_internal') : r.side === 'address' ? L('test_denied_address') :
|
||||
(r.policy ? L('test_denied_' + r.side).replace('%s', r.policy) : L('test_denied_other'));
|
||||
html = '<div class="alert alert-danger py-2">' + esc(reason) + '<div class="small mt-1"><code>550 5.7.1 ' + esc(r.text) + '</code></div></div>';
|
||||
}
|
||||
if (r && r.allowed !== undefined && sender && r.logged_in == 0 && $('#mail_policy_test_login').is(':checked')) {
|
||||
html += '<div class="small text-muted">' + esc(L('test_not_a_mailbox')) + '</div>';
|
||||
}
|
||||
$('#mail_policy_test_result').html(html);
|
||||
}, 'json').fail(function() {
|
||||
$('#mail_policy_test_result').html('<div class="alert alert-danger py-2">' + esc(lang_danger.generic_server_error) + '</div>');
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('select.mail-policy-domain-select').on('change', function() {
|
||||
filterDomain($(this).closest('form'));
|
||||
});
|
||||
$('#addMailPolicyModal').on('show.bs.modal', function() {
|
||||
filterDomain($(this).find('form'));
|
||||
});
|
||||
$('#mail_policy_test').on('submit', test);
|
||||
});
|
||||
|
||||
return {
|
||||
drawTable: drawTable
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -614,7 +614,7 @@ jQuery(function($){
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang_edit.ratelimit,
|
||||
title: 'RL',
|
||||
data: 'rl',
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
@@ -715,6 +715,10 @@ jQuery(function($){
|
||||
$.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.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);
|
||||
if (item.attributes.rl_frame === "s"){
|
||||
item.attributes.rl_frame = lang_rl.second;
|
||||
@@ -797,25 +801,16 @@ jQuery(function($){
|
||||
title: lang.mailbox_defquota,
|
||||
data: 'attributes.def_quota_for_mbox',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.max_quota,
|
||||
data: 'attributes.max_quota_for_mbox',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.domain_quota_total,
|
||||
data: 'attributes.max_quota_for_domain',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
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.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>' : '') +
|
||||
'<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>';
|
||||
/*
|
||||
if (!item.rl) {
|
||||
@@ -1250,6 +1244,8 @@ jQuery(function($){
|
||||
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.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_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>';
|
||||
@@ -1336,9 +1332,6 @@ jQuery(function($){
|
||||
title: lang.domain_quota,
|
||||
data: 'attributes.quota',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.tls_enforce_in,
|
||||
@@ -2500,6 +2493,7 @@ jQuery(function($){
|
||||
onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
|
||||
onVisible("[id^=imapsync_source_table]", () => mailcowImapsync.drawSourceTable(role === 'admin' || acl_data.syncjobs === 1));
|
||||
onVisible("[id^=mailbox_group_table]", () => mailcowGroups.drawTable());
|
||||
onVisible("[id^=mail_policy_table]", () => mailcowMailPolicy.drawTable());
|
||||
onVisible("[id^=filter_table]", () => draw_filter_table());
|
||||
onVisible("[id^=bcc_table]", () => draw_bcc_table());
|
||||
onVisible("[id^=recipient_map_table]", () => draw_recipient_map_table());
|
||||
|
||||
+28
-6
@@ -280,6 +280,9 @@ if (isset($_GET['query'])) {
|
||||
case "mailbox-group":
|
||||
process_add_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->addGroup($attr));
|
||||
break;
|
||||
case "mail-policy":
|
||||
process_add_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->addPolicy($attr));
|
||||
break;
|
||||
case "domain":
|
||||
switch ($object) {
|
||||
case "template":
|
||||
@@ -858,6 +861,23 @@ if (isset($_GET['query'])) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "mail-policy":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
process_get_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->getPolicies($extra));
|
||||
break;
|
||||
|
||||
// test/<sender>/<recipient>[/login]: would the mail be accepted?
|
||||
case "test":
|
||||
process_get_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->test((string)$extra, (string)($query[4] ?? ''), ($query[5] ?? '') === 'login'));
|
||||
break;
|
||||
|
||||
default:
|
||||
process_get_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->getPolicy($object));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "logs":
|
||||
switch ($object) {
|
||||
case "dovecot":
|
||||
@@ -1087,14 +1107,10 @@ if (isset($_GET['query'])) {
|
||||
$data['mailboxes'][$mailbox][] = $mailbox;
|
||||
$user_alias_details = user_get_alias_details($mailbox);
|
||||
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) {
|
||||
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr(strrchr($k, '@'), 1))) {
|
||||
$data['mailboxes'][$mailbox][] = $k;
|
||||
}
|
||||
$data['mailboxes'][$mailbox][] = $k;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1783,6 +1799,9 @@ if (isset($_GET['query'])) {
|
||||
case "mailbox-group":
|
||||
process_delete_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->deleteGroups($items));
|
||||
break;
|
||||
case "mail-policy":
|
||||
process_delete_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->deletePolicies($items));
|
||||
break;
|
||||
case "app-passwd":
|
||||
process_delete_return(app_passwd('delete', array('id' => $items)));
|
||||
break;
|
||||
@@ -1957,6 +1976,9 @@ if (isset($_GET['query'])) {
|
||||
case "mailbox-group":
|
||||
process_edit_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->editGroups($items, $attr));
|
||||
break;
|
||||
case "mail-policy":
|
||||
process_edit_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->editPolicies($items, $attr));
|
||||
break;
|
||||
case "recipient_map":
|
||||
process_edit_return(recipient_map('edit', array_merge(array('id' => $items), $attr)));
|
||||
break;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"extend_sender_acl": "Eingabe externer Absenderadressen erlauben",
|
||||
"filters": "Filter",
|
||||
"login_as": "Einloggen als Mailbox-Benutzer",
|
||||
"mail_policies": "Mail-Richtlinien",
|
||||
"mailbox_groups": "Gruppen",
|
||||
"mailbox_relayhost": "Relayhost für eine Mailbox setzen",
|
||||
"prohibited": "Untersagt durch Richtlinie",
|
||||
@@ -66,7 +67,7 @@
|
||||
"hostname": "Host",
|
||||
"inactive": "Inaktiv",
|
||||
"internal": "Intern",
|
||||
"internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.",
|
||||
"internal_info": "Interne Aliasse nehmen nur E-Mails von angemeldeten Absendern ihrer Domain an (auch über Alias-Domains).",
|
||||
"sender_allowed": "Als dieser Alias senden erlauben",
|
||||
"kind": "Art",
|
||||
"mailbox_quota_def": "Standard-Quota einer Mailbox",
|
||||
@@ -582,6 +583,14 @@
|
||||
"is_spam_alias": "%s lautet bereits eine temporäre Alias-Adresse (Spam-Alias-Adresse)",
|
||||
"last_key": "Letzter Key kann nicht gelöscht werden, bitte stattdessen die 2FA deaktivieren.",
|
||||
"login_failed": "Anmeldung fehlgeschlagen",
|
||||
"mail_policy_description_invalid": "Ungültige Beschreibung",
|
||||
"mail_policy_entries_exceeded": "Höchstens %s Einträge pro Liste oder Auswahl möglich",
|
||||
"mail_policy_entry_invalid": "Ungültige Adresse, Domain oder Gruppe in der Liste: %s",
|
||||
"mail_policy_member_invalid": "Mailbox oder Gruppe %s gehört nicht zur Domain der Richtlinie",
|
||||
"mail_policy_mode_invalid": "Ungültiger Modus",
|
||||
"mail_policy_name_invalid": "Ungültiger Name (1 bis 100 Zeichen)",
|
||||
"mail_policy_reject_text_invalid": "Ungültiger Ablehnungstext: druckbare ASCII-Zeichen, höchstens 200",
|
||||
"mail_policy_test_invalid": "Ungültiger Absender oder Empfänger",
|
||||
"mailbox_defquota_exceeds_mailbox_maxquota": "Standard-Quota überschreitet das Limit der maximal erlaubten Größe einer Mailbox",
|
||||
"mailbox_invalid": "Mailboxname ist ungültig",
|
||||
"mailbox_quota_exceeded": "Speicherplatz überschreitet das Limit (max. %d MiB)",
|
||||
@@ -829,7 +838,7 @@
|
||||
"iam_unlink": "Verknüpfung lösen",
|
||||
"inactive": "Inaktiv",
|
||||
"internal": "Intern",
|
||||
"internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.",
|
||||
"internal_info": "Interne Aliasse nehmen nur E-Mails von angemeldeten Absendern ihrer Domain an (auch über Alias-Domains).",
|
||||
"sender_allowed": "Als dieser Alias senden erlauben",
|
||||
"sender_allowed_info": "Wenn deaktiviert, kann dieser Alias nur E-Mails empfangen. Verwenden Sie Sender-ACL, um bestimmten Postfächern die Berechtigung zum Senden zu erteilen.",
|
||||
"kind": "Art",
|
||||
@@ -969,7 +978,7 @@
|
||||
"imap_grants": "Postfachzugriff",
|
||||
"imap_grants_hint": "Mitglieder öffnen diese Mailboxen in ihrem Mailprogramm (Ordner Shared/<mailbox>). Lesen: lesen und als gelesen markieren. Schreiben: zusätzlich ablegen, verschieben und löschen. Vollzugriff: zusätzlich Ordner löschen. Team-Postfächer ohne eigenen Login als geteiltes Postfach markieren.",
|
||||
"info": "Gruppen bündeln Postfächer. Eine Gruppe kann eine eigene Adresse haben: ein Alias, der automatisch an alle Mitglieder zustellt. Mitglieder können als andere Adressen senden, auf freigegebene Postfächer, Kalender und Adressbücher zugreifen und ein Postfach-Template erhalten. Vom Identity Provider synchronisierte Mitglieder sind mit IdP markiert.",
|
||||
"internal": "Nur Mails von Absendern der Domain annehmen",
|
||||
"internal": "Nur E-Mails von angemeldeten Absendern der Domain annehmen",
|
||||
"list_error": "Gruppen konnten nicht geladen werden",
|
||||
"managed_alias": "Wird von Gruppe %s verwaltet",
|
||||
"members": "Mitglieder",
|
||||
@@ -1036,6 +1045,61 @@
|
||||
"username": "Benutzername",
|
||||
"email": "E-Mail-Adresse"
|
||||
},
|
||||
"mail_policy": {
|
||||
"add": "Mail-Richtlinie hinzufügen",
|
||||
"allow_receive": "Erlaubte Absender",
|
||||
"allow_send": "Erlaubte Empfänger",
|
||||
"assign": "Zugewiesen an",
|
||||
"assign_info": "Die Richtlinie gilt für diese Mailboxen und für die Mitglieder dieser Gruppen aus der Domain der Richtlinie, bei einer Gruppe auch für E-Mails an die Gruppenadresse. E-Mails an einen Alias oder eine andere Gruppe erreichen die Mitglieder ohne deren Richtlinien.",
|
||||
"assigned_none": "Nicht zugewiesen",
|
||||
"block_receive": "Gesperrte Absender",
|
||||
"block_send": "Gesperrte Empfänger",
|
||||
"description": "Beschreibung",
|
||||
"domain": "Domain",
|
||||
"entries_info_receive": "Adressen oder Domains, eine pro Zeile, und Gruppen der Domain. Erlaubte Adressen und Domains dieser mailcow gelten nur für angemeldete Absender, gesperrte auch für die Absenderadresse im Umschlag. Externe Absender werden an der Absenderadresse im Umschlag erkannt.",
|
||||
"entries_info_send": "Adressen oder Domains, eine pro Zeile, und Gruppen der Domain.",
|
||||
"groups": "Gruppen",
|
||||
"info": "Mail-Richtlinien legen fest, an wen Mailboxen und Gruppen senden und von wem sie empfangen dürfen, z. B. nur intern oder nur mit bestimmten Domains. Postfix prüft sie für jeden Empfänger und lehnt nicht erlaubte Empfänger mit einem 550-Fehler ab. Eingehende E-Mails erreichen die übrigen Empfänger trotzdem, SOGo sendet eine E-Mail mit einem abgelehnten Empfänger dagegen gar nicht.",
|
||||
"internal_info": "Intern: Empfänger in der Domain der Richtlinie und ihren Alias-Domains; Absender, die mit einer Mailbox dieser Domain angemeldet sind.",
|
||||
"list_error": "Die Mail-Richtlinien konnten nicht geladen werden",
|
||||
"mail_policies": "Mail-Richtlinien",
|
||||
"mailboxes": "Mailboxen",
|
||||
"mode": "Erlaubt",
|
||||
"mode_receive_all": "Alle Absender",
|
||||
"mode_receive_internal": "Interne Absender und die erlaubten Absender",
|
||||
"mode_receive_list": "Nur die erlaubten Absender",
|
||||
"mode_send_all": "Alle Empfänger",
|
||||
"mode_send_internal": "Interne Empfänger und die erlaubten Empfänger",
|
||||
"mode_send_list": "Nur die erlaubten Empfänger",
|
||||
"name": "Name",
|
||||
"policy": "Mail-Richtlinie",
|
||||
"receive": "Empfangen",
|
||||
"receive_info": "Für E-Mails an eine zugewiesene Mailbox oder an die Adresse einer zugewiesenen Gruppe.",
|
||||
"reject_text": "Ablehnungstext",
|
||||
"reject_text_info": "Text der SMTP-Ablehnung (550 5.7.1 …), nur ASCII. Leer: ein Standardtext.",
|
||||
"send": "Senden",
|
||||
"send_info": "Für E-Mails, die eine zugewiesene Mailbox angemeldet sendet oder die unter ihrer Adresse gesendet werden, auch Weiterleitungen per Filter und Abwesenheitsnotizen.",
|
||||
"summary_all": "Alle",
|
||||
"summary_allowed": "erlaubt",
|
||||
"summary_blocked": "gesperrt",
|
||||
"summary_internal": "Intern",
|
||||
"summary_list": "Nur erlaubte",
|
||||
"test": "Regel testen",
|
||||
"test_allowed": "Angenommen: Keine Richtlinie lehnt diese E-Mail ab.",
|
||||
"test_denied_address": "Abgelehnt: Diese Schreibweise leitet über eine andere Adresse um und ist nicht erlaubt.",
|
||||
"test_denied_internal": "Abgelehnt: Die Adresse nimmt nur E-Mails von angemeldeten Absendern ihrer Domain an (interner Alias oder interne Gruppe).",
|
||||
"test_denied_other": "Abgelehnt durch eine Richtlinie einer anderen Domain.",
|
||||
"test_denied_receive": "Abgelehnt durch den Empfangen-Teil der Richtlinie „%s“.",
|
||||
"test_denied_send": "Abgelehnt durch den Senden-Teil der Richtlinie „%s“.",
|
||||
"test_info": "Prüft, ob Postfix die E-Mail für diesen Empfänger annehmen würde. Für Bounces den Absender leer lassen.",
|
||||
"test_invalid": "Geben Sie einen gültigen Empfänger (und Absender) Ihrer Domains ein.",
|
||||
"test_logged_in": "Absender ist angemeldet",
|
||||
"test_not_a_mailbox": "Der Absender ist keine Mailbox Ihrer Domains und wurde als nicht angemeldet geprüft.",
|
||||
"test_recipient": "Empfänger",
|
||||
"test_run": "Testen",
|
||||
"test_sender": "Absender",
|
||||
"view": "Ansehen"
|
||||
},
|
||||
"mailbox": {
|
||||
"action": "Aktion",
|
||||
"activate": "Aktivieren",
|
||||
@@ -1134,6 +1198,9 @@
|
||||
"no_record": "Kein Eintrag für Objekt %s",
|
||||
"no_record_single": "Kein Eintrag",
|
||||
"owner": "Besitzer",
|
||||
"policies": "Richtlinien",
|
||||
"policies_mail": "Mail",
|
||||
"policies_tls": "TLS",
|
||||
"private_comment": "Privater Kommentar",
|
||||
"public_comment": "Öffentlicher Kommentar",
|
||||
"q_add_header": "bei Mail in Junk-Ordner",
|
||||
@@ -1348,6 +1415,9 @@
|
||||
"learned_ham": "ID %s wurde erfolgreich als Ham gelernt",
|
||||
"license_modified": "Änderungen an Lizenz wurden gespeichert",
|
||||
"logged_in_as": "Eingeloggt als %s",
|
||||
"mail_policy_added": "Mail-Richtlinie %s hinzugefügt",
|
||||
"mail_policy_modified": "Mail-Richtlinie %s gespeichert",
|
||||
"mail_policy_removed": "Mail-Richtlinie %s entfernt",
|
||||
"mailbox_added": "Mailbox %s wurde angelegt",
|
||||
"mailbox_modified": "Änderungen an Mailbox %s wurden gespeichert",
|
||||
"mailbox_removed": "Mailbox %s wurde entfernt",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"extend_sender_acl": "Allow to extend sender ACL by external addresses",
|
||||
"filters": "Filters",
|
||||
"login_as": "Login as mailbox user",
|
||||
"mail_policies": "Mail policies",
|
||||
"mailbox_groups": "Groups",
|
||||
"mailbox_relayhost": "Change relayhost for a mailbox",
|
||||
"prohibited": "Prohibited by ACL",
|
||||
@@ -66,7 +67,7 @@
|
||||
"hostname": "Host",
|
||||
"inactive": "Inactive",
|
||||
"internal": "Internal",
|
||||
"internal_info": "Internal aliases are only accessible from the own domain or alias domains.",
|
||||
"internal_info": "Internal aliases only accept mail from logged-in senders of their domain (also through alias domains).",
|
||||
"sender_allowed": "Allow to send as this alias",
|
||||
"kind": "Kind",
|
||||
"mailbox_quota_def": "Default mailbox quota",
|
||||
@@ -518,6 +519,14 @@
|
||||
"group_send_as_invalid": "Send-as address %s is invalid or unknown",
|
||||
"iam_sync_mode_invalid": "Invalid sync mode",
|
||||
"iam_sync_unavailable": "The identity provider cannot be synced (Keycloak and LDAP only)",
|
||||
"mail_policy_description_invalid": "Invalid description",
|
||||
"mail_policy_entries_exceeded": "At most %s entries per list or selection are possible",
|
||||
"mail_policy_entry_invalid": "Invalid address, domain or group in the list: %s",
|
||||
"mail_policy_member_invalid": "Mailbox or group %s does not belong to the domain of the policy",
|
||||
"mail_policy_mode_invalid": "Invalid mode",
|
||||
"mail_policy_name_invalid": "Invalid name (1 to 100 characters)",
|
||||
"mail_policy_reject_text_invalid": "Invalid rejection text: printable ASCII, at most 200 characters",
|
||||
"mail_policy_test_invalid": "Invalid sender or recipient",
|
||||
"tfa_login_locked": "Too many failed two-factor logins, please try again later",
|
||||
"tfa_removal_blocked": "Two-factor authentication cannot be removed because it is required for your account.",
|
||||
"alias_domain_invalid": "Alias domain %s is invalid",
|
||||
@@ -829,7 +838,7 @@
|
||||
"iam_unlink": "Unlink",
|
||||
"inactive": "Inactive",
|
||||
"internal": "Internal",
|
||||
"internal_info": "Internal aliases are only accessible from the own domain or alias domains.",
|
||||
"internal_info": "Internal aliases only accept mail from logged-in senders of their domain (also through alias domains).",
|
||||
"sender_allowed": "Allow to send as this alias",
|
||||
"sender_allowed_info": "If disabled, this alias can only receive mail. Use sender ACL to override and grant specific mailboxes permission to send.",
|
||||
"kind": "Kind",
|
||||
@@ -969,7 +978,7 @@
|
||||
"imap_grants": "Mailbox access",
|
||||
"imap_grants_hint": "Members open these mailboxes in their mail client (folder Shared/<mailbox>). Read: read and mark as read. Write: also file, move and delete mails. Full access: also delete folders. For team mailboxes without a login of their own, mark the mailbox as shared mailbox.",
|
||||
"info": "Groups bundle mailboxes. A group can have its own address: an alias that delivers to all members automatically. Members can send as other addresses, access shared mailboxes, calendars and address books and get a mailbox template. Members synced from the identity provider are marked IdP.",
|
||||
"internal": "Only accept mail from senders of the domain",
|
||||
"internal": "Only accept mail from logged-in senders of the domain",
|
||||
"list_error": "Groups could not be loaded",
|
||||
"managed_alias": "Managed by group %s",
|
||||
"members": "Members",
|
||||
@@ -1036,6 +1045,61 @@
|
||||
"username": "Username",
|
||||
"email": "Email address"
|
||||
},
|
||||
"mail_policy": {
|
||||
"add": "Add mail policy",
|
||||
"allow_receive": "Allowed senders",
|
||||
"allow_send": "Allowed recipients",
|
||||
"assign": "Assigned to",
|
||||
"assign_info": "The policy applies to these mailboxes and to the members of these groups in the policy's domain; for a group also to mail sent to the group address. Mail sent to an alias or another group reaches the members without their policies.",
|
||||
"assigned_none": "Not assigned",
|
||||
"block_receive": "Blocked senders",
|
||||
"block_send": "Blocked recipients",
|
||||
"description": "Description",
|
||||
"domain": "Domain",
|
||||
"entries_info_receive": "Addresses or domains, one per line, and groups of the domain. Allowed addresses and domains of this mailcow count for logged-in senders only, blocked ones also for the envelope sender address. External senders are recognised by the envelope sender address.",
|
||||
"entries_info_send": "Addresses or domains, one per line, and groups of the domain.",
|
||||
"groups": "Groups",
|
||||
"info": "Mail policies define whom mailboxes and groups may send to and receive from, e.g. only internally or only with certain domains. Postfix checks them for every recipient and refuses recipients that are not allowed with a 550 error. Incoming mail still reaches the other recipients, whereas SOGo does not send a mail with a refused recipient at all.",
|
||||
"internal_info": "Internal: recipients in the domain of the policy and its alias domains; senders logged in with a mailbox of this domain.",
|
||||
"list_error": "The mail policies could not be loaded",
|
||||
"mail_policies": "Mail policies",
|
||||
"mailboxes": "Mailboxes",
|
||||
"mode": "Allowed",
|
||||
"mode_receive_all": "All senders",
|
||||
"mode_receive_internal": "Internal senders and the allowed senders",
|
||||
"mode_receive_list": "Only the allowed senders",
|
||||
"mode_send_all": "All recipients",
|
||||
"mode_send_internal": "Internal recipients and the allowed recipients",
|
||||
"mode_send_list": "Only the allowed recipients",
|
||||
"name": "Name",
|
||||
"policy": "Mail policy",
|
||||
"receive": "Receiving",
|
||||
"receive_info": "For mail to an assigned mailbox or to the address of an assigned group.",
|
||||
"reject_text": "Rejection text",
|
||||
"reject_text_info": "Text of the SMTP rejection (550 5.7.1 ...), ASCII only. Empty: a default text.",
|
||||
"send": "Sending",
|
||||
"send_info": "For mail an assigned mailbox sends while logged in, or that is sent with its address, including redirects by filters and vacation replies.",
|
||||
"summary_all": "All",
|
||||
"summary_allowed": "allowed",
|
||||
"summary_blocked": "blocked",
|
||||
"summary_internal": "Internal",
|
||||
"summary_list": "Allowed only",
|
||||
"test": "Test a rule",
|
||||
"test_allowed": "Accepted: no policy refuses this mail.",
|
||||
"test_denied_address": "Refused: this form of the address routes through another address and is not allowed.",
|
||||
"test_denied_internal": "Refused: the address only accepts mail from logged-in senders of its domain (internal alias or group).",
|
||||
"test_denied_other": "Refused by a policy of another domain.",
|
||||
"test_denied_receive": "Refused by the receiving part of the policy \"%s\".",
|
||||
"test_denied_send": "Refused by the sending part of the policy \"%s\".",
|
||||
"test_info": "Checks whether Postfix would accept the mail for this recipient. Leave the sender empty for bounces.",
|
||||
"test_invalid": "Enter a valid recipient (and sender) of your domains.",
|
||||
"test_logged_in": "Sender is logged in",
|
||||
"test_not_a_mailbox": "The sender is no mailbox of your domains and was tested as not logged in.",
|
||||
"test_recipient": "Recipient",
|
||||
"test_run": "Test",
|
||||
"test_sender": "Sender",
|
||||
"view": "View"
|
||||
},
|
||||
"mailbox": {
|
||||
"action": "Action",
|
||||
"activate": "Activate",
|
||||
@@ -1127,7 +1191,7 @@
|
||||
"mailbox_defaults_info": "Define default settings for new mailboxes.",
|
||||
"mailbox_defquota": "Default mailbox size",
|
||||
"mailbox_templates": "Mailbox Templates",
|
||||
"mailbox_quota": "Max. mailbox size",
|
||||
"mailbox_quota": "Max. size of a mailbox",
|
||||
"mailboxes": "Mailboxes",
|
||||
"max_aliases": "Max. aliases",
|
||||
"max_mailboxes": "Max. possible mailboxes",
|
||||
@@ -1141,6 +1205,9 @@
|
||||
"no_record_single": "No record",
|
||||
"open_logs": "Open logs",
|
||||
"owner": "Owner",
|
||||
"policies": "Policies",
|
||||
"policies_mail": "Mail",
|
||||
"policies_tls": "TLS",
|
||||
"private_comment": "Private comment",
|
||||
"public_comment": "Public comment",
|
||||
"q_add_header": "when moved to Junk folder",
|
||||
@@ -1355,6 +1422,9 @@
|
||||
"learned_ham": "Successfully learned ID %s as ham",
|
||||
"license_modified": "Changes to license have been saved",
|
||||
"logged_in_as": "Logged in as %s",
|
||||
"mail_policy_added": "Mail policy %s added",
|
||||
"mail_policy_modified": "Mail policy %s saved",
|
||||
"mail_policy_removed": "Mail policy %s removed",
|
||||
"mailbox_added": "Mailbox %s has been added",
|
||||
"mailbox_modified": "Changes to mailbox %s have been saved",
|
||||
"mailbox_removed": "Mailbox %s has been removed",
|
||||
@@ -1429,7 +1499,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_important": "Please register your Key in the admin panel with the new WebAuthn method.",
|
||||
"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_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"
|
||||
|
||||
@@ -1074,6 +1074,7 @@
|
||||
"verified_webauthn_login": "Inicio de sesión WebAuthn verificado",
|
||||
"verified_yotp_login": "Inicio de sesión Yubico OTP verificado",
|
||||
"admin_added": "Se agregó el administrador %s",
|
||||
"admin_api_modified": "Se guardaron los cambios en la API",
|
||||
"admin_removed": "Se eliminó el administrador %s",
|
||||
"app_links": "Se guardaron los cambios en los enlaces de aplicaciones",
|
||||
"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>.",
|
||||
"spam_score_reset": "Restablecer al valor predeterminado del servidor",
|
||||
"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)."
|
||||
},
|
||||
"warning": {
|
||||
|
||||
@@ -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.",
|
||||
"app_passwd_protocols": "Protocolli consentiti per la password dell'app",
|
||||
"tags": "Tag",
|
||||
"dry": "Simula sincronizzazione",
|
||||
"internal": "Interno",
|
||||
"internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.",
|
||||
"sender_allowed": "Consenti l'invio con questo alias"
|
||||
|
||||
@@ -416,24 +416,18 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
return navigator.credentials.create(json);
|
||||
}).then(function(cred) {
|
||||
return {
|
||||
clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON),
|
||||
attestationObject: arrayBufferToBase64(cred.response.attestationObject),
|
||||
key_id: keyId,
|
||||
tfa_method: 'webauthn',
|
||||
confirm_password: ''
|
||||
id: cred.id,
|
||||
rawId: arrayBufferToBase64(cred.rawId),
|
||||
response: {
|
||||
attestationObject: arrayBufferToBase64(cred.response.attestationObject),
|
||||
clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON)
|
||||
},
|
||||
type: cred.type
|
||||
};
|
||||
}).then(JSON.stringify).then(function(credentialData) {
|
||||
// send the attestation to the JSON API, like the regular WebAuthn modal does,
|
||||
// so that the credential is parsed and stored correctly
|
||||
return window.fetch('/api/v1/add/webauthn-tfa-registration', {method: 'POST', body: credentialData, cache: 'no-cache'});
|
||||
}).then(function(response) {
|
||||
return response.json();
|
||||
}).then(function(json) {
|
||||
if (json.success) {
|
||||
window.location = window.location.href.split('#')[0];
|
||||
} else {
|
||||
throw new Error(json.msg);
|
||||
}
|
||||
}).then(function(credData) {
|
||||
$('#setup_webauthn_register_data').val(JSON.stringify(credData));
|
||||
$('#setup_webauthn_reg_form input[name=set_tfa]').val('1');
|
||||
$('#setup_webauthn_reg_form').submit();
|
||||
}).catch(function(err) {
|
||||
$('#setup_webauthn_return_code').show().text(err.message || 'Registration failed');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
{# Fields of a mail policy (add modal, edit page): policy, id (prefix of the element ids), groups (choices: id, name,
|
||||
domain, address), mailboxes (choices), ro, label_class. Groups of a list are sent apart as <list>_groups; without
|
||||
group choices (none in the domain, no access to groups) the group fields are left out and stay unchanged. #}
|
||||
{% set entries = {} %}
|
||||
{% set listed = {} %}
|
||||
{% for key in ['send_allow', 'send_block', 'receive_allow', 'receive_block'] %}
|
||||
{% set plain = [] %}
|
||||
{% set group_ids = [] %}
|
||||
{% for entry in attribute(policy, key)|default([]) %}
|
||||
{% if entry starts with 'group:' %}
|
||||
{% set group_ids = group_ids|merge([entry|slice(6)]) %}
|
||||
{% else %}
|
||||
{% set plain = plain|merge([entry]) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% set entries = entries|merge({(key): plain|join("\n")}) %}
|
||||
{% set listed = listed|merge({(key): group_ids}) %}
|
||||
{% endfor %}
|
||||
{% for side in ['send', 'receive'] %}
|
||||
<legend class="fs-5 mt-4"><i class="bi {{ side == 'send' ? 'bi-send' : 'bi-inbox' }}"></i> {{ attribute(lang.mail_policy, side) }}</legend><hr>
|
||||
<p class="text-muted small">{{ attribute(lang.mail_policy, side ~ '_info') }}</p>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_{{ side }}_mode">{{ lang.mail_policy.mode }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" name="{{ side }}_mode" id="{{ id }}_{{ side }}_mode"{% if ro %} disabled{% endif %}>
|
||||
{% for mode in ['all', 'internal', 'list'] %}
|
||||
<option value="{{ mode }}"{% if attribute(policy, side ~ '_mode')|default('all') == mode %} selected{% endif %}>{{ attribute(lang.mail_policy, 'mode_' ~ side ~ '_' ~ mode) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small class="text-muted d-block">{{ lang.mail_policy.internal_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% for list in ['allow', 'block'] %}
|
||||
{% set key = side ~ '_' ~ list %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_{{ key }}">{{ attribute(lang.mail_policy, list ~ '_' ~ side) }}</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" rows="3" name="{{ key }}" id="{{ id }}_{{ key }}" placeholder="partner.example, user@example.org"{% if ro %} disabled{% endif %}>{{ attribute(entries, key) }}</textarea>
|
||||
<small class="text-muted d-block mb-1">{{ attribute(lang.mail_policy, 'entries_info_' ~ side) }}</small>
|
||||
{% if groups %}
|
||||
{% if not ro %}<input type="hidden" name="{{ key }}_groups" value="">{% endif %}
|
||||
<select data-live-search="true" data-width="100%" name="{{ key }}_groups" class="form-control mail-policy-groups" multiple title="{{ lang.mail_policy.groups }}"{% if ro %} disabled{% endif %}>
|
||||
{% for group in groups %}
|
||||
<option value="{{ group.id }}" data-domain="{{ group.domain }}"{% if group.id ~ '' in attribute(listed, key) %} selected{% endif %}>{{ group.name }}{% if group.address %} ({{ group.address }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_reject_text">{{ lang.mail_policy.reject_text }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="reject_text" id="{{ id }}_reject_text" maxlength="200" value="{{ policy.reject_text|default('') }}"{% if ro %} disabled{% endif %}>
|
||||
<small class="text-muted d-block">{{ lang.mail_policy.reject_text_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<legend class="fs-5 mt-4"><i class="bi bi-people"></i> {{ lang.mail_policy.assign }}</legend><hr>
|
||||
<p class="text-muted small">{{ lang.mail_policy.assign_info }}</p>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_mailboxes">{{ lang.mail_policy.mailboxes }}</label>
|
||||
<div class="col-sm-10">
|
||||
{% if not ro %}<input type="hidden" name="mailboxes" value="">{% endif %}
|
||||
<select data-live-search="true" data-width="100%" name="mailboxes" id="{{ id }}_mailboxes" class="form-control mail-policy-mailboxes" multiple title="{{ lang.mail_policy.mailboxes }}"{% if ro %} disabled{% endif %}>
|
||||
{% for mailbox in mailboxes %}
|
||||
<option value="{{ mailbox }}" data-domain="{{ mailbox|split('@')|last }}"{% if mailbox in policy.mailboxes|default([]) %} selected{% endif %}>{{ mailbox }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% if groups %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_groups">{{ lang.mail_policy.groups }}</label>
|
||||
<div class="col-sm-10">
|
||||
{% if not ro %}<input type="hidden" name="groups" value="">{% endif %}
|
||||
<select data-live-search="true" data-width="100%" name="groups" id="{{ id }}_groups" class="form-control mail-policy-groups" multiple title="{{ lang.mail_policy.groups }}"{% if ro %} disabled{% endif %}>
|
||||
{% for group in groups %}
|
||||
<option value="{{ group.id }}" data-domain="{{ group.domain }}"{% if group.id in policy.groups|default([]) %} selected{% endif %}>{{ group.name }}{% if group.address %} ({{ group.address }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,46 @@
|
||||
{% extends 'edit.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
{% if result %}
|
||||
{% set ro = not result.can_edit %}
|
||||
<h4>{{ lang.mail_policy.policy }}: {{ result.name }}</h4>
|
||||
<form data-id="edit_mail_policy" class="form-horizontal" role="form" method="post" autocapitalize="none" autocorrect="off">
|
||||
<input type="hidden" value="0" name="active">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="mail_policy_domain">{{ lang.mail_policy.domain }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="mail_policy_domain" value="{{ result.domain }}" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="mail_policy_name">{{ lang.mail_policy.name }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="name" id="mail_policy_name" value="{{ result.name }}" maxlength="100" required{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="mail_policy_description">{{ lang.mail_policy.description }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="description" id="mail_policy_description" value="{{ result.description }}" maxlength="255"{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'edit/mail-policy-fields.twig' with {'policy': result, 'id': 'mail_policy', 'groups': mail_policy_groups, 'mailboxes': mailboxes, 'ro': ro, 'label_class': ''} %}
|
||||
<div class="row mb-4">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="active"{% if result.active == 1 %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.mailbox.active }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not ro %}
|
||||
<div class="row">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="edit_mail_policy" data-item="{{ result.id }}" data-api-url='edit/mail-policy' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% else %}
|
||||
{{ parent() }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -97,11 +97,9 @@
|
||||
<div class="col-sm-10">
|
||||
<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 %}
|
||||
{% if domain == '*' or mailcow_cc_role == 'admin' %}
|
||||
<option data-subtext="Admin" value="{{ domain }}" disabled selected>
|
||||
{{ lang.edit.dont_check_sender_acl|format(domain) }}
|
||||
</option>
|
||||
{% endif %}
|
||||
<option data-subtext="Admin" value="{{ domain }}" disabled selected>
|
||||
{{ lang.edit.dont_check_sender_acl|format(domain) }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% for domain in sender_acl_handles.sender_acl_domains.rw %}
|
||||
<option value="{{ domain }}" selected>
|
||||
|
||||
@@ -36,10 +36,20 @@
|
||||
{% if acl.mailbox_groups == 1 %}
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-groups" role="tab" data-bs-toggle="tab" data-bs-target="#tab-groups">{{ lang.groups.groups }}</button></li>
|
||||
{% endif %}
|
||||
{% if mailcow_cc_role == 'admin' %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.policies }}</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.policies_tls }}</button></li>
|
||||
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-mail-policies" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mail-policies">{{ lang.mailbox.policies_mail }}</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% elseif acl.mail_policies == 1 %}
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-mail-policies" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mail-policies">{{ lang.mail_policy.mail_policies }}</button></li>
|
||||
{% endif %}
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#tab-syncjobs">{{ lang.syncjobs.sync_jobs }}</button></li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-filters" role="tab" data-bs-toggle="tab" data-bs-target="#tab-filters">{{ lang.mailbox.filters }}</button></li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-bcc" role="tab" data-bs-toggle="tab" data-bs-target="#tab-bcc">{{ lang.mailbox.address_rewriting }}</button></li>
|
||||
<li class="nav-item" role="presentation"{% if mailcow_cc_role != 'admin' %} class="d-none"{% endif %}><button class="nav-link" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.tls_policy_maps }}</button></li>
|
||||
</ul>
|
||||
|
||||
<div class="row">
|
||||
@@ -59,6 +69,9 @@
|
||||
{% if acl.mailbox_groups == 1 %}
|
||||
{% include 'mailbox/tab-groups.twig' %}
|
||||
{% endif %}
|
||||
{% if acl.mail_policies == 1 %}
|
||||
{% include 'mailbox/tab-mail-policies.twig' %}
|
||||
{% endif %}
|
||||
{% include 'mailbox/tab-syncjobs.twig' %}
|
||||
{% include 'mailbox/tab-filters.twig' %}
|
||||
{% include 'mailbox/tab-bcc.twig' %}
|
||||
@@ -75,6 +88,7 @@
|
||||
var lang = {{ lang_mailbox|raw }};
|
||||
lang.syncjobs = {{ lang_syncjobs|raw }};
|
||||
lang.groups = {{ lang_groups|raw }};
|
||||
lang.mail_policy = {{ lang_mail_policy|raw }};
|
||||
var lang_rl = {{ lang_rl|raw }};
|
||||
var lang_edit = {{ lang_edit|raw }};
|
||||
var lang_datatables = {{ lang_datatables|raw }};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<div class="tab-pane fade" id="tab-mail-policies" role="tabpanel" aria-labelledby="tab-mail-policies">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex fs-5">
|
||||
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-mail-policies" data-bs-toggle="collapse" aria-controls="collapse-tab-mail-policies">
|
||||
{{ lang.mail_policy.mail_policies }} <span class="badge bg-info table-lines"></span>
|
||||
</button>
|
||||
<span class="d-none d-md-block">{{ lang.mail_policy.mail_policies }} <span class="badge bg-info table-lines"></span></span>
|
||||
<div class="btn-group ms-auto d-flex">
|
||||
<button class="btn btn-xs btn-secondary refresh_table" data-table="mail_policy_table">{{ lang.admin.refresh }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="collapse-tab-mail-policies" class="card-body collapse" data-bs-parent="#mail-content">
|
||||
<p class="text-muted">{{ lang.mail_policy.info }}</p>
|
||||
<table id="mail_policy_table" class="table table-striped dt-responsive w-100"></table>
|
||||
<div class="mass-actions-mailbox mt-4">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="mail_policy" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
|
||||
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="mail_policy" data-api-url='edit/mail-policy' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="mail_policy" data-api-url='edit/mail-policy' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" data-action="delete_selected" data-id="mail_policy" data-api-url='delete/mail-policy' href="#">{{ lang.mailbox.remove }}</a></li>
|
||||
</ul>
|
||||
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addMailPolicyModal"><i class="bi bi-plus-lg"></i> {{ lang.mail_policy.add }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Test a rule: would the mail be accepted? Filled by js/site/mail-policy.js #}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">{{ lang.mail_policy.test }}</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small">{{ lang.mail_policy.test_info }}</p>
|
||||
<form class="row g-2 align-items-center" id="mail_policy_test" autocomplete="off">
|
||||
<div class="col-12 col-md-4">
|
||||
<input type="email" class="form-control" id="mail_policy_test_sender" placeholder="{{ lang.mail_policy.test_sender }}" aria-label="{{ lang.mail_policy.test_sender }}">
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<input type="email" class="form-control" id="mail_policy_test_recipient" placeholder="{{ lang.mail_policy.test_recipient }}" aria-label="{{ lang.mail_policy.test_recipient }}" required>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 d-flex align-items-center">
|
||||
<div class="form-check me-3">
|
||||
<input class="form-check-input" type="checkbox" id="mail_policy_test_login" checked>
|
||||
<label class="form-check-label" for="mail_policy_test_login">{{ lang.mail_policy.test_logged_in }}</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-sm btn-secondary ms-auto"><i class="bi bi-play"></i> {{ lang.mail_policy.test_run }}</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="mail_policy_test_result" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,50 @@
|
||||
<div class="modal fade" id="addMailPolicyModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">{{ lang.mail_policy.add }}</h3>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal" role="form" data-id="add_mail_policy" autocapitalize="none" autocorrect="off">
|
||||
<input type="hidden" value="0" name="active">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_domain">{{ lang.mail_policy.domain }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select data-live-search="true" name="domain" id="add_mail_policy_domain" class="form-control mail-policy-domain-select" required>
|
||||
{% for domain in domains %}
|
||||
<option>{{ domain }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_name">{{ lang.mail_policy.name }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="name" id="add_mail_policy_name" maxlength="100" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_description">{{ lang.mail_policy.description }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="description" id="add_mail_policy_description" maxlength="255">
|
||||
</div>
|
||||
</div>
|
||||
{% include 'edit/mail-policy-fields.twig' with {'policy': {}, 'id': 'add_mail_policy', 'groups': mail_policy_groups, 'mailboxes': mailboxes, 'ro': false, 'label_class': ' text-sm-end'} %}
|
||||
<div class="row mb-2">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="active" checked> {{ lang.mailbox.active }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="add_item" data-id="add_mail_policy" data-api-url='add/mail-policy' data-api-attr='{}' href="#">{{ lang.admin.add }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1362,3 +1362,6 @@
|
||||
{% if acl.mailbox_groups == 1 %}
|
||||
{% include "modals/mailbox_group.twig" %}
|
||||
{% endif %}
|
||||
{% if acl.mail_policies == 1 %}
|
||||
{% include "modals/mail_policy.twig" %}
|
||||
{% endif %}
|
||||
+7
-6
@@ -1,5 +1,5 @@
|
||||
# Version of the database schema
|
||||
x-db-version: &db_version DB_VERSION=02112026_1600
|
||||
x-db-version: &db_version DB_VERSION=07112026_1800
|
||||
|
||||
services:
|
||||
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
- redis
|
||||
|
||||
clamd-mailcow:
|
||||
image: ghcr.io/mailcow/clamd:1.4.6-2
|
||||
image: ghcr.io/mailcow/clamd:1.4.6-1
|
||||
restart: always
|
||||
depends_on:
|
||||
unbound-mailcow:
|
||||
@@ -348,7 +348,7 @@ services:
|
||||
- dovecot
|
||||
|
||||
postfix-mailcow:
|
||||
image: ghcr.io/mailcow/postfix:3.10.13-1
|
||||
image: ghcr.io/mailcow/postfix:3.10.12-2
|
||||
depends_on:
|
||||
mysql-mailcow:
|
||||
condition: service_started
|
||||
@@ -375,6 +375,7 @@ services:
|
||||
- REDISPASS=${REDISPASS}
|
||||
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
|
||||
- SPAMHAUS_DQS_KEY=${SPAMHAUS_DQS_KEY:-}
|
||||
- *db_version
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
ports:
|
||||
@@ -391,7 +392,7 @@ services:
|
||||
- postfix
|
||||
|
||||
postfix-tlspol-mailcow:
|
||||
image: ghcr.io/mailcow/postfix-tlspol:1.14.5
|
||||
image: ghcr.io/mailcow/postfix-tlspol:1.14.0
|
||||
depends_on:
|
||||
unbound-mailcow:
|
||||
condition: service_healthy
|
||||
@@ -428,7 +429,7 @@ services:
|
||||
- php-fpm-mailcow
|
||||
- sogo-mailcow
|
||||
- rspamd-mailcow
|
||||
image: ghcr.io/mailcow/nginx:1.30.6-3
|
||||
image: ghcr.io/mailcow/nginx:1.30.6-2
|
||||
dns:
|
||||
- ${IPV4_NETWORK:-172.22.1}.254
|
||||
environment:
|
||||
@@ -612,7 +613,7 @@ services:
|
||||
- watchdog
|
||||
|
||||
dockerapi-mailcow:
|
||||
image: ghcr.io/mailcow/dockerapi:2.16
|
||||
image: ghcr.io/mailcow/dockerapi:2.15
|
||||
security_opt:
|
||||
- label=disable
|
||||
restart: always
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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']]]);
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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]]]);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user