mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-11-11 02:45:23 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c48691fca7 | ||
|
|
2fc6868b72 | ||
|
|
043029137a | ||
|
|
0175fce9b5 | ||
|
|
1c24de19c3 | ||
|
|
fb85a9eb5c | ||
|
|
fc56d53029 | ||
|
|
2ac2edf0ad |
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 && \
|
||||
|
||||
@@ -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
|
||||
@@ -163,6 +178,45 @@ query = SELECT CONCAT('smtp_via_transport_maps:', nexthop) AS transport FROM tra
|
||||
AND destination = '%s';
|
||||
EOF
|
||||
|
||||
# Archive copies (journal) to the archive set in the admin UI: on port 591 the sender archive-bounce@localhost
|
||||
# sends the whole mail through archive_smtp (master.cf), whatever the recipient domain
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_archive_sender_filter_maps.cf
|
||||
# Autogenerated by mailcow
|
||||
user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = unix:/var/run/mysqld/mysqld.sock
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT CONCAT('FILTER archive_smtp:', nexthop) AS action FROM archive_transport
|
||||
WHERE active = '1'
|
||||
AND '%s' = 'archive-bounce@localhost'
|
||||
LIMIT 1;
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_archive_sasl_passwd_maps.cf
|
||||
# Autogenerated by mailcow
|
||||
user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = unix:/var/run/mysqld/mysqld.sock
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM archive_transport
|
||||
WHERE nexthop = '%s'
|
||||
AND active = '1'
|
||||
AND username != ''
|
||||
LIMIT 1;
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_archive_tls_policy_maps.cf
|
||||
# Autogenerated by mailcow
|
||||
user = ${DBUSER}
|
||||
password = ${DBPASS}
|
||||
hosts = unix:/var/run/mysqld/mysqld.sock
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT tls_policy FROM archive_transport
|
||||
WHERE nexthop = '%s'
|
||||
AND active = '1'
|
||||
LIMIT 1;
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_resource_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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -60,6 +60,7 @@ $_SESSION['acl']['sogo_access'] = "1";
|
||||
$_SESSION['acl']['protocol_access'] = "1";
|
||||
$_SESSION['acl']['mailbox_relayhost'] = "1";
|
||||
$_SESSION['acl']['unlimited_quota'] = "1";
|
||||
$_SESSION['acl']['archive'] = "1";
|
||||
|
||||
// One run at a time, released when the process ends
|
||||
$lock = fopen('/tmp/identity-sync.lock', 'c');
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
/watchdog@localhost$/ watchdog_discard:
|
||||
/^archive-bounce@localhost$/ archive_discard:
|
||||
/localhost$/ local:
|
||||
@@ -50,6 +50,9 @@ 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_archive_sender_filter_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_archive_sasl_passwd_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_archive_tls_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,
|
||||
|
||||
@@ -61,13 +61,15 @@ submission inet n - n - - smtpd
|
||||
-o non_smtpd_milters=
|
||||
-o syslog_name=postfix/quarantine
|
||||
|
||||
# used to send bcc mails
|
||||
# used to send bcc mails and archive copies (sender archive-bounce@localhost goes to the archive)
|
||||
# the archive FILTER comes after the one of MX-based transports, Postfix uses the last FILTER
|
||||
591 inet n - n - - smtpd
|
||||
-o smtpd_helo_restrictions=
|
||||
-o smtpd_client_restrictions=permit_mynetworks,reject
|
||||
-o smtpd_tls_auth_only=no
|
||||
-o smtpd_milters=
|
||||
-o non_smtpd_milters=
|
||||
-o smtpd_recipient_restrictions=check_recipient_mx_access,proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,check_sender_access,proxy:mysql:/opt/postfix/conf/sql/mysql_archive_sender_filter_maps.cf,permit_mynetworks,reject
|
||||
-o syslog_name=postfix/bcc
|
||||
|
||||
# enforced smtp connector
|
||||
@@ -81,6 +83,27 @@ smtp_enforced_tls unix - - n - - smtp
|
||||
smtp_via_transport_maps unix - - n - - smtp
|
||||
-o smtp_sasl_password_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf
|
||||
|
||||
# archive copies (journal) to the archive set in the admin UI: only mails from archive-bounce@localhost on 591 get here
|
||||
# (FILTER of mysql_archive_sender_filter_maps), sent unchanged, TLS by default, refusals are retried (soft_bounce)
|
||||
# until maximal_queue_lifetime
|
||||
archive_smtp unix - - n - - smtp
|
||||
-o soft_bounce=yes
|
||||
-o smtp_header_checks=
|
||||
-o smtp_mime_header_checks=
|
||||
-o smtp_nested_header_checks=
|
||||
-o smtp_body_checks=
|
||||
-o smtp_line_length_limit=0
|
||||
-o disable_mime_output_conversion=yes
|
||||
-o smtp_tls_security_level=encrypt
|
||||
-o smtp_tls_policy_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_archive_tls_policy_maps.cf
|
||||
-o smtp_sasl_auth_enable=yes
|
||||
-o smtp_sasl_password_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_archive_sasl_passwd_maps.cf
|
||||
-o smtp_sasl_tls_security_options=noanonymous
|
||||
-o syslog_name=postfix/archive
|
||||
# notices about archive copies (delays, bounces) go to archive-bounce@localhost and are dropped here
|
||||
archive_discard unix - - n - - discard
|
||||
-o syslog_name=postfix/archive
|
||||
|
||||
tlsproxy unix - - n - 0 tlsproxy
|
||||
dnsblog unix - - n - 0 dnsblog
|
||||
pickup fifo n - n 60 1 pickup
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// Archive decision for an accepted mail, asked by the MAILCOW_ARCHIVE symbol of rspamd
|
||||
// Request: JSON {from, rcpt[], user, bcc, qid, received, sha256}; answer: JSON {archive, rcpt, sender, headers}
|
||||
header('Content-Type: application/json');
|
||||
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("ARCHIVE: " . $e . PHP_EOL);
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
// Init Redis, it keeps the global switch; without it nothing is archived
|
||||
$redis = new Redis();
|
||||
try {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
$redis->auth(getenv("REDISPASS"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$redis = null;
|
||||
}
|
||||
|
||||
$request = json_decode(file_get_contents('php://input'), true);
|
||||
if (!is_array($request)) {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$archive = \Mailcow\Archive\ArchiveModule::create($pdo, \Mailcow\Core\Context::system(), $redis);
|
||||
echo json_encode($archive->journal->prepare($request));
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
error_log("ARCHIVE: " . $e->getMessage() . PHP_EOL);
|
||||
http_response_code(502);
|
||||
}
|
||||
@@ -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
|
||||
@@ -1013,3 +956,134 @@ rspamd_config:register_symbol({
|
||||
end,
|
||||
priority = 1
|
||||
})
|
||||
|
||||
-- Archive: a copy of every accepted mail of an archived mailbox goes by SMTP to the archive, through Postfix (port 591)
|
||||
rspamd_config:register_symbol({
|
||||
name = 'MAILCOW_ARCHIVE',
|
||||
type = 'idempotent',
|
||||
flags = 'empty,explicit_disable,ignore_passthrough',
|
||||
callback = function(task)
|
||||
local rspamd_http = require "rspamd_http"
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local rspamd_text = require "rspamd_text"
|
||||
local rspamd_util = require "rspamd_util"
|
||||
local hash = require "rspamd_cryptobox_hash"
|
||||
local lua_smtp = require "lua_smtp"
|
||||
local cjson = require "cjson"
|
||||
|
||||
-- accepted mails only: no reject, soft reject (greylisting included), discard or quarantine
|
||||
local action = task:get_metric_action('default')
|
||||
local accepted = (action == 'no action' or action == 'add header' or action == 'rewrite subject' or action == 'greylist')
|
||||
local pre_accept = false
|
||||
if task.has_pre_result then
|
||||
local has_pre, pre_action = task:has_pre_result()
|
||||
pre_accept = (has_pre and pre_action == 'accept')
|
||||
end
|
||||
if not accepted and not pre_accept then
|
||||
return
|
||||
end
|
||||
|
||||
local rcpts = {}
|
||||
for _, r in ipairs(task:get_recipients('smtp') or {}) do
|
||||
if r.addr and r.addr ~= '' then
|
||||
table.insert(rcpts, r.addr)
|
||||
end
|
||||
end
|
||||
if #rcpts == 0 then
|
||||
return
|
||||
end
|
||||
local from = task:get_from('smtp')
|
||||
local from_addr = (from and from[1] and from[1].addr) or ''
|
||||
local qid = task:get_queue_id() or ''
|
||||
-- the BCC symbol sends copies under the same condition, they bypass rspamd
|
||||
local bcc = (pre_accept or (action ~= 'greylist' and accepted)) and not task:has_symbol('ENCRYPTED_CHAT')
|
||||
local redis_params = rspamd_parse_redis_server('archive')
|
||||
|
||||
-- not archived: counted and listed for the archive status in the UI
|
||||
-- the Message-ID finds the mail in the archive log of the UI
|
||||
local message_id = task:get_message_id()
|
||||
if not message_id or message_id == 'undef' then
|
||||
message_id = ''
|
||||
end
|
||||
local function dropped(reason)
|
||||
rspamd_logger.errx(task, "archive: mail %s is not archived: %s", qid, reason)
|
||||
rspamd_redis_make_request(task, redis_params, 'ARCHIVE_DROPPED', true, function() end, 'INCR', {'ARCHIVE_DROPPED'})
|
||||
rspamd_redis_make_request(task, redis_params, 'ARCHIVE_DROPPED_LOG', true, function() end, 'LPUSH',
|
||||
{'ARCHIVE_DROPPED_LOG', cjson.encode({time = os.time(), qid = qid, reason = tostring(reason), message_id = message_id})})
|
||||
rspamd_redis_make_request(task, redis_params, 'ARCHIVE_DROPPED_LOG', true, function() end, 'LTRIM', {'ARCHIVE_DROPPED_LOG', '0', '99'})
|
||||
end
|
||||
|
||||
local function archive()
|
||||
-- the archive gets the mail as accepted, with CRLF line ends and a final CRLF (also what the hash covers)
|
||||
local content = rspamd_text.fromstring(tostring(task:get_content()))
|
||||
content:normalize_newlines('smtp')
|
||||
if #content < 2 or content:at(#content) ~= 10 or content:at(#content - 1) ~= 13 then
|
||||
content = rspamd_text.fromtable({content, '\r\n'})
|
||||
end
|
||||
local body = {
|
||||
from = from_addr,
|
||||
rcpt = rcpts,
|
||||
user = task:get_user() or '',
|
||||
bcc = bcc,
|
||||
qid = qid,
|
||||
received = rspamd_util.get_time(),
|
||||
sha256 = hash.create_specific('sha256', content):hex()
|
||||
}
|
||||
|
||||
local function sent_cb(ok, err)
|
||||
if not ok then
|
||||
dropped(err)
|
||||
end
|
||||
end
|
||||
|
||||
local function journal_cb(err, code, res_body)
|
||||
if err or code ~= 200 then
|
||||
dropped(string.format('owner lookup failed (%s, %s)', err, code))
|
||||
return
|
||||
end
|
||||
local ok, res = pcall(cjson.decode, tostring(res_body))
|
||||
if not ok or type(res) ~= 'table' then
|
||||
dropped('invalid answer of the owner lookup')
|
||||
return
|
||||
end
|
||||
if res.archive ~= true then
|
||||
return
|
||||
end
|
||||
-- lua_smtp ends the data with CRLF.CRLF, so the last CRLF of the content is left to it
|
||||
lua_smtp.sendmail({
|
||||
task = task,
|
||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
||||
port = 591,
|
||||
from = res.sender,
|
||||
recipients = res.rcpt,
|
||||
helo = 'archive',
|
||||
timeout = 20,
|
||||
}, {res.headers or '', content:span(1, #content - 2)}, sent_cb)
|
||||
end
|
||||
|
||||
rspamd_http.request({
|
||||
task = task,
|
||||
url = 'http://nginx:8081/archive.php',
|
||||
body = cjson.encode(body),
|
||||
timeout = (#rcpts > 100) and 10.0 or 2.0,
|
||||
callback = journal_cb,
|
||||
headers = {['Content-Type'] = 'application/json'},
|
||||
})
|
||||
end
|
||||
|
||||
local function enabled_cb(err, data)
|
||||
if not err and type(data) == 'string' and data == '1' then
|
||||
archive()
|
||||
end
|
||||
end
|
||||
|
||||
rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
'ARCHIVE_ENABLED', -- hash key
|
||||
false, -- is write
|
||||
enabled_cb, --callback
|
||||
'GET', -- command
|
||||
{'ARCHIVE_ENABLED'} -- arguments
|
||||
)
|
||||
end
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -46,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(),
|
||||
'archive_enabled' => \Mailcow\Archive\ArchiveModule::fromGlobals()->settings->enabled(),
|
||||
];
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
|
||||
@@ -13,6 +13,7 @@ $js_minifier->add('/web/js/site/admin.js');
|
||||
$js_minifier->add('/web/js/presets/rspamd.js');
|
||||
$js_minifier->add('/web/js/site/pwgen.js');
|
||||
$js_minifier->add('/web/js/site/identity.js');
|
||||
$js_minifier->add('/web/js/site/archive.js');
|
||||
|
||||
// all domains
|
||||
$domains = mailbox('get', 'domains');
|
||||
@@ -116,6 +117,7 @@ $template_data = [
|
||||
'ui_theme' => customize('get', 'ui_theme'),
|
||||
'password_complexity' => password_complexity('get'),
|
||||
'imapsync_settings' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->api->getSettings(),
|
||||
'archive_settings' => \Mailcow\Archive\ArchiveModule::fromGlobals()->api->getSettings(),
|
||||
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
||||
'cors_settings' => $cors_settings,
|
||||
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
|
||||
|
||||
@@ -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()}));
|
||||
+7863
-14052
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":"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 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":"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 truncated
|
||||
{"version":3,"file":"swagger-ui-es-bundle.js","mappings":";wCAEAA,EAAQ,OAAc,EACtB,IAAIC,EAAuB,wCACvBC,EAAoB,mBACpBC,EAAsB,oBACtBC,EAAsB,qDACtBC,EAAiB,oBACjBC,EAA0B,CAAC,IAAK,KA+BpCN,EAAQ,EArBR,SAAqBO,GACjB,IAN0BC,EAMtBC,GANsBD,EAMcD,GAAO,GALxCC,EAAIE,QAAQR,GAAmB,SAAUS,EAAOC,GACnD,OAAOC,OAAOC,aAAaF,EAC/B,KAIKF,QAAQP,EAAqB,IAC7BO,QAAQN,EAAqB,IAC7BW,OACL,IAAKN,EACD,MAAO,cAEX,GAjBJ,SAAsCF,GAClC,OAAOD,EAAwBU,QAAQT,EAAI,KAAO,CACtD,CAeQU,CAA6BR,GAC7B,OAAOA,EAEX,IAAIS,EAAwBT,EAAaE,MAAMN,GAC/C,IAAKa,EACD,OAAOT,EAEX,IAAIU,EAAYD,EAAsB,GACtC,OAAIjB,EAAqBmB,KAAKD,GACnB,cAEJV,CACX,2JCnCA,SAASY,EAAQC,GAWf,OATED,EADoB,mBAAXE,QAAoD,iBAApBA,OAAOC,SACtC,SAAUF,GAClB,cAAcA,CAChB,EAEU,SAAUA,GAClB,OAAOA,GAAyB,mBAAXC,QAAyBD,EAAIG,cAAgBF,QAAUD,IAAQC,OAAOG,UAAY,gBAAkBJ,CAC3H,EAGKD,EAAQC,EACjB,CAQA,SAASK,EAAkBC,EAAQC,GACjC,IAAK,IAAIC,EAAI,EAAGA,EAAID,EAAME,OAAQD,IAAK,CACrC,IAAIE,EAAaH,EAAMC,GACvBE,EAAWC,WAAaD,EAAWC,aAAc,EACjDD,EAAWE,cAAe,EACtB,UAAWF,IAAYA,EAAWG,UAAW,GACjDC,OAAOC,eAAeT,EAAQI,EAAWM,IAAKN,EAChD,CACF,CAQA,SAASO,EAAgBjB,EAAKgB,EAAKE,GAYjC,OAXIF,KAAOhB,EACTc,OAAOC,eAAef,EAAKgB,EAAK,CAC9BE,MAAOA,EACPP,YAAY,EACZC,cAAc,EACdC,UAAU,IAGZb,EAAIgB,GAAOE,EAGNlB,CACT,CAEA,SAASmB,EAAQC,EAAQC,GACvB,IAAIC,EAAOR,OAAOQ,KAAKF,GAEvB,GAAIN,OAAOS,sBAAuB,CAChC,IAAIC,EAAUV,OAAOS,sBAAsBH,GACvCC,IAAgBG,EAAUA,EAAQC,QAAO,SAAUC,GACrD,OAAOZ,OAAOa,yBAAyBP,EAAQM,GAAKf,UACtD,KACAW,EAAKM,KAAKC,MAAMP,EAAME,EACxB,CAEA,OAAOF,CACT,CAqCA,SAASQ,EAAgBC,GAIvB,OAHAD,EAAkBhB,OAAOkB,eAAiBlB,OAAOmB,eAAiB,SAAyBF,GACzF,OAAOA,EAAEG,WAAapB,OAAOmB,eAAeF,EAC9C,EACOD,EAAgBC,EACzB,CAEA,SAASI,EAAgBJ,EAAGK,GAM1B,OALAD,EAAkBrB,OAAOkB,gBAAkB,SAAyBD,EAAGK,GAErE,OADAL,EAAEG,UAAYE,EACPL,CACT,EAEOI,EAAgBJ,EAAGK,EAC5B,CAUA,SAASC,EAA2BC,EAAMC,GACxC,OAAIA,GAAyB,iBAATA,GAAqC,mBAATA,EATlD,SAAgCD,GAC9B,QAAa,IAATA,EACF,MAAM,IAAIE,eAAe,6DAG3B,OAAOF,CACT,CAOSG,CAAuBH,GAHrBC,CAIX,CAEA,IAAIG,EAAU,CAAC,EASf,SAASC,EAAIC,EAAY5B,EAAK6B,GAC5B,OALF,SAAmBD,GACjB,OAAOA,OACT,CAGME,CAAUF,GACLC,EAVX,SAAmBD,GACjB,OAAsB,OAAfA,GAA+C,WAAxB7C,EAAQ6C,IAAsD,mBAAnBA,EAAWD,KAAgD,mBAAnBC,EAAWG,GAC9H,CAWMC,CAAUJ,GACLA,EAAWG,IAAI/B,GAAO4B,EAAWD,IAAI3B,GAAO6B,EAG9CI,eAAeV,KAAKK,EAAY5B,GAAO4B,EAAW5B,GAAO6B,CAClE,CACA,SAASK,EAAMN,EAAYO,EAASN,GAGlC,IAFA,IAAIrC,EAAI,EAEDA,IAAM2C,EAAQ1C,QAGnB,IAFAmC,EAAaD,EAAIC,EAAYO,EAAQ3C,KAAMkC,MAExBA,EACjB,OAAOG,EAIX,OAAOD,CACT,CAEA,SAASQ,EAAMC,GACb,IAAIC,EAAOC,UAAU9C,OAAS,QAAsB+C,IAAjBD,UAAU,GAAmBA,UAAU,GAAK,CAAC,EAC5EE,EAAOF,UAAU9C,OAAS,QAAsB+C,IAAjBD,UAAU,GAAmBA,UAAU,GAAK,CAAC,EAC5EG,EAKN,SAAuBJ,EAAMG,GAC3B,OAAO,SAAUE,GACf,GAAoB,iBAATA,EACT,OAAO,IAAAC,IAAGH,EAAKE,GAAOL,EAAKK,IACtB,GAAIE,MAAMC,QAAQH,GACvB,OAAO,IAAAC,IAAGV,EAAMO,EAAME,GAAOT,EAAMI,EAAMK,IAG3C,MAAM,IAAII,UAAU,0CAA4CJ,EAClE,CACF,CAfkBK,CAAcV,EAAMG,GAChCQ,EAAYZ,GAAUvC,OAAOQ,KAzGnC,SAAwBhB,GACtB,IAAK,IAAIE,EAAI,EAAGA,EAAI+C,UAAU9C,OAAQD,IAAK,CACzC,IAAI0D,EAAyB,MAAhBX,UAAU/C,GAAa+C,UAAU/C,GAAK,CAAC,EAEhDA,EAAI,EACNW,EAAQ+C,GAAQ,GAAMC,SAAQ,SAAUnD,GACtCC,EAAgBX,EAAQU,EAAKkD,EAAOlD,GACtC,IACSF,OAAOsD,0BAChBtD,OAAOuD,iBAAiB/D,EAAQQ,OAAOsD,0BAA0BF,IAEjE/C,EAAQ+C,GAAQC,SAAQ,SAAUnD,GAChCF,OAAOC,eAAeT,EAAQU,EAAKF,OAAOa,yBAAyBuC,EAAQlD,GAC7E,GAEJ,CAEA,OAAOV,CACT,CAuFwCgE,CAAe,CAAC,EAAGb,EAAM,CAAC,EAAGH,IACnE,OAAOW,EAAUM,MAAMb,EACzB,CA0CA,QA1BA,SAAUc,GAGR,SAASC,IAGP,OApLJ,SAAyBC,EAAUC,GACjC,KAAMD,aAAoBC,GACxB,MAAM,IAAIZ,UAAU,oCAExB,CA8KIa,CAAgBC,KAAMJ,GAEfpC,EAA2BwC,KAAM/C,EAAgB2C,GAAwB5C,MAAMgD,KAAMtB,WAC9F,CArKF,IAAsBoB,EAAaG,EAAYC,EA+K7C,OAxHF,SAAmBC,EAAUC,GAC3B,GAA0B,mBAAfA,GAA4C,OAAfA,EACtC,MAAM,IAAIlB,UAAU,sDAGtBiB,EAAS5E,UAAYU,OAAOoE,OAAOD,GAAcA,EAAW7E,UAAW,CACrED,YAAa,CACXe,MAAO8D,EACPnE,UAAU,EACVD,cAAc,KAGdqE,GAAY9C,EAAgB6C,EAAUC,EAC5C,CA2FEE,CAAUV,EAAwBD,GA/JdG,EAuKPF,EAvKoBK,EAuKI,CAAC,CACpC9D,IAAK,wBACLE,MAAO,SAA+BkE,GACpC,IAAIC,EAAY9B,UAAU9C,OAAS,QAAsB+C,IAAjBD,UAAU,GAAmBA,UAAU,GAAK,CAAC,EACrF,OAAQH,EAAMyB,KAAKS,cAAeT,KAAKtE,MAAO6E,EAAW,mBAAqBhC,EAAMyB,KAAKU,eAAgBV,KAAKW,MAAOH,EAAW,iBAClI,IA3KEP,GAAYzE,EAAkBsE,EAAYvE,UAAW0E,GACrDC,GAAa1E,EAAkBsE,EAAaI,GA6KzCN,CACT,CAlBA,CAkBE,yDC5MF,MAAMgB,EAAgBC,IACpB,MAAMC,EAAYD,EAAItG,QAAQ,MAAO,KAAKA,QAAQ,MAAO,KAEzD,IACE,OAAOwG,mBAAmBD,EAC5B,CAAE,MACA,OAAOA,CACT,GAGa,MAAME,UAAcpB,EAAuBtE,cAAA,SAAAoD,WAAAtC,IAAA,qBAiBxC6E,IAC0B,IAAnCC,IAAAD,GAAGvD,KAAHuD,EAAY,kBACRL,EAAcK,EAAI1G,QAAQ,sBAAuB,MAEX,IAA1C2G,IAAAD,GAAGvD,KAAHuD,EAAY,yBACRL,EAAcK,EAAI1G,QAAQ,8BAA+B,UADlE,IAGD6B,IAAA,qBAEe+E,IACd,IAAI,cAAEC,GAAkBpB,KAAKtE,MAE7B,OAAO0F,EAAcC,eAAeF,EAAM,GAC3C,CAEDG,SACE,IAAI,aAAEC,EAAY,WAAEC,EAAU,cAAEJ,EAAa,OAAEK,EAAM,SAAEC,EAAQ,KAAE5C,EAAI,MAAE6C,EAAK,SAAEC,EAAQ,YAAEC,EAAW,gBACjGC,EAAe,iBAAEC,GAAoB/B,KAAKtE,MAC5C,MAAMsG,EAAcT,EAAa,eAC3BU,EAAaV,EAAa,cAC1BW,EAAiBX,EAAa,kBACpC,IAAIY,EAAO,SACPC,EAAQX,GAAUA,EAAO3D,IAAI,SAWjC,IARMgB,GAAQsD,IACZtD,EAAOkB,KAAKqC,aAAcD,KAGtBX,GAAUW,IACdX,EAASzB,KAAKsC,aAAcxD,KAG1B2C,EACF,OAAOc,EAAAA,cAAA,QAAMC,UAAU,qBACfD,EAAAA,cAAA,QAAMC,UAAU,qBAAsBX,GAAe/C,GACrDyD,EAAAA,cAAA,OAAKE,IAAKC,EAAQ,MAAiCC,OAAQ,OAAQC,MAAO,UAIpF,MAAMC,EAAazB,EAAc0B,UAAYrB,EAAO3D,IAAI,cAIxD,OAHA6D,OAAkBhD,IAAVgD,EAAsBA,IAAUS,EACxCD,EAAOV,GAAUA,EAAO3D,IAAI,SAAWqE,EAEhCA,GACL,IAAK,SACH,OAAOILine 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 {
|
||||
|
||||
@@ -45,6 +45,7 @@ $template_data = [
|
||||
'lang_edit' => json_encode($lang['edit']),
|
||||
'lang_datatables' => json_encode($lang['datatables']),
|
||||
'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(),
|
||||
'archive_enabled' => \Mailcow\Archive\ArchiveModule::fromGlobals()->settings->enabled(),
|
||||
];
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
|
||||
+5
-1
@@ -65,7 +65,8 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'mta_sts' => $mta_sts,
|
||||
'mailboxes' => mailbox('get', 'mailboxes', $_GET["domain"]),
|
||||
'aliases' => mailbox('get', 'aliases', $_GET["domain"], 'address'),
|
||||
'alias_domains' => mailbox('get', 'alias_domains', $_GET["domain"])
|
||||
'alias_domains' => mailbox('get', 'alias_domains', $_GET["domain"]),
|
||||
'archive_enabled' => \Mailcow\Archive\ArchiveModule::fromGlobals()->settings->enabled()
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -75,6 +76,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
$template_data = [
|
||||
'template' => $domain_template,
|
||||
'rl' => ['frame' => $domain_template['attributes']['rl_frame']],
|
||||
'archive_enabled' => \Mailcow\Archive\ArchiveModule::fromGlobals()->settings->enabled(),
|
||||
];
|
||||
$template = 'edit/domain-templates.twig';
|
||||
$result = true;
|
||||
@@ -85,6 +87,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
$template_data = [
|
||||
'template' => $mailbox_template,
|
||||
'rl' => ['frame' => $mailbox_template['attributes']['rl_frame']],
|
||||
'archive_enabled' => \Mailcow\Archive\ArchiveModule::fromGlobals()->settings->enabled(),
|
||||
];
|
||||
$template = 'edit/mailbox-templates.twig';
|
||||
$result = true;
|
||||
@@ -162,6 +165,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'user_acls' => acl('get', 'user', $mailbox),
|
||||
'mailbox_details' => $result,
|
||||
'iam_settings' => $iam_settings,
|
||||
'archive_enabled' => \Mailcow\Archive\ArchiveModule::fromGlobals()->settings->enabled(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -539,6 +539,7 @@ function sys_mail($_data) {
|
||||
$mail->Body = $mass_text;
|
||||
}
|
||||
$mail->XMailer = 'MooMassMail';
|
||||
$archive = \Mailcow\Archive\ArchiveModule::fromGlobals();
|
||||
foreach ($rcpts as $rcpt) {
|
||||
$mail->AddAddress($rcpt);
|
||||
if (!$mail->send()) {
|
||||
@@ -548,6 +549,10 @@ function sys_mail($_data) {
|
||||
'msg' => 'Mailer error (RCPT "' . htmlspecialchars($rcpt) . '"): ' . str_replace('https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting', '', $mail->ErrorInfo)
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Delivered by LMTP without rspamd, so the archive gets it here
|
||||
$archive->journal->submitDelivered($mail->getSentMIMEMessage(), $mass_from, array($rcpt), 'massmail');
|
||||
}
|
||||
$mail->ClearAllRecipients();
|
||||
}
|
||||
}
|
||||
@@ -1394,18 +1399,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 +1421,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 +1692,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 +2425,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 +2449,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 +3111,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 +3162,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;
|
||||
}
|
||||
|
||||
@@ -366,6 +366,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_unknown_only'];
|
||||
$backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $DOMAIN_DEFAULT_ATTRIBUTES['backupmx'];
|
||||
$gal = (isset($_data['gal'])) ? intval($_data['gal']) : $DOMAIN_DEFAULT_ATTRIBUTES['gal'];
|
||||
$archive = (intval((isset($_data['archive'])) ? $_data['archive'] : ($DOMAIN_DEFAULT_ATTRIBUTES['archive'] ?? 0)) == 1) ? 1 : 0;
|
||||
if ($relay_all_recipients == 1) {
|
||||
$backupmx = '1';
|
||||
}
|
||||
@@ -421,8 +422,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => '%@' . $domain
|
||||
));
|
||||
// save domain
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`)
|
||||
VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients)");
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`, `archive`)
|
||||
VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients, :archive)");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
':description' => $description,
|
||||
@@ -435,7 +436,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':gal' => $gal,
|
||||
':active' => $active,
|
||||
':relay_unknown_only' => $relay_unknown_only,
|
||||
':relay_all_recipients' => $relay_all_recipients
|
||||
':relay_all_recipients' => $relay_all_recipients,
|
||||
':archive' => $archive
|
||||
));
|
||||
// save tags
|
||||
foreach($tags as $index => $tag){
|
||||
@@ -940,6 +942,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0;
|
||||
// Shared mailbox: no login of its own, members open it through group shares
|
||||
$shared = (isset($_data['shared'])) ? intval($_data['shared']) : 0;
|
||||
// Archiving comes from the template unless the caller may set it
|
||||
$archive = \Mailcow\Archive\Model\Mailbox::setting((isset($_data['archive']) && hasACLAccess("archive")) ? $_data['archive'] : ($MAILBOX_DEFAULT_ATTRIBUTES['archive'] ?? 'inherit'));
|
||||
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
|
||||
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
|
||||
// Validate quarantine_category
|
||||
@@ -974,6 +978,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
'dav_access' => strval($dav_access),
|
||||
'relayhost' => strval($relayhost),
|
||||
'shared' => strval($shared),
|
||||
'archive' => $archive,
|
||||
'passwd_update' => time(),
|
||||
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
|
||||
'quarantine_notification' => strval($quarantine_notification),
|
||||
@@ -1525,6 +1530,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
|
||||
$attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
|
||||
$attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
|
||||
$attr['archive'] = (isset($_data['archive']) && intval($_data['archive']) == 1) ? 1 : 0;
|
||||
$attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
|
||||
$attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
|
||||
|
||||
@@ -1600,6 +1606,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
|
||||
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
|
||||
$attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']);
|
||||
$attr["archive"] = \Mailcow\Archive\Model\Mailbox::setting($_data['archive'] ?? $MAILBOX_DEFAULT_ATTRIBUTES['archive']);
|
||||
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']);
|
||||
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1;
|
||||
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
|
||||
@@ -2488,6 +2495,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal'];
|
||||
$description = (!empty($_data['description']) && isset($_SESSION['acl']['domain_desc']) && $_SESSION['acl']['domain_desc'] == "1") ? $_data['description'] : $is_now['description'];
|
||||
(int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['domain_relayhost']) && $_SESSION['acl']['domain_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['relayhost']);
|
||||
$archive = (isset($_data['archive']) && hasACLAccess("archive")) ? ((intval($_data['archive']) == 1) ? 1 : 0) : intval($is_now['archive']);
|
||||
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
|
||||
}
|
||||
else {
|
||||
@@ -2501,11 +2509,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE `domain` SET
|
||||
`description` = :description,
|
||||
`gal` = :gal
|
||||
`gal` = :gal,
|
||||
`archive` = :archive
|
||||
WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':description' => $description,
|
||||
':gal' => $gal,
|
||||
':archive' => $archive,
|
||||
':domain' => $domain
|
||||
));
|
||||
// save tags
|
||||
@@ -2524,10 +2534,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(
|
||||
@@ -2542,6 +2548,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||
$backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx'];
|
||||
$gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal'];
|
||||
$archive = (isset($_data['archive'])) ? ((intval($_data['archive']) == 1) ? 1 : 0) : intval($is_now['archive']);
|
||||
$relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients'];
|
||||
$relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $is_now['relay_unknown_only'];
|
||||
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost'];
|
||||
@@ -2656,6 +2663,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`relay_unknown_only` = :relay_unknown_only,
|
||||
`backupmx` = :backupmx,
|
||||
`gal` = :gal,
|
||||
`archive` = :archive,
|
||||
`active` = :active,
|
||||
`quota` = :quota,
|
||||
`defquota` = :defquota,
|
||||
@@ -2670,6 +2678,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':relay_unknown_only' => $relay_unknown_only,
|
||||
':backupmx' => $backupmx,
|
||||
':gal' => $gal,
|
||||
':archive' => $archive,
|
||||
':active' => $active,
|
||||
':quota' => $quota,
|
||||
':defquota' => $defquota,
|
||||
@@ -2696,10 +2705,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(
|
||||
@@ -2761,6 +2766,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
|
||||
$attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
|
||||
$attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
|
||||
$attr['archive'] = (isset($_data['archive'])) ? ((intval($_data['archive']) == 1) ? 1 : 0) : intval($is_now['attributes']['archive'] ?? 0);
|
||||
$attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
|
||||
$attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
|
||||
|
||||
@@ -2822,6 +2828,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
(int)$eas_access = (isset($_data['eas_access']) && hasACLAccess("protocol_access")) ? intval($_data['eas_access']) : intval($is_now['attributes']['eas_access']);
|
||||
(int)$dav_access = (isset($_data['dav_access']) && hasACLAccess("protocol_access")) ? intval($_data['dav_access']) : intval($is_now['attributes']['dav_access']);
|
||||
(int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
|
||||
$archive = \Mailcow\Archive\Model\Mailbox::setting((isset($_data['archive']) && hasACLAccess("archive")) ? $_data['archive'] : ($is_now['attributes']['archive'] ?? null));
|
||||
(int)$shared = (isset($_data['shared'])) ? intval($_data['shared']) : intval($is_now['attributes']['shared'] ?? 0);
|
||||
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
|
||||
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
|
||||
@@ -3122,6 +3129,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.eas_access', :eas_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.dav_access', :dav_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.archive', :archive),
|
||||
`attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email),
|
||||
`attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash)
|
||||
WHERE `username` = :username");
|
||||
@@ -3139,6 +3147,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':smtp_access' => $smtp_access,
|
||||
':eas_access' => $eas_access,
|
||||
':dav_access' => $dav_access,
|
||||
':archive' => $archive,
|
||||
':recovery_email' => $pw_recovery_email,
|
||||
':relayhost' => $relayhost,
|
||||
':shared' => strval($shared),
|
||||
@@ -3178,10 +3187,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) {
|
||||
}
|
||||
}
|
||||
@@ -3546,6 +3551,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value'];
|
||||
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];
|
||||
$attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : $is_now['force_tfa'];
|
||||
$attr["archive"] = \Mailcow\Archive\Model\Mailbox::setting($_data['archive'] ?? ($is_now['attributes']['archive'] ?? null));
|
||||
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access'];
|
||||
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active'];
|
||||
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
|
||||
@@ -4000,9 +4006,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
|
||||
@@ -4697,6 +4700,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`relay_unknown_only`,
|
||||
`backupmx`,
|
||||
`gal`,
|
||||
`archive`,
|
||||
`active`
|
||||
FROM `domain` WHERE `domain`= :domain");
|
||||
$stmt->execute(array(
|
||||
@@ -4757,6 +4761,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$domaindata['backupmx_int'] = $row['backupmx'];
|
||||
$domaindata['gal'] = $row['gal'];
|
||||
$domaindata['gal_int'] = $row['gal'];
|
||||
$domaindata['archive'] = $row['archive'];
|
||||
$domaindata['rl'] = $rl;
|
||||
$domaindata['active'] = $row['active'];
|
||||
$domaindata['active_int'] = $row['active'];
|
||||
@@ -4844,6 +4849,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
if (preg_match('/y|yes/i', getenv('MASTER'))) {
|
||||
$stmt = $pdo->prepare("SELECT
|
||||
`domain`.`backupmx`,
|
||||
`domain`.`active` AS `domain_active`,
|
||||
`domain`.`archive` AS `domain_archive`,
|
||||
`mailbox`.`username`,
|
||||
`mailbox`.`name`,
|
||||
`mailbox`.`active`,
|
||||
@@ -4866,6 +4873,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
else {
|
||||
$stmt = $pdo->prepare("SELECT
|
||||
`domain`.`backupmx`,
|
||||
`domain`.`active` AS `domain_active`,
|
||||
`domain`.`archive` AS `domain_archive`,
|
||||
`mailbox`.`username`,
|
||||
`mailbox`.`name`,
|
||||
`mailbox`.`active`,
|
||||
@@ -4905,6 +4914,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$mailboxdata['created'] = $row['created'];
|
||||
$mailboxdata['modified'] = $row['modified'];
|
||||
$mailboxdata['authsource'] = ($row['authsource']) ? $row['authsource'] : 'mailcow';
|
||||
// Mails of the mailbox go to the archive: global switch, mailbox setting ("inherit" follows the domain), receives mail
|
||||
static $archive_enabled = null;
|
||||
if ($archive_enabled === null) {
|
||||
try {
|
||||
$archive_enabled = \Mailcow\Archive\ArchiveModule::fromGlobals()->settings->enabled();
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$archive_enabled = false;
|
||||
}
|
||||
}
|
||||
$mailboxdata['archive'] = ($archive_enabled && $row['active'] != 0 && $row['domain_active'] == 1 && $row['backupmx'] == 0
|
||||
&& \Mailcow\Archive\Model\Mailbox::effective($mailboxdata['attributes']['archive'] ?? null, $row['domain_archive'])) ? 1 : 0;
|
||||
|
||||
if ($mailboxdata['percent_in_use'] === '- ') {
|
||||
$mailboxdata['percent_class'] = "info";
|
||||
@@ -4938,9 +4959,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 +4972,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 +5013,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 +5952,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 +6004,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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,7 @@ function quarantine($_action, $_data = null) {
|
||||
$mail->addAttachment($msg_tmpf, $detail_row['qid'] . '.eml');
|
||||
$mail->Body = sprintf($lang['quarantine']['release_body']);
|
||||
$mail->send();
|
||||
$released_msg = $mail->getSentMIMEMessage();
|
||||
unlink($msg_tmpf);
|
||||
}
|
||||
catch (phpmailerException $e) {
|
||||
@@ -170,6 +171,7 @@ function quarantine($_action, $_data = null) {
|
||||
}
|
||||
elseif ($release_format == 'raw') {
|
||||
$detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']);
|
||||
$released_msg = $detail_row['msg'];
|
||||
// dot-stuffing per RFC5321 4.5.2: escape leading dots
|
||||
$detail_row['msg'] = preg_replace('~^\.~m', '..', $detail_row['msg']);
|
||||
$postfix_talk = array(
|
||||
@@ -213,6 +215,10 @@ function quarantine($_action, $_data = null) {
|
||||
}
|
||||
fclose($smtp_connection);
|
||||
}
|
||||
// Port 590 bypasses rspamd, so the archive gets released mails here
|
||||
if (isset($released_msg)) {
|
||||
\Mailcow\Archive\ArchiveModule::fromGlobals()->journal->submitDelivered($released_msg, $sender, array($detail_row['rcpt']), 'quarantine');
|
||||
}
|
||||
$stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id");
|
||||
$stmt->execute(array(
|
||||
':id' => $row['id']
|
||||
@@ -453,6 +459,7 @@ function quarantine($_action, $_data = null) {
|
||||
$mail->addAttachment($msg_tmpf, $row['qid'] . '.eml');
|
||||
$mail->Body = sprintf($lang['quarantine']['release_body']);
|
||||
$mail->send();
|
||||
$released_msg = $mail->getSentMIMEMessage();
|
||||
unlink($msg_tmpf);
|
||||
}
|
||||
catch (phpmailerException $e) {
|
||||
@@ -467,6 +474,7 @@ function quarantine($_action, $_data = null) {
|
||||
}
|
||||
elseif ($release_format == 'raw') {
|
||||
$row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']);
|
||||
$released_msg = $row['msg'];
|
||||
// dot-stuffing per RFC5321 4.5.2: escape leading dots
|
||||
$row['msg'] = preg_replace('~^\.~m', '..', $row['msg']);
|
||||
$postfix_talk = array(
|
||||
@@ -506,6 +514,11 @@ function quarantine($_action, $_data = null) {
|
||||
}
|
||||
fclose($smtp_connection);
|
||||
}
|
||||
// Port 590 bypasses rspamd, so the archive gets released mails here
|
||||
if (isset($released_msg)) {
|
||||
\Mailcow\Archive\ArchiveModule::fromGlobals()->journal->submitDelivered($released_msg, $sender, array($row['rcpt']), 'quarantine');
|
||||
unset($released_msg);
|
||||
}
|
||||
$stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id");
|
||||
$stmt->execute(array(
|
||||
':id' => $id
|
||||
|
||||
@@ -242,6 +242,7 @@ function init_db_schema()
|
||||
"gal" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"relay_unknown_only" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"archive" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||
@@ -1053,7 +1054,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'",
|
||||
"archive" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
@@ -1277,6 +1279,42 @@ function init_db_schema()
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"archive_settings" => array(
|
||||
"cols" => array(
|
||||
"name" => "VARCHAR(64) NOT NULL",
|
||||
"value" => "TEXT NOT NULL",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("name")
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
// Archive target the Postfix maps read (FILTER of the copies on port 591, SASL, TLS policy), one row kept in line with archive_settings
|
||||
"archive_transport" => array(
|
||||
"cols" => array(
|
||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||
"address" => "VARCHAR(255) NOT NULL",
|
||||
"nexthop" => "VARCHAR(255) NOT NULL",
|
||||
"username" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
||||
"password" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
||||
"tls_policy" => "VARCHAR(64) NOT NULL DEFAULT 'encrypt'",
|
||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("id")
|
||||
),
|
||||
"key" => array(
|
||||
"address" => array("address"),
|
||||
"nexthop" => array("nexthop")
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"bcc_maps" => array(
|
||||
"cols" => array(
|
||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||
@@ -2009,6 +2047,7 @@ function init_db_schema()
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.relayhost', \"0\") WHERE JSON_VALUE(`attributes`, '$.relayhost') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_pw_update') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_tfa', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_tfa') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.archive', \"inherit\") WHERE JSON_VALUE(`attributes`, '$.archive') IS NULL;");
|
||||
// admin attributes
|
||||
$pdo->query("UPDATE `admin` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;");
|
||||
$pdo->query("UPDATE `admin` SET `attributes` = JSON_SET(`attributes`, '$.force_tfa', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_tfa') IS NULL;");
|
||||
@@ -2056,6 +2095,7 @@ function init_db_schema()
|
||||
"backupmx" => 0,
|
||||
"relay_all_recipients" => 0,
|
||||
"relay_unknown_only" => 0,
|
||||
"archive" => 0,
|
||||
"dkim_selector" => "dkim",
|
||||
"key_size" => 2048,
|
||||
"max_quota_for_domain" => 10240 * 1048576,
|
||||
@@ -2073,6 +2113,7 @@ function init_db_schema()
|
||||
"rl_value" => "",
|
||||
"force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']),
|
||||
"force_tfa" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_tfa']),
|
||||
"archive" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['archive']),
|
||||
"sogo_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_access']),
|
||||
"active" => 1,
|
||||
"tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']),
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive;
|
||||
|
||||
use Mailcow\Archive\Controller\ArchiveApiController;
|
||||
use Mailcow\Archive\Legacy\LegacyQueue;
|
||||
use Mailcow\Archive\Redis\ArchiveState;
|
||||
use Mailcow\Archive\Repository\LookupRepository;
|
||||
use Mailcow\Archive\Repository\SettingsRepository;
|
||||
use Mailcow\Archive\Service\HeaderBuilder;
|
||||
use Mailcow\Archive\Service\JournalService;
|
||||
use Mailcow\Archive\Service\MailboxListService;
|
||||
use Mailcow\Archive\Service\OwnerResolver;
|
||||
use Mailcow\Archive\Service\SettingsService;
|
||||
use Mailcow\Archive\Service\StatusService;
|
||||
use Mailcow\Archive\Smtp\SmtpClient;
|
||||
use Mailcow\Core\ApiResponse;
|
||||
use Mailcow\Core\Context;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Composition root of the archive module (archive copies by SMTP through Postfix).
|
||||
*
|
||||
* process_get_return(ArchiveModule::fromGlobals()->api->getMailboxes());
|
||||
* ArchiveModule::create($pdo, Context::system(), $redis)->journal->prepare($request); // rspamd dynmap
|
||||
* ArchiveModule::fromGlobals()->journal->submitDelivered($message, $from, $rcpts, 'quarantine');
|
||||
*/
|
||||
class ArchiveModule
|
||||
{
|
||||
/** Request keys whose values never reach a log */
|
||||
private const SECRET_KEYS = ['password', 'signing_secret'];
|
||||
|
||||
public function __construct(
|
||||
public readonly SettingsService $settings,
|
||||
public readonly JournalService $journal,
|
||||
public readonly StatusService $status,
|
||||
public readonly MailboxListService $mailboxes,
|
||||
public readonly ArchiveApiController $api
|
||||
) {
|
||||
}
|
||||
|
||||
/** @param ?object $redis Redis connection; without it nothing is read from or written to Redis */
|
||||
public static function create(PDO $pdo, Context $context, ?object $redis = null): self
|
||||
{
|
||||
$settingsRepo = new SettingsRepository($pdo);
|
||||
$lookup = new LookupRepository($pdo);
|
||||
$state = new ArchiveState($redis);
|
||||
$smtp = new SmtpClient();
|
||||
$settings = new SettingsService($settingsRepo, $lookup, $state, $smtp, $context);
|
||||
$journal = new JournalService($settingsRepo, new OwnerResolver($lookup), new HeaderBuilder(), $smtp, $state, $context);
|
||||
$status = new StatusService($settingsRepo, $state, new LegacyQueue(), $context);
|
||||
$mailboxes = new MailboxListService($lookup, $state, $context);
|
||||
$api = new ArchiveApiController($settings, $status, $journal, $mailboxes, new ApiResponse(self::SECRET_KEYS));
|
||||
return new self($settings, $journal, $status, $mailboxes, $api);
|
||||
}
|
||||
|
||||
/** Legacy entry point for procedural callers: reads $pdo, $redis and $_SESSION; $context overrides the session caller */
|
||||
public static function fromGlobals(?Context $context = null): self
|
||||
{
|
||||
$redis = $GLOBALS['redis'] ?? null;
|
||||
return self::create($GLOBALS['pdo'], $context ?? Context::fromSession($_SESSION ?? []), is_object($redis) ? $redis : null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Controller;
|
||||
|
||||
use Mailcow\Archive\Service\JournalService;
|
||||
use Mailcow\Archive\Service\MailboxListService;
|
||||
use Mailcow\Archive\Service\SettingsService;
|
||||
use Mailcow\Archive\Service\StatusService;
|
||||
use Mailcow\Core\ApiResponse;
|
||||
use Mailcow\Core\UserError;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* HTTP API adapter for json_api.php (routes `archive/*`, `archive`).
|
||||
*
|
||||
* Write methods return what json_api passes to process_*_return() and write their messages via ApiResponse.
|
||||
* Messages are logged as ['archive', action, object, data].
|
||||
*/
|
||||
class ArchiveApiController
|
||||
{
|
||||
public function __construct(private SettingsService $settings, private StatusService $status, private JournalService $journal,
|
||||
private MailboxListService $mailboxes, private ApiResponse $response)
|
||||
{
|
||||
}
|
||||
|
||||
/** GET archive/settings */
|
||||
public function getSettings(): array
|
||||
{
|
||||
return $this->read(fn(): array => $this->settings->get()->toArray());
|
||||
}
|
||||
|
||||
/** GET archive/status */
|
||||
public function getStatus(): array
|
||||
{
|
||||
return $this->read(fn(): array => $this->status->status());
|
||||
}
|
||||
|
||||
/** GET logs/archive[/count] */
|
||||
public function getLog(int $count = 0): array
|
||||
{
|
||||
return $this->read(fn(): array => $this->status->log($count));
|
||||
}
|
||||
|
||||
/** GET archive/mailboxes */
|
||||
public function getMailboxes(): array
|
||||
{
|
||||
return $this->read(fn(): array => $this->mailboxes->archived());
|
||||
}
|
||||
|
||||
/** POST edit/archive: the settings; `action: test` tests the connection, `action: testmail` sends a test copy */
|
||||
public function edit(array $attr): bool
|
||||
{
|
||||
return match ($attr['action'] ?? null) {
|
||||
'test' => $this->handle('edit', 'test', [], fn(array $log): bool => $this->testResult($this->settings->test(), $log)),
|
||||
'testmail' => $this->handle('edit', 'testmail', [],
|
||||
fn(array $log): bool => $this->response->success(['archive_testmail_queued', htmlspecialchars($this->journal->testMail())], $log)),
|
||||
default => $this->handle('edit', 'settings', $attr, function (array $log) use ($attr): bool {
|
||||
$this->settings->save($attr);
|
||||
return $this->response->success('archive_settings_saved', $log);
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/** A missing extension is a warning: the archive works, but may refuse some mails */
|
||||
private function testResult(string|array $msg, array $log): bool
|
||||
{
|
||||
if (is_array($msg)) {
|
||||
$this->response->warning($msg, $log);
|
||||
return true;
|
||||
}
|
||||
return $this->response->success($msg, $log);
|
||||
}
|
||||
|
||||
/** A GET answer, empty when the caller may not read it */
|
||||
private function read(callable $work): array
|
||||
{
|
||||
try {
|
||||
return $work();
|
||||
} catch (UserError) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, string $object, mixed $data, callable $work): bool
|
||||
{
|
||||
$log = ['archive', $action, $object, $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Legacy;
|
||||
|
||||
/**
|
||||
* Bridge to the legacy mailq() (Postfix queue through dockerapi) until it is converted.
|
||||
*/
|
||||
class LegacyQueue
|
||||
{
|
||||
/**
|
||||
* @return array[] queue entries of $sender: queue_id, queue_name, arrival_time, message_size,
|
||||
* recipients ("address (delay reason)")
|
||||
*/
|
||||
public function entries(string $sender): array
|
||||
{
|
||||
if (!function_exists('mailq')) {
|
||||
return [];
|
||||
}
|
||||
$list = json_decode((string)mailq('get'), true);
|
||||
if (!is_array($list)) {
|
||||
return [];
|
||||
}
|
||||
return array_values(array_filter($list, fn(mixed $entry): bool => is_array($entry) && ($entry['sender'] ?? '') === $sender));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Model;
|
||||
|
||||
/**
|
||||
* What the archive learns about one mail besides its content: owners, SMTP envelope, origin and content hash.
|
||||
*/
|
||||
class Journal
|
||||
{
|
||||
public const DIRECTIONS = ['in', 'out', 'internal'];
|
||||
public const SOURCES = ['smtp', 'quarantine', 'massmail', 'test'];
|
||||
|
||||
/**
|
||||
* @param string[] $owners mailbox usernames the mail belongs to
|
||||
* @param string[] $rcpts SMTP envelope recipients as received (incl. BCC)
|
||||
* @param float $received time mailcow accepted the mail (unix time with milliseconds)
|
||||
* @param string $sha256 hash of the content as sent to the archive (CRLF line ends)
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $owners,
|
||||
public readonly array $rcpts,
|
||||
public readonly string $from,
|
||||
public readonly string $qid,
|
||||
public readonly string $direction,
|
||||
public readonly string $source,
|
||||
public readonly float $received,
|
||||
public readonly string $sha256
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Model;
|
||||
|
||||
/**
|
||||
* A mailbox that receives mail (regular kind, active 1 or 2, in an active domain that is no backup MX)
|
||||
* with its archive settings: the mailbox attribute `archive` ('1', '0' or 'inherit') and the domain default.
|
||||
*/
|
||||
class Mailbox
|
||||
{
|
||||
public const INHERIT = 'inherit';
|
||||
|
||||
public function __construct(
|
||||
public readonly string $username,
|
||||
public readonly string $domain,
|
||||
public readonly string $archive,
|
||||
public readonly bool $domainArchive,
|
||||
public readonly string $name = '',
|
||||
public readonly int $active = 1
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromRow(array $row): self
|
||||
{
|
||||
return new self(
|
||||
strtolower((string)$row['username']),
|
||||
strtolower((string)$row['domain']),
|
||||
self::setting($row['archive'] ?? null),
|
||||
(int)($row['domain_archive'] ?? 0) === 1,
|
||||
(string)($row['name'] ?? ''),
|
||||
(int)($row['active'] ?? 1)
|
||||
);
|
||||
}
|
||||
|
||||
/** Mailbox setting: '1', '0' (also as number or boolean), anything else (also a missing attribute) is 'inherit' */
|
||||
public static function setting(mixed $value): string
|
||||
{
|
||||
$value = is_bool($value) ? (int)$value : $value;
|
||||
$value = is_scalar($value) ? (string)$value : '';
|
||||
return in_array($value, ['0', '1'], true) ? $value : self::INHERIT;
|
||||
}
|
||||
|
||||
/** Archiving applies by the mailbox setting, 'inherit' follows the domain (without the global switch) */
|
||||
public static function effective(mixed $archive, mixed $domainArchive): bool
|
||||
{
|
||||
$archive = self::setting($archive);
|
||||
return ($archive === self::INHERIT) ? (int)$domainArchive === 1 : $archive === '1';
|
||||
}
|
||||
|
||||
public function archived(): bool
|
||||
{
|
||||
return self::effective($this->archive, $this->domainArchive);
|
||||
}
|
||||
|
||||
/** Why the mailbox is archived: its own setting or the domain default */
|
||||
public function source(): string
|
||||
{
|
||||
return ($this->archive === '1') ? 'mailbox' : 'domain';
|
||||
}
|
||||
|
||||
/** Entry of GET archive/mailboxes */
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'username' => $this->username,
|
||||
'domain' => $this->domain,
|
||||
'name' => $this->name,
|
||||
'active' => $this->active,
|
||||
'source' => $this->source(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Model;
|
||||
|
||||
/**
|
||||
* The archive target (table `archive_settings`): archive copies of accepted mails go by SMTP to `address`,
|
||||
* through the Postfix queue (table `archive_transport`, service archive_smtp in master.cf).
|
||||
*/
|
||||
class Settings
|
||||
{
|
||||
public const TLS_MODES = ['encrypt', 'verify', 'may'];
|
||||
/** Metadata headers of a copy: archivecow (X-Archive-*, signed), envelope (sender/recipients), none */
|
||||
public const PROFILES = ['archivecow', 'envelope', 'none'];
|
||||
/** Envelope sender of copies; Postfix drops every notice to it (archive_discard), nothing reaches a real sender */
|
||||
public const SENDER = 'archive-bounce@localhost';
|
||||
|
||||
/** rspamd hands accepted mails to the archive */
|
||||
public bool $enabled = false;
|
||||
/** Address the archive receives copies at */
|
||||
public string $address = '';
|
||||
/** SMTP server of the archive; empty: MX of the address domain */
|
||||
public string $host = '';
|
||||
public int $port = 25;
|
||||
public string $tls = 'encrypt';
|
||||
/** SMTP AUTH at the archive, optional */
|
||||
public string $username = '';
|
||||
/** Never part of toArray() */
|
||||
public string $password = '';
|
||||
public string $profile = 'archivecow';
|
||||
/** Key of X-Archive-Signature (profile archivecow), never part of toArray() */
|
||||
public string $signingKey = '';
|
||||
/** Header names of the envelope profile */
|
||||
public string $headerFrom = 'X-Envelope-From';
|
||||
public string $headerRcpt = 'X-Envelope-To';
|
||||
|
||||
/** Copies can be sent: an address, and a signing key for archivecow */
|
||||
public function canDeliver(): bool
|
||||
{
|
||||
return $this->address !== '' && ($this->profile !== 'archivecow' || $this->signingKey !== '');
|
||||
}
|
||||
|
||||
/** Next hop for Postfix: [host]:port, else the domain of the address (MX lookup), with the port when not 25 */
|
||||
public function nexthop(): string
|
||||
{
|
||||
if ($this->host !== '') {
|
||||
$host = filter_var($this->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? 'ipv6:' . $this->host : $this->host;
|
||||
return '[' . $host . ']:' . $this->port;
|
||||
}
|
||||
$domain = substr($this->address, (int)strrpos($this->address, '@') + 1);
|
||||
return ($this->port === 25) ? $domain : $domain . ':' . $this->port;
|
||||
}
|
||||
|
||||
/** API representation (GET archive/settings), without secrets */
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'enabled' => (int)$this->enabled,
|
||||
'address' => $this->address,
|
||||
'host' => $this->host,
|
||||
'port' => $this->port,
|
||||
'tls' => $this->tls,
|
||||
'username' => $this->username,
|
||||
'has_password' => (int)($this->password !== ''),
|
||||
'profile' => $this->profile,
|
||||
'has_signing_secret' => (int)($this->signingKey !== ''),
|
||||
'header_from' => $this->headerFrom,
|
||||
'header_rcpt' => $this->headerRcpt,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Redis;
|
||||
|
||||
use Mailcow\Archive\Model\Settings;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Redis side of the archive:
|
||||
* - ARCHIVE_ENABLED: the global switch, kept here only; rspamd reads it before it asks for owners.
|
||||
* - ARCHIVE_DROPPED / ARCHIVE_DROPPED_LOG: copies that could not be handed to Postfix (rspamd.local.lua, release
|
||||
* hooks), counted and the last 100 listed.
|
||||
* - POSTFIX_MAILLOG: the Postfix log, read for the archive log: deliveries of the archive_smtp service and expired
|
||||
* copies, with the Message-ID of the mail (a copy keeps the Message-ID of the original).
|
||||
*
|
||||
* The connection is a plain object, so the class loads without the redis extension; without a connection
|
||||
* nothing is read or written.
|
||||
*/
|
||||
class ArchiveState
|
||||
{
|
||||
public const ENABLED = 'ARCHIVE_ENABLED';
|
||||
public const DROPPED = 'ARCHIVE_DROPPED';
|
||||
public const DROPPED_LOG = 'ARCHIVE_DROPPED_LOG';
|
||||
private const MAILLOG = 'POSTFIX_MAILLOG';
|
||||
|
||||
public function __construct(private ?object $redis)
|
||||
{
|
||||
}
|
||||
|
||||
public function setEnabled(bool $enabled): void
|
||||
{
|
||||
$this->redis?->set(self::ENABLED, $enabled ? '1' : '0');
|
||||
}
|
||||
|
||||
/** The global switch: on only while ARCHIVE_ENABLED is '1', as rspamd reads it (missing or unreadable is off) */
|
||||
public function enabled(): bool
|
||||
{
|
||||
try {
|
||||
return $this->redis?->get(self::ENABLED) === '1';
|
||||
} catch (RuntimeException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** A mail was not archived: its copy could not be handed to Postfix */
|
||||
public function dropped(string $qid, string $reason, string $messageId = ''): void
|
||||
{
|
||||
if ($this->redis === null) {
|
||||
return;
|
||||
}
|
||||
$this->redis->incr(self::DROPPED);
|
||||
$this->redis->lPush(self::DROPPED_LOG, json_encode(['time' => time(), 'qid' => $qid, 'reason' => $reason, 'message_id' => $messageId]));
|
||||
$this->redis->lTrim(self::DROPPED_LOG, 0, 99);
|
||||
}
|
||||
|
||||
/** @return array{count: int, recent: array[]} count since ever; recent: time, qid, reason, message_id; newest first */
|
||||
public function droppedStats(int $limit = 100): array
|
||||
{
|
||||
if ($this->redis === null) {
|
||||
return ['count' => 0, 'recent' => []];
|
||||
}
|
||||
$recent = [];
|
||||
foreach ((array)$this->redis->lRange(self::DROPPED_LOG, 0, $limit - 1) as $line) {
|
||||
$entry = is_string($line) ? json_decode($line, true) : null;
|
||||
if (is_array($entry)) {
|
||||
$recent[] = ['time' => (int)($entry['time'] ?? 0), 'qid' => (string)($entry['qid'] ?? ''),
|
||||
'reason' => (string)($entry['reason'] ?? ''), 'message_id' => (string)($entry['message_id'] ?? '')];
|
||||
}
|
||||
}
|
||||
return ['count' => (int)$this->redis->get(self::DROPPED), 'recent' => $recent];
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive log, newest first: deliveries of copies (status sent|deferred|softbounce|bounced, text = answer of the
|
||||
* archive), expired copies (expired) and mails not archived (dropped, text = reason).
|
||||
*
|
||||
* @return array[] time, status, qid (of the copy, of the mail for dropped), message_id, text
|
||||
*/
|
||||
public function events(int $limit = 1000): array
|
||||
{
|
||||
if ($this->redis === null) {
|
||||
return [];
|
||||
}
|
||||
$events = [];
|
||||
$messageIds = [];
|
||||
foreach ((array)$this->redis->lRange(self::MAILLOG, 0, -1) as $line) {
|
||||
$entry = is_string($line) ? json_decode($line, true) : null;
|
||||
if (!is_array($entry)) {
|
||||
continue;
|
||||
}
|
||||
$program = (string)($entry['program'] ?? '');
|
||||
$message = (string)($entry['message'] ?? '');
|
||||
$time = (int)($entry['time'] ?? 0);
|
||||
if ($program === 'postfix/cleanup' && preg_match('/^([0-9A-Za-z]+): message-id=(\S*)/', $message, $m)) {
|
||||
$messageIds[$m[1]] = trim($m[2], '<>');
|
||||
}
|
||||
// the archive_discard lines are the dropped notices, not deliveries
|
||||
elseif ($program === 'postfix/archive/smtp' && preg_match('/^([0-9A-Za-z]+): to=<[^>]*>.*\bstatus=(\w+) \((.*)\)$/', $message, $m)) {
|
||||
$events[] = ['time' => $time, 'status' => strtolower($m[2]), 'qid' => $m[1], 'message_id' => '', 'text' => $m[3]];
|
||||
}
|
||||
elseif ($program === 'postfix/qmgr' && preg_match('/^([0-9A-Za-z]+): from=<' . preg_quote(Settings::SENDER, '/') . '>, status=expired/', $message, $m)) {
|
||||
$events[] = ['time' => $time, 'status' => 'expired', 'qid' => $m[1], 'message_id' => '', 'text' => ''];
|
||||
}
|
||||
}
|
||||
foreach ($events as &$event) {
|
||||
$event['message_id'] = $messageIds[$event['qid']] ?? '';
|
||||
}
|
||||
unset($event);
|
||||
foreach ($this->droppedStats()['recent'] as $dropped) {
|
||||
$events[] = ['time' => $dropped['time'], 'status' => 'dropped', 'qid' => $dropped['qid'],
|
||||
'message_id' => $dropped['message_id'], 'text' => $dropped['reason']];
|
||||
}
|
||||
usort($events, fn(array $a, array $b): int => $b['time'] <=> $a['time']);
|
||||
return array_slice($events, 0, $limit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Repository;
|
||||
|
||||
use Mailcow\Archive\Model\Mailbox;
|
||||
use Mailcow\Core\Repository;
|
||||
|
||||
/**
|
||||
* Read-only lookups of the address tables, with the same conditions as the Postfix maps
|
||||
* (data/Dockerfiles/postfix/postfix.sh), and the mailboxes with their archive settings.
|
||||
*/
|
||||
class LookupRepository extends Repository
|
||||
{
|
||||
/** Columns of a mailbox that receives mail; the domain must be active and no backup MX */
|
||||
private const MAILBOX = "SELECT m.`username`, m.`domain`, m.`name`, m.`active`,
|
||||
JSON_VALUE(m.`attributes`, '$.archive') AS `archive`, d.`archive` AS `domain_archive`
|
||||
FROM `mailbox` m
|
||||
JOIN `domain` d ON d.`domain` = m.`domain`
|
||||
WHERE m.`kind` = '' AND (m.`active` = '1' OR m.`active` = '2') AND d.`active` = '1' AND d.`backupmx` = '0'";
|
||||
|
||||
/** recipient_canonical_maps: new destination of an address or '@domain' */
|
||||
public function recipientMap(string $key): ?string
|
||||
{
|
||||
return $this->value("SELECT `new_dest` FROM `recipient_maps` WHERE `old_dest` = :key AND `active` = '1' LIMIT 1", $key);
|
||||
}
|
||||
|
||||
/** Targets of an alias row (aliases, catch-alls '@domain', groups, mailbox self rows) */
|
||||
public function aliasGoto(string $key): ?string
|
||||
{
|
||||
return $this->value("SELECT `goto` FROM `alias` WHERE `address` = :key AND (`active` = '1' OR `active` = '2') LIMIT 1", $key);
|
||||
}
|
||||
|
||||
/** Resources (location, thing, group) are discarded by Postfix */
|
||||
public function isResource(string $username): bool
|
||||
{
|
||||
return $this->value("SELECT `username` FROM `mailbox` WHERE `username` = :key AND `kind` REGEXP 'location|thing|group' LIMIT 1", $username) !== null;
|
||||
}
|
||||
|
||||
public function spamAliasGoto(string $key): ?string
|
||||
{
|
||||
return $this->value("SELECT `goto` FROM `spamalias` WHERE `address` = :key
|
||||
AND (`validity` >= UNIX_TIMESTAMP() OR `permanent` != 0) LIMIT 1", $key);
|
||||
}
|
||||
|
||||
/** Mailbox local@target behind local@domain when domain is an alias domain */
|
||||
public function aliasDomainMailbox(string $local, string $domain): ?string
|
||||
{
|
||||
$username = $this->query("SELECT `mailbox`.`username` FROM `mailbox`, `alias_domain`
|
||||
WHERE `alias_domain`.`alias_domain` = :domain AND `mailbox`.`username` = CONCAT(:local, '@', `alias_domain`.`target_domain`)
|
||||
AND (`mailbox`.`active` = '1' OR `mailbox`.`active` = '2') AND `alias_domain`.`active` = '1' LIMIT 1",
|
||||
['domain' => $domain, 'local' => $local])->fetchColumn();
|
||||
return is_string($username) ? $username : null;
|
||||
}
|
||||
|
||||
/** BCC destination of an address or '@domain' as rspamd's bcc.php finds it; $type 'rcpt' or 'sender' */
|
||||
public function bccDestination(string $key, string $type): ?string
|
||||
{
|
||||
$dest = $this->query("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = :type AND `local_dest` = :key AND `active` = '1' LIMIT 1",
|
||||
['type' => $type, 'key' => $key])->fetchColumn();
|
||||
return is_string($dest) ? $dest : null;
|
||||
}
|
||||
|
||||
/** A domain or alias domain of this mailcow */
|
||||
public function isMailcowDomain(string $domain): bool
|
||||
{
|
||||
return $this->query("SELECT `domain` FROM `domain` WHERE `domain` = :domain
|
||||
UNION SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain` = :alias LIMIT 1",
|
||||
['domain' => $domain, 'alias' => $domain])->fetchColumn() !== false;
|
||||
}
|
||||
|
||||
/** The mailbox when it receives mail */
|
||||
public function mailbox(string $username): ?Mailbox
|
||||
{
|
||||
$row = $this->query(self::MAILBOX . " AND m.`username` = :username", ['username' => $username])->fetch();
|
||||
return is_array($row) ? Mailbox::fromRow($row) : null;
|
||||
}
|
||||
|
||||
/** @return Mailbox[] mailboxes archived by their setting or their domain (without the global switch) */
|
||||
public function archivedMailboxes(): array
|
||||
{
|
||||
$rows = $this->query(self::MAILBOX . " AND (JSON_VALUE(m.`attributes`, '$.archive') = '1'
|
||||
OR (COALESCE(JSON_VALUE(m.`attributes`, '$.archive'), '') NOT IN ('0', '1') AND d.`archive` = '1'))
|
||||
ORDER BY m.`username`")->fetchAll();
|
||||
return array_map(fn(array $row): Mailbox => Mailbox::fromRow($row), $rows);
|
||||
}
|
||||
|
||||
private function value(string $sql, string $key): ?string
|
||||
{
|
||||
$value = $this->query($sql, ['key' => $key])->fetchColumn();
|
||||
return is_string($value) ? $value : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Repository;
|
||||
|
||||
use Mailcow\Archive\Model\Settings;
|
||||
use Mailcow\Core\Repository;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* SQL for the archive target: the settings (table `archive_settings`, name => value) and the row the Postfix maps
|
||||
* read (table `archive_transport`: transport, SASL, TLS policy of the archive_smtp service).
|
||||
*/
|
||||
class SettingsRepository extends Repository
|
||||
{
|
||||
/** Stored settings; keys that are not stored yet keep the defaults of the model */
|
||||
public function load(): Settings
|
||||
{
|
||||
$rows = $this->query("SELECT `name`, `value` FROM `archive_settings`")->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$settings = new Settings();
|
||||
$settings->address = (string)($rows['address'] ?? '');
|
||||
$settings->host = (string)($rows['host'] ?? '');
|
||||
$settings->port = (int)($rows['port'] ?? $settings->port);
|
||||
$settings->tls = (string)($rows['tls'] ?? $settings->tls);
|
||||
$settings->username = (string)($rows['username'] ?? '');
|
||||
$settings->password = (string)($rows['password'] ?? '');
|
||||
$settings->profile = (string)($rows['profile'] ?? $settings->profile);
|
||||
$settings->signingKey = (string)($rows['signing_secret'] ?? '');
|
||||
$settings->headerFrom = (string)($rows['header_from'] ?? $settings->headerFrom);
|
||||
$settings->headerRcpt = (string)($rows['header_rcpt'] ?? $settings->headerRcpt);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the settings and the transport row. The row stays while an address is set, also with archiving off,
|
||||
* so copies still queued in Postfix are delivered.
|
||||
*/
|
||||
public function save(Settings $settings): void
|
||||
{
|
||||
$values = [
|
||||
'address' => $settings->address,
|
||||
'host' => $settings->host,
|
||||
'port' => (string)$settings->port,
|
||||
'tls' => $settings->tls,
|
||||
'username' => $settings->username,
|
||||
'password' => $settings->password,
|
||||
'profile' => $settings->profile,
|
||||
'signing_secret' => $settings->signingKey,
|
||||
'header_from' => $settings->headerFrom,
|
||||
'header_rcpt' => $settings->headerRcpt,
|
||||
];
|
||||
$sql = "INSERT INTO `archive_settings` (`name`, `value`) VALUES (:name, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)";
|
||||
$this->transaction(function () use ($sql, $values, $settings): void {
|
||||
foreach ($values as $name => $value) {
|
||||
$this->query($sql, ['name' => $name, 'value' => $value]);
|
||||
}
|
||||
$this->query("DELETE FROM `archive_transport`");
|
||||
if ($settings->address !== '') {
|
||||
$this->insertRow('archive_transport', [
|
||||
'address' => $settings->address,
|
||||
'nexthop' => $settings->nexthop(),
|
||||
'username' => $settings->username,
|
||||
'password' => $settings->password,
|
||||
'tls_policy' => $settings->tls,
|
||||
'active' => 1,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Service;
|
||||
|
||||
use DateTime;
|
||||
use Mailcow\Archive\Model\Journal;
|
||||
use Mailcow\Archive\Model\Settings;
|
||||
|
||||
/**
|
||||
* Metadata header block in front of an archive copy, by profile:
|
||||
*
|
||||
* - archivecow: X-Archive-Owner, -Rcpt, -From, -Queue-ID, -Received, -Direction, -Source, -SHA256 and as the last
|
||||
* line X-Archive-Signature: v=1; a=hmac-sha256; s=<hex>. The signature is HMAC-SHA256 with the signing key over
|
||||
* the lines above it, each as "<lower case name>:<value without any whitespace>", joined by "\n".
|
||||
* The block ends with the signature line, everything after it is the mail as accepted (CRLF line ends).
|
||||
* - envelope: SMTP sender and recipients under configurable names (default X-Envelope-From / X-Envelope-To).
|
||||
* - none: no headers.
|
||||
*
|
||||
* Empty values are left out. Lists are folded after commas (CRLF + space).
|
||||
*/
|
||||
class HeaderBuilder
|
||||
{
|
||||
/** Fold a list before a line would get longer */
|
||||
private const FOLD_AT = 78;
|
||||
|
||||
public function build(Settings $settings, Journal $journal): string
|
||||
{
|
||||
return match ($settings->profile) {
|
||||
'archivecow' => $this->archivecow($settings, $journal),
|
||||
'envelope' => $this->envelope($settings, $journal),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/** One line of the signed text */
|
||||
public static function canonical(string $name, string $value): string
|
||||
{
|
||||
return strtolower($name) . ':' . preg_replace('/\s+/', '', $value);
|
||||
}
|
||||
|
||||
private function archivecow(Settings $settings, Journal $journal): string
|
||||
{
|
||||
$values = [
|
||||
'X-Archive-Owner' => $journal->owners,
|
||||
'X-Archive-Rcpt' => $journal->rcpts,
|
||||
'X-Archive-From' => $journal->from,
|
||||
'X-Archive-Queue-ID' => $journal->qid,
|
||||
'X-Archive-Received' => self::time($journal->received),
|
||||
'X-Archive-Direction' => $journal->direction,
|
||||
'X-Archive-Source' => $journal->source,
|
||||
'X-Archive-SHA256' => $journal->sha256,
|
||||
];
|
||||
$lines = [];
|
||||
$signed = [];
|
||||
foreach ($values as $name => $value) {
|
||||
$value = is_array($value) ? self::cleanList($value) : self::clean($value);
|
||||
if ($value === '' || $value === []) {
|
||||
continue;
|
||||
}
|
||||
$lines[] = self::line($name, $value);
|
||||
$signed[] = self::canonical($name, is_array($value) ? implode(',', $value) : $value);
|
||||
}
|
||||
$lines[] = 'X-Archive-Signature: v=1; a=hmac-sha256; s=' . hash_hmac('sha256', implode("\n", $signed), $settings->signingKey);
|
||||
return implode("\r\n", $lines) . "\r\n";
|
||||
}
|
||||
|
||||
private function envelope(Settings $settings, Journal $journal): string
|
||||
{
|
||||
$from = self::clean($journal->from);
|
||||
$lines = [self::line($settings->headerFrom, ($from === '') ? '<>' : $from)];
|
||||
$rcpts = self::cleanList($journal->rcpts);
|
||||
if ($rcpts !== []) {
|
||||
$lines[] = self::line($settings->headerRcpt, $rcpts);
|
||||
}
|
||||
return implode("\r\n", $lines) . "\r\n";
|
||||
}
|
||||
|
||||
/** Header line; a list is folded after commas */
|
||||
private static function line(string $name, string|array $value): string
|
||||
{
|
||||
if (is_string($value)) {
|
||||
return $name . ': ' . $value;
|
||||
}
|
||||
$out = $name . ': ' . array_shift($value);
|
||||
$length = strlen($out);
|
||||
foreach ($value as $item) {
|
||||
if ($length + strlen($item) + 1 > self::FOLD_AT) {
|
||||
$out .= ",\r\n " . $item;
|
||||
$length = strlen($item) + 1;
|
||||
} else {
|
||||
$out .= ',' . $item;
|
||||
$length += strlen($item) + 1;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Header value without control characters (no header injection) */
|
||||
private static function clean(string $value): string
|
||||
{
|
||||
return trim((string)preg_replace('/[\x00-\x1f\x7f]+/', ' ', $value));
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
private static function cleanList(array $values): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($values as $value) {
|
||||
$value = is_string($value) ? str_replace(',', '', self::clean($value)) : '';
|
||||
if ($value !== '') {
|
||||
$list[] = $value;
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/** RFC 3339 in UTC with milliseconds */
|
||||
private static function time(float $time): string
|
||||
{
|
||||
$date = DateTime::createFromFormat('U.u', sprintf('%.3F', $time));
|
||||
return $date ? $date->format('Y-m-d\TH:i:s.v\Z') : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Service;
|
||||
|
||||
use Mailcow\Archive\Model\Journal;
|
||||
use Mailcow\Archive\Model\Settings;
|
||||
use Mailcow\Archive\Redis\ArchiveState;
|
||||
use Mailcow\Archive\Repository\SettingsRepository;
|
||||
use Mailcow\Archive\Smtp\SmtpClient;
|
||||
use Mailcow\Archive\Smtp\SmtpException;
|
||||
use Mailcow\Core\Context;
|
||||
use Mailcow\Core\UserError;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Archive copies: a copy of every accepted mail of an archived mailbox goes by SMTP to the archive, through the
|
||||
* Postfix queue. rspamd sends the copies of mails it scanned (rspamd.local.lua) and asks prepare() whether to send
|
||||
* one and for its header block; mails that bypass rspamd (quarantine release, mass mail) are sent here.
|
||||
*
|
||||
* Copies have the envelope sender Settings::SENDER, Postfix drops every notice to it: an archive copy never causes
|
||||
* a bounce or delay warning to a real sender.
|
||||
*/
|
||||
class JournalService
|
||||
{
|
||||
/** Postfix service that takes copies (no milter, mynetworks only) */
|
||||
private const SUBMIT_PORT = 591;
|
||||
|
||||
public function __construct(
|
||||
private SettingsRepository $settings,
|
||||
private OwnerResolver $resolver,
|
||||
private HeaderBuilder $headers,
|
||||
private SmtpClient $smtp,
|
||||
private ArchiveState $state,
|
||||
private Context $context,
|
||||
private string $postfixHost = 'postfix'
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive decision for a mail rspamd accepted (dynmap archive.php), no copy while archiving is off or no
|
||||
* archived mailbox is involved.
|
||||
*
|
||||
* @param array $request from, rcpt[], user, bcc, qid, received, sha256 (of the content rspamd sends)
|
||||
* @return array{archive: bool, rcpt?: string, sender?: string, headers?: string}
|
||||
*/
|
||||
public function prepare(array $request): array
|
||||
{
|
||||
$settings = $this->settings->load();
|
||||
if (!$this->state->enabled() || !$settings->canDeliver()) {
|
||||
return ['archive' => false];
|
||||
}
|
||||
$from = self::text($request['from'] ?? '');
|
||||
$rcpts = is_array($request['rcpt'] ?? null) ? array_values(array_filter($request['rcpt'], 'is_string')) : [];
|
||||
$resolved = $this->resolver->resolve($from, $rcpts, self::text($request['user'] ?? ''), ($request['bcc'] ?? false) === true);
|
||||
if ($resolved['owners'] === []) {
|
||||
return ['archive' => false];
|
||||
}
|
||||
$sha256 = self::text($request['sha256'] ?? '');
|
||||
$journal = new Journal($resolved['owners'], $rcpts, $from, mb_strcut(self::text($request['qid'] ?? ''), 0, 64),
|
||||
$resolved['direction'], 'smtp', is_numeric($request['received'] ?? null) ? (float)$request['received'] : microtime(true),
|
||||
preg_match('/^[0-9a-f]{64}$/', $sha256) ? $sha256 : '');
|
||||
return ['archive' => true, 'rcpt' => $settings->address, 'sender' => Settings::SENDER, 'headers' => $this->headers->build($settings, $journal)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the copy of a mail that reached its mailbox without rspamd (quarantine release, mass mail) for its
|
||||
* archived recipients. Errors are logged and counted, never thrown: the mail is delivered already.
|
||||
*
|
||||
* @param string[] $rcpts
|
||||
*/
|
||||
public function submitDelivered(string $message, string $from, array $rcpts, string $source): bool
|
||||
{
|
||||
try {
|
||||
$settings = $this->settings->load();
|
||||
if (!$this->state->enabled() || !$settings->canDeliver()) {
|
||||
return false;
|
||||
}
|
||||
$resolved = $this->resolver->resolve($from, $rcpts, '', false);
|
||||
if ($resolved['owners'] === []) {
|
||||
return false;
|
||||
}
|
||||
$this->send($settings, $message, $resolved['owners'], array_values($rcpts), $from, 'in', $source);
|
||||
return true;
|
||||
} catch (Throwable $e) {
|
||||
error_log('ARCHIVE: cannot send the archive copy (' . $source . '): ' . $e->getMessage());
|
||||
$this->state->dropped('', $source . ': ' . $e->getMessage(), self::messageId($message));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a test copy through Postfix to the archive and returns the Postfix queue id.
|
||||
*
|
||||
* @throws UserError
|
||||
*/
|
||||
public function testMail(): string
|
||||
{
|
||||
$this->context->requireAdmin();
|
||||
$settings = $this->settings->load();
|
||||
if (!$settings->canDeliver()) {
|
||||
throw new UserError('archive_incomplete');
|
||||
}
|
||||
$message = implode("\r\n", [
|
||||
'From: mailcow <' . Settings::SENDER . '>',
|
||||
'To: <' . $settings->address . '>',
|
||||
'Subject: mailcow archive test',
|
||||
'Date: ' . date(DATE_RFC2822),
|
||||
'Message-ID: <' . bin2hex(random_bytes(12)) . '@archive-test.mailcow>',
|
||||
'MIME-Version: 1.0',
|
||||
'Content-Type: text/plain; charset=utf-8',
|
||||
'',
|
||||
'Test copy from mailcow (System - Configuration - Archive).',
|
||||
'',
|
||||
]);
|
||||
try {
|
||||
return $this->send($settings, $message, [], [$settings->address], Settings::SENDER, 'in', 'test');
|
||||
} catch (SmtpException $e) {
|
||||
throw new UserError('archive_testmail_failed', [htmlspecialchars($e->describe())]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws SmtpException */
|
||||
private function send(Settings $settings, string $message, array $owners, array $rcpts, string $from, string $direction, string $source): string
|
||||
{
|
||||
$message = SmtpClient::crlf($message);
|
||||
$journal = new Journal($owners, $rcpts, $from, '', $direction, $source, microtime(true), hash('sha256', $message));
|
||||
return $this->smtp->submit($this->postfixHost, self::SUBMIT_PORT, Settings::SENDER, $settings->address,
|
||||
$this->headers->build($settings, $journal) . $message);
|
||||
}
|
||||
|
||||
/** Message-ID of a mail, for the archive log */
|
||||
private static function messageId(string $message): string
|
||||
{
|
||||
$headers = explode("\n\n", str_replace("\r\n", "\n", $message), 2)[0];
|
||||
return preg_match('/^Message-ID:\s*<?([^>\s]+)>?/mi', $headers, $m) ? $m[1] : '';
|
||||
}
|
||||
|
||||
private static function text(mixed $value): string
|
||||
{
|
||||
return is_scalar($value) ? trim((string)preg_replace('/[\x00-\x1f\x7f]+/', ' ', (string)$value)) : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Service;
|
||||
|
||||
use Mailcow\Archive\Model\Mailbox;
|
||||
use Mailcow\Archive\Redis\ArchiveState;
|
||||
use Mailcow\Archive\Repository\LookupRepository;
|
||||
use Mailcow\Core\AccessDenied;
|
||||
use Mailcow\Core\Context;
|
||||
|
||||
/**
|
||||
* The mailboxes archived now (GET archive/mailboxes), the list archivecow keeps its mailboxes in line with.
|
||||
* Same rule as the owners of a mail: the global switch, then the mailbox setting, 'inherit' follows the domain.
|
||||
*/
|
||||
class MailboxListService
|
||||
{
|
||||
public function __construct(private LookupRepository $lookup, private ArchiveState $state, private Context $context)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Admins, read-only API keys included.
|
||||
*
|
||||
* @return array{enabled: int, mailboxes: array[]}
|
||||
* @throws AccessDenied
|
||||
*/
|
||||
public function archived(): array
|
||||
{
|
||||
$this->context->requireAdmin(false);
|
||||
$enabled = $this->state->enabled();
|
||||
return [
|
||||
'enabled' => (int)$enabled,
|
||||
'mailboxes' => $enabled ? array_map(fn(Mailbox $mailbox): array => $mailbox->toArray(), $this->lookup->archivedMailboxes()) : [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Service;
|
||||
|
||||
use Mailcow\Archive\Model\Mailbox;
|
||||
use Mailcow\Archive\Repository\LookupRepository;
|
||||
|
||||
/**
|
||||
* Finds the mailboxes a mail belongs to in the archive, following the way Postfix delivers it.
|
||||
*
|
||||
* Incoming: each envelope recipient goes through recipient_maps (one rewrite) and the virtual alias expansion:
|
||||
* alias rows (incl. catch-alls and groups), resources (discarded), spam aliases and mailboxes of alias domains,
|
||||
* each tried as u+ext@d, u@d and @d, recursively. The local mailboxes at the end receive the mail. BCC map
|
||||
* destinations count as recipients when rspamd sends the BCC copy (it bypasses rspamd).
|
||||
*
|
||||
* Outgoing: the SASL user and the mailbox the user sent as (its address, with +tag or in an alias domain);
|
||||
* Postfix already checked that the login may use that sender. Without a login the sender never counts, so a
|
||||
* forged local sender cannot make a mailbox an owner.
|
||||
*
|
||||
* Only mailboxes with archiving become owners.
|
||||
*/
|
||||
class OwnerResolver
|
||||
{
|
||||
/** virtual_alias_recursion_limit and virtual_alias_expansion_limit of Postfix */
|
||||
private const MAX_DEPTH = 1000;
|
||||
private const MAX_ADDRESSES = 1000;
|
||||
|
||||
public function __construct(private LookupRepository $lookup)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $rcpts SMTP envelope recipients
|
||||
* @param bool $bcc rspamd sent the BCC copies of this mail
|
||||
* @return array{owners: string[], direction: string} sorted usernames; direction in, out or internal
|
||||
*/
|
||||
public function resolve(string $from, array $rcpts, string $user, bool $bcc): array
|
||||
{
|
||||
$from = self::address($from);
|
||||
$incoming = [];
|
||||
foreach ($rcpts as $rcpt) {
|
||||
$rcpt = self::address($rcpt);
|
||||
if ($rcpt !== '') {
|
||||
$this->deliver($rcpt, $incoming);
|
||||
}
|
||||
}
|
||||
if ($bcc) {
|
||||
foreach ($this->bccDestinations($from, $rcpts) as $destination) {
|
||||
$this->deliver($destination, $incoming);
|
||||
}
|
||||
}
|
||||
$outgoing = $this->senders($from, self::address($user));
|
||||
$owners = [];
|
||||
foreach (array_merge(array_values($incoming), array_values($outgoing)) as $mailbox) {
|
||||
if ($mailbox->archived()) {
|
||||
$owners[$mailbox->username] = true;
|
||||
}
|
||||
}
|
||||
$owners = array_keys($owners);
|
||||
sort($owners);
|
||||
$direction = empty($outgoing) ? 'in' : (empty($incoming) ? 'out' : 'internal');
|
||||
return ['owners' => $owners, 'direction' => $direction];
|
||||
}
|
||||
|
||||
/** Adds the local mailboxes a recipient ends in to $mailboxes (username => Mailbox) */
|
||||
private function deliver(string $address, array &$mailboxes): void
|
||||
{
|
||||
$seen = [];
|
||||
$this->expand($this->canonical($address), 0, $mailboxes, $seen);
|
||||
}
|
||||
|
||||
/** recipient_maps: first matching key form, one rewrite */
|
||||
private function canonical(string $address): string
|
||||
{
|
||||
foreach (self::keys($address) as $key) {
|
||||
$target = self::target($this->lookup->recipientMap($key));
|
||||
if ($target !== '') {
|
||||
return self::rewrite($address, $key, $target);
|
||||
}
|
||||
}
|
||||
return $address;
|
||||
}
|
||||
|
||||
private function expand(string $address, int $depth, array &$mailboxes, array &$seen): void
|
||||
{
|
||||
if ($depth > self::MAX_DEPTH || count($seen) >= self::MAX_ADDRESSES || isset($seen[$address])) {
|
||||
return;
|
||||
}
|
||||
$seen[$address] = true;
|
||||
$targets = $this->virtual($address);
|
||||
if ($targets === null) {
|
||||
$this->final($address, $mailboxes);
|
||||
return;
|
||||
}
|
||||
foreach ($targets as $target) {
|
||||
// an address that expands to itself is delivered as it is
|
||||
if ($target === $address) {
|
||||
$this->final($address, $mailboxes);
|
||||
} else {
|
||||
$this->expand($target, $depth + 1, $mailboxes, $seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Targets from the first key form a virtual alias map knows, null if none does */
|
||||
private function virtual(string $address): ?array
|
||||
{
|
||||
if (self::split($address)[1] === 'localhost') {
|
||||
// null@, spam@, ham@localhost: local delivery of Postfix, never a mailbox
|
||||
return [];
|
||||
}
|
||||
foreach (self::keys($address) as $key) {
|
||||
$targets = $this->virtualLookup($key);
|
||||
if ($targets !== null) {
|
||||
return array_map(fn(string $target): string => self::rewrite($address, $key, $target), $targets);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** virtual_alias_maps in the order of main.cf: alias, resource, spam alias, alias domain mailbox */
|
||||
private function virtualLookup(string $key): ?array
|
||||
{
|
||||
$domainKey = str_starts_with($key, '@');
|
||||
$targets = self::targets($this->lookup->aliasGoto($key));
|
||||
if ($targets === null && !$domainKey && $this->lookup->isResource($key)) {
|
||||
$targets = ['null@localhost'];
|
||||
}
|
||||
$targets ??= self::targets($this->lookup->spamAliasGoto($key));
|
||||
if ($targets === null && !$domainKey) {
|
||||
[$local, $domain] = self::split($key);
|
||||
$targets = self::targets($this->lookup->aliasDomainMailbox($local, $domain));
|
||||
}
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/** A final address: a mailbox (as u+ext@d or u@d) or anything else, which is not ours */
|
||||
private function final(string $address, array &$mailboxes): void
|
||||
{
|
||||
foreach (array_unique([$address, self::untagged($address)]) as $candidate) {
|
||||
$mailbox = $this->lookup->mailbox($candidate);
|
||||
if ($mailbox !== null) {
|
||||
$mailboxes[$mailbox->username] = $mailbox;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** BCC destinations of rspamd: rcpt maps for every recipient and its domain, sender maps for MAIL FROM and its domain */
|
||||
private function bccDestinations(string $from, array $rcpts): array
|
||||
{
|
||||
$destinations = [];
|
||||
foreach ($rcpts as $rcpt) {
|
||||
$rcpt = self::address($rcpt);
|
||||
if ($rcpt !== '') {
|
||||
$destinations[] = $this->lookup->bccDestination(self::untagged($rcpt), 'rcpt');
|
||||
$destinations[] = $this->lookup->bccDestination('@' . self::split($rcpt)[1], 'rcpt');
|
||||
}
|
||||
}
|
||||
if ($from !== '') {
|
||||
$destinations[] = $this->lookup->bccDestination(self::untagged($from), 'sender');
|
||||
$destinations[] = $this->lookup->bccDestination('@' . self::split($from)[1], 'sender');
|
||||
}
|
||||
return array_values(array_unique(array_filter(array_map(fn(?string $address): string => self::address($address), $destinations))));
|
||||
}
|
||||
|
||||
/** Mailboxes on the sending side: the SASL user and the mailbox the user sent as */
|
||||
private function senders(string $from, string $user): array
|
||||
{
|
||||
$mailbox = ($user !== '') ? $this->lookup->mailbox($user) : null;
|
||||
if ($mailbox === null) {
|
||||
return [];
|
||||
}
|
||||
$senders = [$mailbox->username => $mailbox];
|
||||
$sentAs = ($from !== '') ? $this->sentAs(self::untagged($from)) : null;
|
||||
if ($sentAs !== null) {
|
||||
$senders[$sentAs->username] = $sentAs;
|
||||
}
|
||||
return $senders;
|
||||
}
|
||||
|
||||
/** Mailbox a sender address belongs to: the mailbox itself or the mailbox behind an alias domain address, not aliases */
|
||||
private function sentAs(string $address): ?Mailbox
|
||||
{
|
||||
$mailbox = $this->lookup->mailbox($address);
|
||||
if ($mailbox === null) {
|
||||
[$local, $domain] = self::split($address);
|
||||
$username = $this->lookup->aliasDomainMailbox($local, $domain);
|
||||
$mailbox = ($username !== null) ? $this->lookup->mailbox(strtolower($username)) : null;
|
||||
}
|
||||
return $mailbox;
|
||||
}
|
||||
|
||||
/** Lookup keys in the order of Postfix: u+ext@d, u@d, @d */
|
||||
private static function keys(string $address): array
|
||||
{
|
||||
$keys = [$address, self::untagged($address), '@' . self::split($address)[1]];
|
||||
return array_values(array_unique($keys));
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of a lookup for $address under $key: '@domain' keeps the local part, a match without the extension
|
||||
* passes it on (propagate_unmatched_extensions).
|
||||
*/
|
||||
private static function rewrite(string $address, string $key, string $target): string
|
||||
{
|
||||
[$local] = self::split($address);
|
||||
if (str_starts_with($target, '@')) {
|
||||
return $local . $target;
|
||||
}
|
||||
$extension = self::extension($local);
|
||||
if ($key === $address || $extension === '') {
|
||||
return $target;
|
||||
}
|
||||
[$targetLocal, $targetDomain] = self::split($target);
|
||||
return ($targetDomain === 'localhost' || self::extension($targetLocal) !== '')
|
||||
? $target
|
||||
: $targetLocal . '+' . $extension . '@' . $targetDomain;
|
||||
}
|
||||
|
||||
/** Comma separated lookup result; null when empty (not found) */
|
||||
private static function targets(?string $value): ?array
|
||||
{
|
||||
$targets = array_values(array_filter(array_map(fn(string $target): string => self::target($target), explode(',', (string)$value))));
|
||||
return empty($targets) ? null : $targets;
|
||||
}
|
||||
|
||||
/** Address or '@domain' from a lookup result, lower case; '' if invalid */
|
||||
private static function target(?string $value): string
|
||||
{
|
||||
$value = strtolower(trim((string)$value));
|
||||
return preg_match('/^[^@\s,<>]*@[^@\s,<>]+$/', $value) ? $value : '';
|
||||
}
|
||||
|
||||
/** Lower case address without angle brackets; '' unless local@domain */
|
||||
private static function address(mixed $value): string
|
||||
{
|
||||
$value = is_string($value) ? strtolower(trim($value, " \t\r\n<>")) : '';
|
||||
return preg_match('/^[^@\s,<>]+@[^@\s,<>]+$/', $value) ? $value : '';
|
||||
}
|
||||
|
||||
private static function untagged(string $address): string
|
||||
{
|
||||
[$local, $domain] = self::split($address);
|
||||
$pos = strpos($local, '+');
|
||||
return ($pos === false || $pos === 0) ? $address : substr($local, 0, $pos) . '@' . $domain;
|
||||
}
|
||||
|
||||
/** Address extension after the recipient delimiter "+" */
|
||||
private static function extension(string $local): string
|
||||
{
|
||||
$pos = strpos($local, '+');
|
||||
return ($pos === false || $pos === 0) ? '' : substr($local, $pos + 1);
|
||||
}
|
||||
|
||||
/** [local part, domain] of an address or '@domain' */
|
||||
private static function split(string $address): array
|
||||
{
|
||||
$at = strrpos($address, '@');
|
||||
return ($at === false) ? [$address, ''] : [substr($address, 0, $at), substr($address, $at + 1)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Service;
|
||||
|
||||
use Mailcow\Archive\Model\Settings;
|
||||
use Mailcow\Archive\Redis\ArchiveState;
|
||||
use Mailcow\Archive\Repository\LookupRepository;
|
||||
use Mailcow\Archive\Repository\SettingsRepository;
|
||||
use Mailcow\Archive\Smtp\SmtpClient;
|
||||
use Mailcow\Archive\Smtp\SmtpException;
|
||||
use Mailcow\Core\AccessDenied;
|
||||
use Mailcow\Core\Context;
|
||||
use Mailcow\Core\Input;
|
||||
use Mailcow\Core\UserError;
|
||||
|
||||
/**
|
||||
* Admin use cases of the archive target. Read-only API keys may read the settings (never password or key).
|
||||
*/
|
||||
class SettingsService
|
||||
{
|
||||
private const MAX_SECRET_LENGTH = 255;
|
||||
/** Extensions the archive should announce: Postfix passes 8-bit mails unchanged (archive_smtp in master.cf) */
|
||||
private const WANTED_EXTENSIONS = ['8BITMIME'];
|
||||
|
||||
public function __construct(private SettingsRepository $repo, private LookupRepository $lookup, private ArchiveState $state,
|
||||
private SmtpClient $smtp, private Context $context)
|
||||
{
|
||||
}
|
||||
|
||||
/** @throws AccessDenied */
|
||||
public function get(): Settings
|
||||
{
|
||||
$this->context->requireAdmin(false);
|
||||
$settings = $this->repo->load();
|
||||
$settings->enabled = $this->state->enabled();
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/** Global switch for internal callers (page templates, mailbox details), no permission check */
|
||||
public function enabled(): bool
|
||||
{
|
||||
return $this->state->enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the settings: attributes missing in $input keep their value, an empty password or signing key keeps
|
||||
* the stored one, `password_remove` deletes the password. Switching on needs an address (and a signing key for archivecow).
|
||||
*
|
||||
* @throws UserError
|
||||
*/
|
||||
public function save(array $input): Settings
|
||||
{
|
||||
$this->context->requireAdmin();
|
||||
$settings = $this->repo->load();
|
||||
$settings->enabled = $this->state->enabled();
|
||||
if (array_key_exists('address', $input)) {
|
||||
$address = strtolower((string)Input::text($input['address']));
|
||||
if (!is_scalar($input['address']) || ($address !== '' && !filter_var($address, FILTER_VALIDATE_EMAIL))) {
|
||||
throw new UserError('archive_address_invalid');
|
||||
}
|
||||
// Postfix would treat an own address as local (aliases, unknown users) before the copy reaches the archive
|
||||
if ($address !== '' && $this->lookup->isMailcowDomain(substr($address, strrpos($address, '@') + 1))) {
|
||||
throw new UserError('archive_address_local');
|
||||
}
|
||||
$settings->address = $address;
|
||||
}
|
||||
if (array_key_exists('host', $input)) {
|
||||
$host = strtolower(trim((string)Input::text($input['host']), " []"));
|
||||
if (!is_scalar($input['host']) || ($host !== '' && !filter_var($host, FILTER_VALIDATE_IP)
|
||||
&& !filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME))) {
|
||||
throw new UserError('archive_host_invalid');
|
||||
}
|
||||
$settings->host = $host;
|
||||
}
|
||||
if (array_key_exists('port', $input)) {
|
||||
$settings->port = Input::intInRange($input['port'], 1, 65535) ?? throw new UserError('archive_port_invalid');
|
||||
}
|
||||
if (array_key_exists('tls', $input)) {
|
||||
$settings->tls = in_array($input['tls'], Settings::TLS_MODES, true) ? $input['tls'] : throw new UserError('archive_tls_invalid');
|
||||
}
|
||||
if (array_key_exists('username', $input)) {
|
||||
$username = Input::text($input['username']);
|
||||
if ($username === null || strlen($username) > self::MAX_SECRET_LENGTH || preg_match('/[\s:]/', $username)) {
|
||||
throw new UserError('archive_username_invalid');
|
||||
}
|
||||
$settings->username = $username;
|
||||
}
|
||||
if (array_key_exists('password', $input)) {
|
||||
$settings->password = self::secret($input['password'], $settings->password, 'archive_password_invalid');
|
||||
}
|
||||
if (self::flag($input['password_remove'] ?? '0')) {
|
||||
$settings->password = '';
|
||||
}
|
||||
if (array_key_exists('profile', $input)) {
|
||||
$settings->profile = in_array($input['profile'], Settings::PROFILES, true) ? $input['profile'] : throw new UserError('archive_profile_invalid');
|
||||
}
|
||||
if (array_key_exists('signing_secret', $input)) {
|
||||
$settings->signingKey = self::secret($input['signing_secret'], $settings->signingKey, 'archive_signing_secret_invalid');
|
||||
}
|
||||
foreach (['header_from' => 'headerFrom', 'header_rcpt' => 'headerRcpt'] as $key => $property) {
|
||||
if (array_key_exists($key, $input)) {
|
||||
$name = Input::text($input[$key]);
|
||||
if ($name === null || !preg_match('/^[A-Za-z][A-Za-z0-9-]{0,63}$/', $name)) {
|
||||
throw new UserError('archive_header_invalid');
|
||||
}
|
||||
$settings->$property = $name;
|
||||
}
|
||||
}
|
||||
if (array_key_exists('enabled', $input)) {
|
||||
$settings->enabled = self::flag($input['enabled']);
|
||||
}
|
||||
if ($settings->enabled && !$settings->canDeliver()) {
|
||||
throw new UserError('archive_incomplete');
|
||||
}
|
||||
$this->repo->save($settings);
|
||||
$this->state->setEnabled($settings->enabled);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the stored target: connection, TLS, AUTH and whether the archive accepts the address (no mail is sent).
|
||||
* Returns the success message, with the missing extension when the archive lacks 8BITMIME.
|
||||
*
|
||||
* @throws UserError
|
||||
*/
|
||||
public function test(): string|array
|
||||
{
|
||||
$this->context->requireAdmin();
|
||||
$settings = $this->repo->load();
|
||||
if ($settings->address === '') {
|
||||
throw new UserError('archive_incomplete');
|
||||
}
|
||||
$host = ($settings->host !== '') ? $settings->host : $this->smtp->mailHost(substr($settings->address, strrpos($settings->address, '@') + 1));
|
||||
try {
|
||||
$extensions = $this->smtp->probe($host, $settings->port, $settings->tls, $settings->username, $settings->password,
|
||||
Settings::SENDER, $settings->address);
|
||||
} catch (SmtpException $e) {
|
||||
throw new UserError('archive_test_failed', [htmlspecialchars($host . ':' . $settings->port . ' ' . $e->describe())]);
|
||||
}
|
||||
$missing = array_values(array_diff(self::WANTED_EXTENSIONS, $extensions));
|
||||
return empty($missing) ? 'archive_test_ok' : ['archive_test_ok_missing', implode(', ', $missing)];
|
||||
}
|
||||
|
||||
/** A new secret, or the stored one when $value is empty */
|
||||
private static function secret(mixed $value, string $current, string $error): string
|
||||
{
|
||||
$secret = Input::text($value);
|
||||
if ($secret === null || strlen($secret) > self::MAX_SECRET_LENGTH || preg_match('/[^\x21-\x7e]/', $secret)) {
|
||||
throw new UserError($error);
|
||||
}
|
||||
return ($secret === '') ? $current : $secret;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Service;
|
||||
|
||||
use Mailcow\Archive\Legacy\LegacyQueue;
|
||||
use Mailcow\Archive\Model\Settings;
|
||||
use Mailcow\Archive\Redis\ArchiveState;
|
||||
use Mailcow\Archive\Repository\SettingsRepository;
|
||||
use Mailcow\Core\AccessDenied;
|
||||
use Mailcow\Core\Context;
|
||||
|
||||
/**
|
||||
* State of the archive for the admin: the summary of the archive tab (copies waiting in the Postfix queue with the
|
||||
* reason Postfix gives, last delivery, mails not archived) and the archive log of the dashboard.
|
||||
*/
|
||||
class StatusService
|
||||
{
|
||||
/** Mails not archived count as a problem for a day */
|
||||
private const DROPPED_WINDOW = 86400;
|
||||
|
||||
public function __construct(private SettingsRepository $settings, private ArchiveState $state, private LegacyQueue $queue,
|
||||
private Context $context)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* GET archive/status (admins, read-only keys included).
|
||||
*
|
||||
* @throws AccessDenied
|
||||
*/
|
||||
public function status(): array
|
||||
{
|
||||
$this->context->requireAdmin(false);
|
||||
$settings = $this->settings->load();
|
||||
$copies = $this->queue->entries(Settings::SENDER);
|
||||
$reasons = [];
|
||||
$oldest = null;
|
||||
$bytes = 0;
|
||||
foreach ($copies as $copy) {
|
||||
$arrival = (int)($copy['arrival_time'] ?? 0);
|
||||
$oldest = ($oldest === null) ? $arrival : min($oldest, $arrival);
|
||||
$bytes += (int)($copy['message_size'] ?? 0);
|
||||
foreach ((array)($copy['recipients'] ?? []) as $rcpt) {
|
||||
if (is_string($rcpt) && preg_match('/^\S+ \((.+)\)$/', $rcpt, $m)) {
|
||||
$reasons[$m[1]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$dropped = $this->state->droppedStats();
|
||||
$recent = array_values(array_filter($dropped['recent'], fn(array $d): bool => $d['time'] >= time() - self::DROPPED_WINDOW));
|
||||
$lastDelivery = null;
|
||||
foreach ($this->state->events() as $event) {
|
||||
if ($event['status'] === 'sent') {
|
||||
$lastDelivery = $event['time'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [
|
||||
'enabled' => (int)$this->state->enabled(),
|
||||
'configured' => (int)$settings->canDeliver(),
|
||||
'queued' => count($copies),
|
||||
'queued_bytes' => $bytes,
|
||||
'oldest_queued' => $oldest,
|
||||
'delay_reasons' => array_slice(array_keys($reasons), 0, 5),
|
||||
'last_delivery' => $lastDelivery,
|
||||
'dropped' => $dropped['count'],
|
||||
'dropped_24h' => count($recent),
|
||||
'last_dropped' => $recent ? ['time' => $recent[0]['time'], 'reason' => $recent[0]['reason']] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* GET logs/archive: deliveries, expired copies and mails not archived, newest first (admins, read-only keys included);
|
||||
* $count limits the entries, 0 keeps the default.
|
||||
*
|
||||
* @throws AccessDenied
|
||||
*/
|
||||
public function log(int $count = 0): array
|
||||
{
|
||||
$this->context->requireAdmin(false);
|
||||
return ($count > 0) ? $this->state->events($count) : $this->state->events();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Smtp;
|
||||
|
||||
/**
|
||||
* Small SMTP client: hands archive copies to Postfix (port 591: no milter, mynetworks only) and tests the
|
||||
* connection to the archive server (EHLO, STARTTLS, AUTH, MAIL/RCPT, no mail is sent).
|
||||
*
|
||||
* The socket I/O sits in protected methods, so tests can replace it.
|
||||
*/
|
||||
class SmtpClient
|
||||
{
|
||||
private const TIMEOUT = 20;
|
||||
|
||||
/** @var resource|null */
|
||||
private $socket = null;
|
||||
|
||||
/**
|
||||
* Sends one mail and returns the queue id Postfix reports. $data must end with CRLF; it is received as it is.
|
||||
*
|
||||
* @throws SmtpException
|
||||
*/
|
||||
public function submit(string $host, int $port, string $from, string $rcpt, string $data): string
|
||||
{
|
||||
$this->open($host, $port);
|
||||
try {
|
||||
$this->expect('connect', 220);
|
||||
$this->ehlo();
|
||||
$this->command('sender', 'MAIL FROM:<' . $from . '>', 250);
|
||||
$this->command('recipient', 'RCPT TO:<' . $rcpt . '>', 250);
|
||||
$this->command('data', 'DATA', 354);
|
||||
$this->write(self::dotStuff(self::crlf($data)) . ".\r\n");
|
||||
$reply = $this->expect('data', 250);
|
||||
$this->write("QUIT\r\n");
|
||||
return preg_match('/queued as ([0-9A-Za-z]+)/', $reply, $m) ? $m[1] : '';
|
||||
} finally {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection test against the archive server: STARTTLS (unless $tls is 'may' and it is not offered), AUTH when
|
||||
* a username is set, then MAIL and RCPT for the archive address, then RSET.
|
||||
*
|
||||
* @return string[] extensions the server announced (after STARTTLS)
|
||||
* @throws SmtpException
|
||||
*/
|
||||
public function probe(string $host, int $port, string $tls, string $username, string $password, string $from, string $rcpt): array
|
||||
{
|
||||
$this->open($host, $port);
|
||||
try {
|
||||
$this->expect('connect', 220);
|
||||
$extensions = $this->ehlo();
|
||||
if ($tls !== 'may' || in_array('STARTTLS', $extensions, true)) {
|
||||
if (!in_array('STARTTLS', $extensions, true)) {
|
||||
throw new SmtpException('tls', 'STARTTLS not offered');
|
||||
}
|
||||
$this->command('tls', 'STARTTLS', 220);
|
||||
$this->startTls($host, $tls === 'verify');
|
||||
$extensions = $this->ehlo();
|
||||
}
|
||||
if ($username !== '') {
|
||||
$this->command('auth', 'AUTH PLAIN ' . base64_encode("\0" . $username . "\0" . $password), 235);
|
||||
}
|
||||
$this->command('sender', 'MAIL FROM:<' . $from . '>', 250);
|
||||
$this->command('recipient', 'RCPT TO:<' . $rcpt . '>', 250);
|
||||
$this->command('recipient', 'RSET', 250);
|
||||
$this->write("QUIT\r\n");
|
||||
return $extensions;
|
||||
} finally {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Mail server of a domain: the MX with the lowest preference, else the domain itself */
|
||||
public function mailHost(string $domain): string
|
||||
{
|
||||
if (@getmxrr($domain, $hosts, $weights) && !empty($hosts)) {
|
||||
array_multisort($weights, SORT_NUMERIC, $hosts);
|
||||
return rtrim($hosts[0], '.');
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/** CRLF line ends, ending with CRLF */
|
||||
public static function crlf(string $data): string
|
||||
{
|
||||
$data = (string)preg_replace('/\r\n|\r|\n/', "\r\n", $data);
|
||||
return str_ends_with($data, "\r\n") ? $data : $data . "\r\n";
|
||||
}
|
||||
|
||||
/** RFC 5321 4.5.2: a line starting with "." gets another "." */
|
||||
public static function dotStuff(string $data): string
|
||||
{
|
||||
$data = str_replace("\r\n.", "\r\n..", $data);
|
||||
return str_starts_with($data, '.') ? '.' . $data : $data;
|
||||
}
|
||||
|
||||
/** @throws SmtpException */
|
||||
protected function open(string $host, int $port): void
|
||||
{
|
||||
$target = filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? '[' . $host . ']' : $host;
|
||||
$socket = @stream_socket_client('tcp://' . $target . ':' . $port, $errno, $error, self::TIMEOUT);
|
||||
if ($socket === false) {
|
||||
throw new SmtpException('connect', $error !== '' ? $error : 'connection failed');
|
||||
}
|
||||
stream_set_timeout($socket, self::TIMEOUT);
|
||||
$this->socket = $socket;
|
||||
}
|
||||
|
||||
protected function close(): void
|
||||
{
|
||||
if (is_resource($this->socket)) {
|
||||
fclose($this->socket);
|
||||
}
|
||||
$this->socket = null;
|
||||
}
|
||||
|
||||
/** @throws SmtpException */
|
||||
protected function write(string $data): void
|
||||
{
|
||||
for ($written = 0; $written < strlen($data); $written += $count) {
|
||||
$count = @fwrite($this->socket, substr($data, $written, 1048576));
|
||||
if ($count === false || $count === 0) {
|
||||
throw new SmtpException('data', 'connection lost while sending');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** One reply line without CRLF, null when the connection ended */
|
||||
protected function readLine(): ?string
|
||||
{
|
||||
$line = fgets($this->socket, 4096);
|
||||
return ($line === false) ? null : rtrim($line, "\r\n");
|
||||
}
|
||||
|
||||
/** @throws SmtpException */
|
||||
protected function startTls(string $host, bool $verify): void
|
||||
{
|
||||
stream_context_set_option($this->socket, 'ssl', 'verify_peer', $verify);
|
||||
stream_context_set_option($this->socket, 'ssl', 'verify_peer_name', $verify);
|
||||
stream_context_set_option($this->socket, 'ssl', 'allow_self_signed', !$verify);
|
||||
stream_context_set_option($this->socket, 'ssl', 'peer_name', $host);
|
||||
if (!@stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
|
||||
throw new SmtpException('tls', $verify ? 'TLS handshake failed or certificate not valid' : 'TLS handshake failed');
|
||||
}
|
||||
}
|
||||
|
||||
/** @return string[] extensions in upper case */
|
||||
private function ehlo(): array
|
||||
{
|
||||
$reply = $this->command('connect', 'EHLO ' . (gethostname() ?: 'mailcow'), 250);
|
||||
$extensions = [];
|
||||
foreach (array_slice(explode("\n", $reply), 1) as $line) {
|
||||
$extensions[] = strtoupper((string)strtok(substr($line, 4), ' '));
|
||||
}
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/** @throws SmtpException */
|
||||
private function command(string $step, string $command, int $code): string
|
||||
{
|
||||
$this->write($command . "\r\n");
|
||||
return $this->expect($step, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a (multi-line) reply and returns it, lines joined by "\n".
|
||||
*
|
||||
* @throws SmtpException when the code differs
|
||||
*/
|
||||
private function expect(string $step, int $code): string
|
||||
{
|
||||
$lines = [];
|
||||
do {
|
||||
$line = $this->readLine();
|
||||
if ($line === null) {
|
||||
throw new SmtpException($step, 'connection closed');
|
||||
}
|
||||
$lines[] = $line;
|
||||
} while (strlen($line) > 3 && $line[3] === '-');
|
||||
$reply = implode("\n", $lines);
|
||||
if ((int)substr($reply, 0, 3) !== $code && !($code === 250 && (int)substr($reply, 0, 3) === 251)) {
|
||||
throw new SmtpException($step, $reply);
|
||||
}
|
||||
return $reply;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Smtp;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An SMTP step failed: connect, tls, auth, sender, recipient or data, with the reply of the server.
|
||||
*/
|
||||
class SmtpException extends RuntimeException
|
||||
{
|
||||
private const MAX_TEXT_LENGTH = 200;
|
||||
|
||||
public function __construct(public readonly string $step, public readonly string $reply)
|
||||
{
|
||||
parent::__construct($step . ': ' . $reply);
|
||||
}
|
||||
|
||||
/** Short text for messages: "<step>: <reply>", without angle brackets (the UI escapes message arguments twice) */
|
||||
public function describe(): string
|
||||
{
|
||||
$text = trim((string)preg_replace('/\s+/', ' ', $this->step . ': ' . str_replace(['<', '>'], '', $this->reply)));
|
||||
return mb_substr($text, 0, self::MAX_TEXT_LENGTH);
|
||||
}
|
||||
}
|
||||
@@ -205,6 +205,9 @@ $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false;
|
||||
// Force 2FA enrollment at next login
|
||||
$MAILBOX_DEFAULT_ATTRIBUTES['force_tfa'] = false;
|
||||
|
||||
// Archive mails of the mailbox (archivecow): "inherit" follows the domain, "1" always, "0" never
|
||||
$MAILBOX_DEFAULT_ATTRIBUTES['archive'] = 'inherit';
|
||||
|
||||
// Enable SOGo access - Users will be redirected to SOGo after login (set to false to disable redirect by default)
|
||||
$MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true;
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// Archive tab of the admin system page: header profile fields and, while archiving is enabled, the state of the archive
|
||||
// under the switch (one line per problem; the details are in the archive log of the dashboard and in the queue manager).
|
||||
var mailcowArchive = (function($) {
|
||||
'use strict';
|
||||
|
||||
function L(key) {
|
||||
return (lang[key] !== undefined) ? lang[key] : key;
|
||||
}
|
||||
function esc(s) {
|
||||
return escapeHtml(s === null || s === undefined ? '' : String(s));
|
||||
}
|
||||
function time(ts) {
|
||||
return ts ? new Date(ts * 1000).toLocaleString(LOCALE, DATETIME_FORMAT) : '-';
|
||||
}
|
||||
// lang string with %s placeholders, values escaped
|
||||
function text(key, values) {
|
||||
var i = 0;
|
||||
return esc(L(key)).replace(/%s/g, function() { return esc(values[i++]); });
|
||||
}
|
||||
function notice(cls, html) {
|
||||
return '<div class="alert alert-' + cls + ' py-2 mb-2">' + html + '</div>';
|
||||
}
|
||||
var loaded = 0;
|
||||
|
||||
function renderStatus(s) {
|
||||
var html = '';
|
||||
if (s.enabled && !s.configured) {
|
||||
html += notice('warning', esc(L('archive_state_unconfigured')));
|
||||
}
|
||||
if (s.dropped_24h) {
|
||||
html += notice('danger', text('archive_state_dropped', [s.dropped_24h, time(s.last_dropped.time), s.last_dropped.reason]));
|
||||
}
|
||||
if (s.queued) {
|
||||
var reasons = $.map(s.delay_reasons || [], function(r) { return '<li class="text-break">' + esc(r) + '</li>'; });
|
||||
html += notice('warning', text('archive_state_queued', [s.queued, time(s.oldest_queued)]) +
|
||||
(reasons.length ? '<ul class="mb-0 mt-1 small">' + reasons.join('') + '</ul>' : ''));
|
||||
}
|
||||
if (!s.enabled) {
|
||||
html += notice('secondary', esc(L('archive_state_off')));
|
||||
} else if (!html) {
|
||||
html += notice('success', s.last_delivery ? text('archive_state_ok', [time(s.last_delivery)]) : esc(L('archive_state_ok_none')));
|
||||
}
|
||||
$('#archive_state').html(html);
|
||||
}
|
||||
|
||||
function load() {
|
||||
loaded = Date.now();
|
||||
$.get('/api/v1/get/archive/status', function(s) { renderStatus(s || {}); }, 'json').fail(function() {
|
||||
$('#archive_state').html(notice('danger', esc(lang_danger.generic_server_error)));
|
||||
});
|
||||
}
|
||||
|
||||
// fields of the selected header profile only; hidden fields are not posted, so their stored values are kept
|
||||
function showProfile() {
|
||||
var profile = $('#archive_profile').val();
|
||||
$('.archive-profile').each(function() {
|
||||
var on = $(this).hasClass('archive-profile-' + profile);
|
||||
$(this).toggleClass('d-none', !on).find(':input').prop('disabled', !on);
|
||||
});
|
||||
$('.archive-profile-info').each(function() {
|
||||
$(this).toggleClass('d-none', !$(this).hasClass('archive-profile-info-' + profile));
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#archive_profile').on('change', showProfile);
|
||||
// the tests use the saved settings: not with unsaved changes in the form
|
||||
$('form[data-id="archive"]').on('input change', ':input', function() {
|
||||
$('.archive-test').prop('disabled', true);
|
||||
$('#archive_test_info').text(L('archive_save_first')).removeClass('text-muted').addClass('text-warning');
|
||||
});
|
||||
// the state line is only shown while archiving is enabled
|
||||
var tab = document.getElementById('collapse-tab-archive');
|
||||
if (!tab || !document.getElementById('archive_state')) return;
|
||||
// the tab body is at the top: visible as soon as the tab (or on mobile its card) is opened; load again when shown later
|
||||
new IntersectionObserver(function(entries) {
|
||||
if (entries.some(function(e) { return e.intersectionRatio > 0; }) && Date.now() - loaded > 10000) load();
|
||||
}).observe(tab);
|
||||
$('#archive_refresh').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
load();
|
||||
});
|
||||
// open the dashboard with the archive log (the dashboard restores the last tab)
|
||||
$('#archive_log_link').on('click', function() {
|
||||
try {
|
||||
localStorage.setItem('lastTag', 'tab-archive-logs');
|
||||
} catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
return {load: load};
|
||||
})(jQuery);
|
||||
@@ -875,6 +875,81 @@ jQuery(function($){
|
||||
hideTableExpandCollapseBtn('#tab-cron-logs', '#cron_log');
|
||||
});
|
||||
}
|
||||
// archive copies from the Postfix log and mails not archived; the Message-ID finds a mail
|
||||
function draw_archive_logs() {
|
||||
// just recalc width if instance already exists
|
||||
if ($.fn.DataTable.isDataTable('#archive_log') ) {
|
||||
$('#archive_log').DataTable().columns.adjust().responsive.recalc();
|
||||
return;
|
||||
}
|
||||
// softbounce: the archive refused the copy, Postfix keeps it and tries again
|
||||
var status_class = {sent: 'bg-success', deferred: 'bg-warning text-dark', softbounce: 'bg-danger', bounced: 'bg-danger', expired: 'bg-danger', dropped: 'bg-danger'};
|
||||
|
||||
$('#archive_log').DataTable({
|
||||
responsive: true,
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_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: [[0, 'desc']],
|
||||
ajax: {
|
||||
type: "GET",
|
||||
url: "/api/v1/get/logs/archive",
|
||||
dataSrc: function(data){
|
||||
return Array.isArray(data) ? data : [];
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: lang.time,
|
||||
data: 'time',
|
||||
defaultContent: '',
|
||||
createdCell: function(td, cellData) {
|
||||
createSortableDate(td, cellData)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive_delivery,
|
||||
data: 'status',
|
||||
defaultContent: '',
|
||||
render: function(data) {
|
||||
var label = lang['archive_delivery_' + data] || data;
|
||||
return '<span class="badge ' + (status_class[data] || 'bg-secondary') + '">' + escapeHtml(label) + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive_message_id,
|
||||
data: 'message_id',
|
||||
defaultContent: '',
|
||||
className: 'text-break',
|
||||
render: function(data) {
|
||||
return escapeHtml(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive_queue_id,
|
||||
data: 'qid',
|
||||
defaultContent: '',
|
||||
render: function(data) {
|
||||
return escapeHtml(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive_details,
|
||||
data: 'text',
|
||||
defaultContent: '',
|
||||
className: 'dtr-col-md text-break',
|
||||
render: function(data) {
|
||||
return escapeHtml(data);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
function rspamd_pie_graph() {
|
||||
$.ajax({
|
||||
url: '/api/v1/get/rspamd/actions',
|
||||
@@ -1295,6 +1370,7 @@ jQuery(function($){
|
||||
onVisible("[id^=sasl_logs]", () => draw_sasl_logs());
|
||||
onVisible("[id^=netfilter_log]", () => draw_netfilter_logs());
|
||||
onVisible("[id^=cron_log]", () => draw_cron_logs());
|
||||
onVisible("[id^=archive_log]", () => draw_archive_logs());
|
||||
onVisible("[id^=rspamd_history]", () => draw_rspamd_history());
|
||||
onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
|
||||
|
||||
|
||||
+21
-14
@@ -232,6 +232,7 @@ $(document).ready(function() {
|
||||
} else {
|
||||
$('#addDomain_gal').prop('checked', false);
|
||||
}
|
||||
$('#addDomain_archive').prop('checked', template.archive == 1);
|
||||
|
||||
if (template.active == 1){
|
||||
$('#addDomain_active').prop('checked', true);
|
||||
@@ -433,6 +434,7 @@ $(document).ready(function() {
|
||||
} else {
|
||||
$('#force_tfa').prop('checked', false);
|
||||
}
|
||||
$('#addMailbox_archive').selectpicker('val', ['0', '1'].includes(String(template.archive)) ? String(template.archive) : 'inherit');
|
||||
if (template.sogo_access == 1){
|
||||
$('#sogo_access').prop('checked', true);
|
||||
} else {
|
||||
@@ -614,7 +616,7 @@ jQuery(function($){
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang_edit.ratelimit,
|
||||
title: 'RL',
|
||||
data: 'rl',
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
@@ -715,6 +717,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 +803,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 +934,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) {
|
||||
@@ -1193,6 +1189,18 @@ jQuery(function($){
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'—');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive,
|
||||
data: 'archive',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
responsivePriority: 5,
|
||||
// not shown while archiving is switched off
|
||||
className: archive_enabled ? '' : 'never',
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
@@ -1250,6 +1258,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 +1346,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,
|
||||
|
||||
+32
-6
@@ -858,6 +858,26 @@ if (isset($_GET['query'])) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "archive":
|
||||
switch ($object) {
|
||||
case "settings":
|
||||
process_get_return(\Mailcow\Archive\ArchiveModule::fromGlobals()->api->getSettings());
|
||||
break;
|
||||
|
||||
case "status":
|
||||
process_get_return(\Mailcow\Archive\ArchiveModule::fromGlobals()->api->getStatus());
|
||||
break;
|
||||
|
||||
case "mailboxes":
|
||||
process_get_return(\Mailcow\Archive\ArchiveModule::fromGlobals()->api->getMailboxes());
|
||||
break;
|
||||
|
||||
default:
|
||||
process_get_return(null);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "logs":
|
||||
switch ($object) {
|
||||
case "dovecot":
|
||||
@@ -904,6 +924,11 @@ if (isset($_GET['query'])) {
|
||||
}
|
||||
echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
|
||||
break;
|
||||
case "archive":
|
||||
// deliveries of archive copies and mails not archived, newest first
|
||||
$logs = \Mailcow\Archive\ArchiveModule::fromGlobals()->api->getLog(isset($extra) ? intval(preg_replace('/[^\d]/', '', $extra)) : 0);
|
||||
echo (!empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
|
||||
break;
|
||||
case "postfix":
|
||||
// 0 is first record, so empty is fine
|
||||
if (isset($extra)) {
|
||||
@@ -1087,14 +1112,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1686,6 +1707,8 @@ if (isset($_GET['query'])) {
|
||||
['db' => 'messages', 'dt' => 20, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"],
|
||||
['db' => 'tags', 'dt' => 23, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']],
|
||||
['db' => 'active', 'dt' => 24],
|
||||
// archived by the mailbox setting or ('inherit') the domain default
|
||||
['db' => 'archive', 'dt' => 25, 'dummy' => true, 'order_subquery' => "SELECT IF(JSON_VALUE(`m`.`attributes`, '$.archive') = '1' OR (COALESCE(JSON_VALUE(`m`.`attributes`, '$.archive'), '') NOT IN ('0', '1') AND `domain`.`archive` = '1'), 1, 0) FROM `domain` WHERE `domain`.`domain` = `m`.`domain`"],
|
||||
];
|
||||
|
||||
if($_SESSION['mailcow_cc_role'] === 'admin') {
|
||||
@@ -1957,6 +1980,9 @@ if (isset($_GET['query'])) {
|
||||
case "mailbox-group":
|
||||
process_edit_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->editGroups($items, $attr));
|
||||
break;
|
||||
case "archive":
|
||||
process_edit_return(\Mailcow\Archive\ArchiveModule::fromGlobals()->api->edit($attr));
|
||||
break;
|
||||
case "recipient_map":
|
||||
process_edit_return(recipient_map('edit', array_merge(array('id' => $items), $attr)));
|
||||
break;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"alias_domains": "Alias-Domains hinzufügen",
|
||||
"alias_external_goto": "Aliase mit externen Ziel-Domains erlauben",
|
||||
"app_passwds": "App-Passwörter verwalten",
|
||||
"archive": "Archivierung von Domains und Mailboxen ändern",
|
||||
"bcc_maps": "BCC-Maps",
|
||||
"delimiter_action": "Delimiter-Aktionen (tags)",
|
||||
"domain_desc": "Domainbeschreibung ändern",
|
||||
@@ -138,6 +139,57 @@
|
||||
"app_links": "App-Links",
|
||||
"app_name": "App-Name",
|
||||
"apps_name": "\"mailcow Apps\" Name",
|
||||
"archive": "Archivierung",
|
||||
"archive_address": "Archivadresse",
|
||||
"archive_address_info": "mailcow sendet eine Kopie jeder archivierten E-Mail an diese Adresse.",
|
||||
"archive_delivery": "Ergebnis",
|
||||
"archive_delivery_bounced": "Abgewiesen",
|
||||
"archive_delivery_deferred": "Verzögert",
|
||||
"archive_delivery_dropped": "Nicht archiviert",
|
||||
"archive_delivery_expired": "Abgelaufen, verworfen",
|
||||
"archive_delivery_sent": "Zugestellt",
|
||||
"archive_delivery_softbounce": "Abgelehnt, wird wiederholt",
|
||||
"archive_details": "Antwort des Archivs / Grund",
|
||||
"archive_enabled": "Archivierung aktivieren",
|
||||
"archive_enabled_info": "Archiviert die Domains und Mailboxen, bei denen die Archivierung aktiviert ist. Nach dem Deaktivieren entstehen keine neuen Kopien; Kopien in der Mailqueue werden noch zugestellt.",
|
||||
"archive_header_from": "Header-Name für den Envelope-Absender",
|
||||
"archive_header_rcpt": "Header-Name für die Envelope-Empfänger",
|
||||
"archive_host": "Archivserver",
|
||||
"archive_host_info": "Leer lassen, um den MX der Domain der Archivadresse zu verwenden.",
|
||||
"archive_info": "mailcow sendet eine Kopie jeder angenommenen eingehenden, ausgehenden und internen E-Mail archivierter Mailboxen per SMTP an ein externes Archiv (Journaling). Die Kopien laufen über die Mailqueue und werden erneut zugestellt, solange das Archiv nicht erreichbar ist; Absender erhalten dafür nie einen Bounce oder eine Verzögerungswarnung.",
|
||||
"archive_log": "Archiv-Protokoll",
|
||||
"archive_log_info": "Kopien an das Archiv aus dem Postfix-Protokoll im Speicher und E-Mails, die nicht archiviert wurden. Suchen Sie nach der Message-ID einer E-Mail.",
|
||||
"archive_message_id": "Message-ID",
|
||||
"archive_password": "SMTP-Passwort",
|
||||
"archive_password_remove": "Gespeichertes Passwort entfernen",
|
||||
"archive_port": "Port",
|
||||
"archive_profile": "Profil",
|
||||
"archive_profile_archivecow": "archivecow (Besitzer, Envelope, Signatur)",
|
||||
"archive_profile_archivecow_info": "Für archivecow: Signierte Header über der E-Mail nennen die archivierten Mailboxen, denen sie gehört, und den SMTP-Envelope (auch BCC-Empfänger). archivecow prüft die Signatur mit dem Signaturschlüssel.",
|
||||
"archive_profile_envelope": "Envelope-Absender und -Empfänger",
|
||||
"archive_profile_envelope_info": "Für andere Archive (z. B. MailPiler): Zwei Header über der E-Mail nennen Envelope-Absender und -Empfänger, damit das Archiv auch BCC-Empfänger zuordnen kann. Die Header sind nicht signiert.",
|
||||
"archive_profile_none": "Keine (unveränderte Kopie)",
|
||||
"archive_profile_none_info": "Die E-Mail geht unverändert ins Archiv. BCC-Empfänger sind nicht erkennbar, das Archiv ordnet die E-Mail nur nach ihren eigenen Headern (From, To, Cc) zu.",
|
||||
"archive_queue_id": "Queue-ID",
|
||||
"archive_save_first": "Erst speichern: Die Tests verwenden die gespeicherten Einstellungen.",
|
||||
"archive_section_connection": "Verbindung",
|
||||
"archive_section_headers": "Archiv-Header",
|
||||
"archive_signing_secret": "Signaturschlüssel",
|
||||
"archive_signing_secret_info": "Signiert die Archiv-Header (X-Archive-Signature). Muss mit dem Schlüssel in archivecow übereinstimmen.",
|
||||
"archive_state_dropped": "%s E-Mails wurden in den letzten 24 Stunden nicht archiviert, zuletzt um %s: %s",
|
||||
"archive_state_off": "Die Archivierung ist deaktiviert.",
|
||||
"archive_state_ok": "Die Archivierung läuft, letzte Zustellung an das Archiv: %s",
|
||||
"archive_state_ok_none": "Die Archivierung ist aktiv, das Protokoll enthält noch keine Zustellung an das Archiv",
|
||||
"archive_state_queued": "%s Kopien warten auf das Archiv, die älteste seit %s",
|
||||
"archive_state_unconfigured": "Archivadresse oder Signaturschlüssel fehlt",
|
||||
"archive_test": "Verbindung testen",
|
||||
"archive_test_info": "Die Tests verwenden die gespeicherten Einstellungen. Die Test-E-Mail geht über die Mailqueue an das Archiv.",
|
||||
"archive_testmail": "Test-E-Mail senden",
|
||||
"archive_tls": "TLS",
|
||||
"archive_tls_encrypt": "Erforderlich, ohne Zertifikatsprüfung",
|
||||
"archive_tls_may": "Wenn verfügbar",
|
||||
"archive_tls_verify": "Erforderlich, mit Zertifikatsprüfung",
|
||||
"archive_username": "SMTP-Benutzername",
|
||||
"arrival_time": "Ankunftszeit (Serverzeit)",
|
||||
"authed_user": "Auth. Benutzer",
|
||||
"ays": "Soll der Vorgang wirklich ausgeführt werden?",
|
||||
@@ -516,6 +568,19 @@
|
||||
"api_key_service_locked": "API-Key %s ist in der mailcow.conf definiert und kann nur dort geändert werden",
|
||||
"app_name_empty": "App-Name darf nicht leer sein",
|
||||
"app_passwd_id_invalid": "App-Passwort ID %s ist ungültig",
|
||||
"archive_address_invalid": "Ungültige Archivadresse",
|
||||
"archive_address_local": "Die Archivadresse darf in keiner Domain oder Alias-Domain dieser mailcow liegen",
|
||||
"archive_header_invalid": "Ungültiger Header-Name: Buchstaben, Ziffern und Bindestriche, beginnend mit einem Buchstaben",
|
||||
"archive_host_invalid": "Ungültiger Archivserver",
|
||||
"archive_incomplete": "Für die Archivierung sind eine Archivadresse und beim Profil archivecow ein Signaturschlüssel erforderlich",
|
||||
"archive_password_invalid": "Ungültiges SMTP-Passwort: druckbare ASCII-Zeichen ohne Leerzeichen, höchstens 255 Zeichen",
|
||||
"archive_port_invalid": "Ungültiger Port",
|
||||
"archive_profile_invalid": "Ungültiges Profil für die Archiv-Header",
|
||||
"archive_signing_secret_invalid": "Ungültiger Signaturschlüssel: druckbare ASCII-Zeichen ohne Leerzeichen, höchstens 255 Zeichen",
|
||||
"archive_test_failed": "Verbindungstest zum Archiv fehlgeschlagen: %s",
|
||||
"archive_testmail_failed": "Test-E-Mail konnte nicht in die Queue gestellt werden: %s",
|
||||
"archive_tls_invalid": "Ungültiger TLS-Modus",
|
||||
"archive_username_invalid": "Ungültiger SMTP-Benutzername",
|
||||
"authsource_in_use": "Der Identity Provider kann nicht geändert oder gelöscht werden, da er derzeit von einem oder mehreren Benutzern verwendet wird.",
|
||||
"bcc_empty": "BCC-Ziel darf nicht leer sein",
|
||||
"bcc_exists": "Ein BCC-Map-Eintrag %s existiert bereits als Typ %s",
|
||||
@@ -780,6 +845,15 @@
|
||||
"app_name": "App-Name",
|
||||
"app_passwd": "App-Passwörter",
|
||||
"app_passwd_protocols": "Zugelassene Protokolle für App-Passwort",
|
||||
"archive": "Archivierung",
|
||||
"archive_active": "Archiviert",
|
||||
"archive_domain": "Alle Mailboxen standardmäßig archivieren",
|
||||
"archive_domain_info": "Gilt für Mailboxen mit der Einstellung „Domain-Standard“.",
|
||||
"archive_inactive": "Nicht archiviert",
|
||||
"archive_info": "„Domain-Standard“ folgt der Einstellung der Domain. Von archivierten Mailboxen geht eine Kopie jeder gesendeten und empfangenen E-Mail an das Archiv.",
|
||||
"archive_inherit": "Domain-Standard",
|
||||
"archive_off": "Deaktiviert",
|
||||
"archive_on": "Aktiviert",
|
||||
"backup_mx_options": "Relay-Optionen",
|
||||
"bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.",
|
||||
"client_id": "Client-ID",
|
||||
@@ -1061,6 +1135,7 @@
|
||||
"allow_from_smtp": "Nur folgende IPs für <b>SMTP</b> erlauben",
|
||||
"allow_from_smtp_info": "Leer lassen, um keine Prüfung durchzuführen.<br>IPv4- sowie IPv6-Adressen und Subnetzwerke.",
|
||||
"allowed_protocols": "Erlaubte Protokolle",
|
||||
"archive": "Archiviert",
|
||||
"backup_mx": "Relay-Domain",
|
||||
"bcc": "BCC",
|
||||
"bcc_destination": "BCC-Ziel",
|
||||
@@ -1305,6 +1380,9 @@
|
||||
"app_links": "Änderungen an App-Links wurden gespeichert",
|
||||
"app_passwd_added": "App-Passwort wurde gespeichert",
|
||||
"app_passwd_removed": "App-Passwort-ID %s wurde entfernt",
|
||||
"archive_settings_saved": "Archivierungseinstellungen gespeichert",
|
||||
"archive_test_ok": "Verbindungstest erfolgreich: Der Archivserver akzeptiert die Archivadresse",
|
||||
"archive_testmail_queued": "Test-E-Mail an das Archiv in die Queue gestellt (Queue-ID %s)",
|
||||
"bcc_deleted": "BCC-Map-Einträge gelöscht: %s",
|
||||
"bcc_edited": "BCC-Map-Eintrag %s wurde geändert",
|
||||
"bcc_saved": "BCC- Map-Eintrag wurde gespeichert",
|
||||
@@ -1718,6 +1796,7 @@
|
||||
"pushover_sound": "Ton"
|
||||
},
|
||||
"warning": {
|
||||
"archive_test_ok_missing": "Verbindungstest erfolgreich, aber der Archivserver kündigt %s nicht an: 8-Bit-E-Mails werden unverändert gesendet und eventuell abgelehnt",
|
||||
"cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen",
|
||||
"domain_added_sogo_failed": "Domain wurde hinzugefügt, aber SOGo konnte nicht neugestartet werden",
|
||||
"dovecot_restart_failed": "Dovecot wurde nicht erfolgreich neu gestartet, bitte prüfen Sie die Logs.",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"alias_domains": "Add alias domains",
|
||||
"alias_external_goto": "Allow aliases with external goto domains",
|
||||
"app_passwds": "Manage app passwords",
|
||||
"archive": "Change archiving of domains and mailboxes",
|
||||
"bcc_maps": "BCC maps",
|
||||
"delimiter_action": "Delimiter action",
|
||||
"domain_desc": "Change domain description",
|
||||
@@ -142,6 +143,57 @@
|
||||
"app_links": "App links",
|
||||
"app_name": "App name",
|
||||
"apps_name": "\"mailcow Apps\" name",
|
||||
"archive": "Archiving",
|
||||
"archive_address": "Archive address",
|
||||
"archive_address_info": "mailcow sends a copy of every archived message to this address.",
|
||||
"archive_delivery": "Result",
|
||||
"archive_delivery_bounced": "Bounced",
|
||||
"archive_delivery_deferred": "Deferred",
|
||||
"archive_delivery_dropped": "Not archived",
|
||||
"archive_delivery_expired": "Expired, discarded",
|
||||
"archive_delivery_sent": "Delivered",
|
||||
"archive_delivery_softbounce": "Refused, retrying",
|
||||
"archive_details": "Answer of the archive / reason",
|
||||
"archive_enabled": "Enable archiving",
|
||||
"archive_enabled_info": "Archives the domains and mailboxes that have archiving enabled. Disabling stops new copies; copies already in the mail queue are still delivered.",
|
||||
"archive_header_from": "Header name for the envelope sender",
|
||||
"archive_header_rcpt": "Header name for the envelope recipients",
|
||||
"archive_host": "Archive server",
|
||||
"archive_host_info": "Leave empty to use the MX of the archive address domain.",
|
||||
"archive_info": "mailcow sends a copy of every accepted incoming, outgoing and internal message of archived mailboxes to an external archive by SMTP (journaling). Copies are queued and retried while the archive is unreachable; senders never get a bounce or delay notice for them.",
|
||||
"archive_log": "Archive log",
|
||||
"archive_log_info": "Copies sent to the archive, from the Postfix log in memory, and mails that were not archived. Search for the Message-ID of a mail.",
|
||||
"archive_message_id": "Message-ID",
|
||||
"archive_password": "SMTP password",
|
||||
"archive_password_remove": "Remove stored password",
|
||||
"archive_port": "Port",
|
||||
"archive_profile": "Profile",
|
||||
"archive_profile_archivecow": "archivecow (owners, envelope, signature)",
|
||||
"archive_profile_archivecow_info": "For archivecow: signed headers above the mail name the archived mailboxes it belongs to and the SMTP envelope (BCC recipients included). archivecow verifies the signature with the signing key.",
|
||||
"archive_profile_envelope": "Envelope sender and recipients",
|
||||
"archive_profile_envelope_info": "For other archives (e.g. MailPiler): two headers above the mail name the envelope sender and recipients, so the archive can assign BCC recipients too. The headers are not signed.",
|
||||
"archive_profile_none": "None (unchanged copy)",
|
||||
"archive_profile_none_info": "The mail goes to the archive unchanged. BCC recipients cannot be seen, the archive assigns the mail by its own headers (From, To, Cc) only.",
|
||||
"archive_queue_id": "Queue ID",
|
||||
"archive_save_first": "Save your changes first: the tests use the saved settings.",
|
||||
"archive_section_connection": "Connection",
|
||||
"archive_section_headers": "Archive headers",
|
||||
"archive_signing_secret": "Signing key",
|
||||
"archive_signing_secret_info": "Signs the archive headers (X-Archive-Signature). Must match the key in archivecow.",
|
||||
"archive_state_dropped": "%s mails were not archived in the last 24 hours, the last at %s: %s",
|
||||
"archive_state_off": "Archiving is disabled.",
|
||||
"archive_state_ok": "Archiving is working, last delivery to the archive: %s",
|
||||
"archive_state_ok_none": "Archiving is enabled, the log has no delivery to the archive yet",
|
||||
"archive_state_queued": "%s copies are waiting for the archive, the oldest since %s",
|
||||
"archive_state_unconfigured": "Archive address or signing key missing",
|
||||
"archive_test": "Test connection",
|
||||
"archive_test_info": "The tests use the saved settings. The test mail goes through the mail queue to the archive.",
|
||||
"archive_testmail": "Send test mail",
|
||||
"archive_tls": "TLS",
|
||||
"archive_tls_encrypt": "Required, no certificate check",
|
||||
"archive_tls_may": "If available",
|
||||
"archive_tls_verify": "Required, with certificate check",
|
||||
"archive_username": "SMTP username",
|
||||
"arrival_time": "Arrival time (server time)",
|
||||
"authed_user": "Auth. user",
|
||||
"ays": "Are you sure you want to proceed?",
|
||||
@@ -508,6 +560,19 @@
|
||||
"api_key_description_invalid": "API key description must not exceed 255 characters",
|
||||
"api_key_network_invalid": "Invalid IP address or network: %s",
|
||||
"api_key_service_locked": "API key %s is defined in mailcow.conf and can only be changed there",
|
||||
"archive_address_invalid": "Invalid archive address",
|
||||
"archive_address_local": "The archive address must not be in a domain or alias domain of this mailcow",
|
||||
"archive_header_invalid": "Invalid header name: letters, digits and hyphens, starting with a letter",
|
||||
"archive_host_invalid": "Invalid archive server",
|
||||
"archive_incomplete": "Archiving requires an archive address and, for the archivecow profile, a signing key",
|
||||
"archive_password_invalid": "Invalid SMTP password: printable ASCII without spaces, at most 255 characters",
|
||||
"archive_port_invalid": "Invalid port",
|
||||
"archive_profile_invalid": "Invalid archive header profile",
|
||||
"archive_signing_secret_invalid": "Invalid signing key: printable ASCII without spaces, at most 255 characters",
|
||||
"archive_test_failed": "Connection test to the archive failed: %s",
|
||||
"archive_testmail_failed": "Test mail could not be queued: %s",
|
||||
"archive_tls_invalid": "Invalid TLS mode",
|
||||
"archive_username_invalid": "Invalid SMTP username",
|
||||
"group_address_invalid": "Group address %s is invalid or not in the domain of the group",
|
||||
"group_description_invalid": "The group description must not exceed 255 characters",
|
||||
"group_grant_invalid": "Invalid share for mailbox %s",
|
||||
@@ -780,6 +845,15 @@
|
||||
"app_name": "App name",
|
||||
"app_passwd": "App password",
|
||||
"app_passwd_protocols": "Allowed protocols for app password",
|
||||
"archive": "Archiving",
|
||||
"archive_active": "Archived",
|
||||
"archive_domain": "Archive all mailboxes by default",
|
||||
"archive_domain_info": "Applies to mailboxes set to \"Domain default\".",
|
||||
"archive_inactive": "Not archived",
|
||||
"archive_info": "\"Domain default\" follows the domain setting. Archived mailboxes send a copy of every message they send or receive to the archive.",
|
||||
"archive_inherit": "Domain default",
|
||||
"archive_off": "Disabled",
|
||||
"archive_on": "Enabled",
|
||||
"backup_mx_options": "Relay options",
|
||||
"bcc_dest_format": "BCC destination must be a single valid email address.<br>If you need to send a copy to multiple addresses, create an alias and use it here.",
|
||||
"client_id": "Client ID",
|
||||
@@ -1062,6 +1136,7 @@
|
||||
"allow_from_smtp": "Only allow these IPs to use <b>SMTP</b>",
|
||||
"allow_from_smtp_info": "Leave empty to allow all senders.<br>IPv4/IPv6 addresses and networks.",
|
||||
"allowed_protocols": "Allowed protocols",
|
||||
"archive": "Archived",
|
||||
"backup_mx": "Relay domain",
|
||||
"bcc": "BCC",
|
||||
"bcc_destination": "BCC destination",
|
||||
@@ -1127,7 +1202,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",
|
||||
@@ -1312,6 +1387,9 @@
|
||||
"app_links": "Saved changes to app links",
|
||||
"app_passwd_added": "Added new app password",
|
||||
"app_passwd_removed": "Removed app password ID %s",
|
||||
"archive_settings_saved": "Archive settings saved",
|
||||
"archive_test_ok": "Connection test passed: the archive server accepts the archive address",
|
||||
"archive_testmail_queued": "Test mail to the archive queued (queue ID %s)",
|
||||
"bcc_deleted": "BCC map entries deleted: %s",
|
||||
"bcc_edited": "BCC map entry %s edited",
|
||||
"bcc_saved": "BCC map entry saved",
|
||||
@@ -1429,7 +1507,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"
|
||||
@@ -1726,6 +1803,7 @@
|
||||
"years": "years"
|
||||
},
|
||||
"warning": {
|
||||
"archive_test_ok_missing": "Connection test passed, but the archive server does not announce %s: 8-bit messages are sent unchanged and may be rejected",
|
||||
"cannot_delete_self": "Cannot delete logged in user",
|
||||
"domain_added_sogo_failed": "Added domain but failed to restart SOGo, please check your server logs.",
|
||||
"dovecot_restart_failed": "Dovecot failed to restart, please check the logs",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item"><button class="nav-link" data-bs-target="#tab-routing" aria-selected="false" aria-controls="tab-routing" role="tab" data-bs-toggle="tab">{{ lang.admin.routing }}</button></li>
|
||||
<li role="presentation" class="nav-item"><button class="nav-link" data-bs-target="#tab-archive" aria-selected="false" aria-controls="tab-archive" role="tab" data-bs-toggle="tab">{{ lang.admin.archive }}</button></li>
|
||||
<li role="presentation" class="nav-item"><button class="nav-link" data-bs-target="#tab-sys-mails" aria-selected="false" aria-controls="tab-sys-mails" role="tab" data-bs-toggle="tab">{{ lang.admin.sys_mails }}</button></li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">{{ lang.admin.rspamd_global_filters }}</a>
|
||||
@@ -47,6 +48,7 @@
|
||||
{% include 'admin/tab-config-oauth2.twig' %}
|
||||
{% include 'admin/tab-config-rspamd.twig' %}
|
||||
{% include 'admin/tab-routing.twig' %}
|
||||
{% include 'admin/tab-archive.twig' %}
|
||||
{% include 'admin/tab-config-dkim.twig' %}
|
||||
{% include 'admin/tab-config-fwdhosts.twig' %}
|
||||
{% include 'admin/tab-config-f2b.twig' %}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<div class="tab-pane fade" id="tab-archive" role="tabpanel" aria-labelledby="tab-archive">
|
||||
<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-archive" data-bs-toggle="collapse" aria-controls="collapse-tab-archive">
|
||||
{{ lang.admin.archive }}
|
||||
</button>
|
||||
<span class="d-none d-md-block">{{ lang.admin.archive }}</span>
|
||||
</div>
|
||||
<div id="collapse-tab-archive" class="card-body collapse" data-bs-parent="#admin-content">
|
||||
<div class="row">
|
||||
<p class="offset-md-3 col-12 col-md-9 mb-4">{{ lang.admin.archive_info }}</p>
|
||||
</div>
|
||||
<form class="form-horizontal" role="form" data-id="archive" method="post" autocomplete="off">
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_enabled">{{ lang.admin.archive_enabled }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 d-flex align-items-center">
|
||||
<input type="hidden" name="enabled" value="0">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="archive_enabled" name="enabled" value="1"{% if archive_settings.enabled == 1 %} checked{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="offset-md-3 col-12 col-md-9">
|
||||
<small class="text-muted">{{ lang.admin.archive_enabled_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if archive_settings.enabled == 1 %}
|
||||
{# Filled by js/site/archive.js: one line per problem; details in the archive log and the queue manager #}
|
||||
<div class="row mb-2" id="archive_status">
|
||||
<div class="offset-md-3 col-12 col-md-9 col-lg-7">
|
||||
<div id="archive_state"><p class="text-muted mb-2">{{ lang.datatables.loadingRecords }}</p></div>
|
||||
<small class="d-block">
|
||||
<a href="/admin/dashboard" id="archive_log_link"><i class="bi bi-journal-text"></i> {{ lang.admin.archive_log }}</a>
|
||||
<a href="/admin/queue" class="ms-3"><i class="bi bi-inboxes"></i> {{ lang.queue.queue_manager }}</a>
|
||||
<a href="#" class="ms-3" id="archive_refresh"><i class="bi bi-arrow-clockwise"></i> {{ lang.admin.refresh }}</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<legend class="fs-5 mt-4"><i class="bi bi-plug"></i> {{ lang.admin.archive_section_connection }}</legend><hr>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_address">{{ lang.admin.archive_address }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<input type="email" class="form-control" id="archive_address" name="address" placeholder="journal@archive.example.com" value="{{ archive_settings.address }}">
|
||||
</div>
|
||||
<div class="offset-md-3 col-12 col-md-9">
|
||||
<small class="text-muted">{{ lang.admin.archive_address_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_host">{{ lang.admin.archive_host }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="archive_host" name="host" placeholder="archive.example.com" value="{{ archive_settings.host }}">
|
||||
<span class="input-group-text">{{ lang.admin.archive_port }}</span>
|
||||
<input type="number" class="form-control" id="archive_port" name="port" min="1" max="65535" style="max-width: 7rem" aria-label="{{ lang.admin.archive_port }}" value="{{ archive_settings.port }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="offset-md-3 col-12 col-md-9">
|
||||
<small class="text-muted">{{ lang.admin.archive_host_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_tls">{{ lang.admin.archive_tls }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<select class="full-width-select form-control" id="archive_tls" name="tls">
|
||||
{% for mode in ['encrypt', 'verify', 'may'] %}
|
||||
<option value="{{ mode }}"{% if archive_settings.tls == mode %} selected{% endif %}>{{ attribute(lang.admin, 'archive_tls_' ~ mode) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_username">{{ lang.admin.archive_username }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<input type="text" class="form-control" id="archive_username" name="username" autocomplete="off" value="{{ archive_settings.username }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_password">{{ lang.admin.archive_password }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<input type="password" class="form-control" id="archive_password" name="password" autocomplete="new-password"
|
||||
placeholder="{% if archive_settings.has_password == 1 %}{{ lang.edit.unchanged_if_empty }}{% endif %}">
|
||||
{% if archive_settings.has_password == 1 %}
|
||||
<div class="form-check mt-1">
|
||||
<input class="form-check-input" type="checkbox" id="archive_password_remove" name="password_remove" value="1">
|
||||
<label class="form-check-label" for="archive_password_remove">{{ lang.admin.archive_password_remove }}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<legend class="fs-5 mt-4"><i class="bi bi-file-earmark-text"></i> {{ lang.admin.archive_section_headers }}</legend><hr>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_profile">{{ lang.admin.archive_profile }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<select class="full-width-select form-control" id="archive_profile" name="profile">
|
||||
{% for profile in ['archivecow', 'envelope', 'none'] %}
|
||||
<option value="{{ profile }}"{% if archive_settings.profile == profile %} selected{% endif %}>{{ attribute(lang.admin, 'archive_profile_' ~ profile) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="offset-md-3 col-12 col-md-9">
|
||||
{% for profile in ['archivecow', 'envelope', 'none'] %}
|
||||
<small class="text-muted archive-profile-info archive-profile-info-{{ profile }}{% if archive_settings.profile != profile %} d-none{% endif %}">{{ attribute(lang.admin, 'archive_profile_' ~ profile ~ '_info') }}</small>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{# Fields of the other profiles are hidden and disabled, so their stored values are kept #}
|
||||
{% set archivecow = archive_settings.profile == 'archivecow' %}
|
||||
{% set envelope = archive_settings.profile == 'envelope' %}
|
||||
<div class="row mb-2 archive-profile archive-profile-archivecow{% if not archivecow %} d-none{% endif %}">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_signing_secret">{{ lang.admin.archive_signing_secret }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<input type="password" class="form-control" id="archive_signing_secret" name="signing_secret" autocomplete="new-password"
|
||||
placeholder="{% if archive_settings.has_signing_secret == 1 %}{{ lang.edit.unchanged_if_empty }}{% endif %}"{% if not archivecow %} disabled{% endif %}>
|
||||
</div>
|
||||
<div class="offset-md-3 col-12 col-md-9">
|
||||
<small class="text-muted">{{ lang.admin.archive_signing_secret_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 archive-profile archive-profile-envelope{% if not envelope %} d-none{% endif %}">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_header_from">{{ lang.admin.archive_header_from }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<input type="text" class="form-control" id="archive_header_from" name="header_from" value="{{ archive_settings.header_from }}"{% if not envelope %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 archive-profile archive-profile-envelope{% if not envelope %} d-none{% endif %}">
|
||||
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
|
||||
<label class="control-label" for="archive_header_rcpt">{{ lang.admin.archive_header_rcpt }}:</label>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-5">
|
||||
<input type="text" class="form-control" id="archive_header_rcpt" name="header_rcpt" value="{{ archive_settings.header_rcpt }}"{% if not envelope %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="mt-4">
|
||||
<div class="row mb-2">
|
||||
<div class="offset-md-3 col-12 col-md-9">
|
||||
{% set archive_ready = archive_settings.address and (archive_settings.profile != 'archivecow' or archive_settings.has_signing_secret == 1) %}
|
||||
<button type="button" class="btn btn-sm d-block d-sm-inline btn-success mb-1" data-action="edit_selected" data-item="archive" data-id="archive" data-api-url='edit/archive' data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
|
||||
<button type="button" class="btn btn-sm d-block d-sm-inline btn-secondary mb-1 archive-test" data-action="edit_selected" data-item="archive" data-id="archive_test" data-api-url='edit/archive' data-api-attr='{"action":"test"}'{% if not archive_ready %} disabled{% endif %}><i class="bi bi-plug"></i> {{ lang.admin.archive_test }}</button>
|
||||
<button type="button" class="btn btn-sm d-block d-sm-inline btn-secondary mb-1 archive-test" data-action="edit_selected" data-item="archive" data-id="archive_test" data-api-url='edit/archive' data-api-attr='{"action":"testmail"}'{% if not archive_ready %} disabled{% endif %}><i class="bi bi-envelope"></i> {{ lang.admin.archive_testmail }}</button>
|
||||
<small class="text-muted d-block" id="archive_test_info">{{ lang.admin.archive_test_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<li role="presentation"><button class="dropdown-item" data-bs-target="#tab-api-logs" aria-selected="false" aria-controls="tab-api-logs" role="tab" data-bs-toggle="tab">API</button></li>
|
||||
<li role="presentation"><button class="dropdown-item" data-bs-target="#tab-api-rl" aria-selected="false" aria-controls="tab-api-rl" role="tab" data-bs-toggle="tab">Ratelimits</button></li>
|
||||
<li role="presentation"><button class="dropdown-item" data-bs-target="#tab-cron-logs" aria-selected="false" aria-controls="tab-api-rl" role="tab" data-bs-toggle="tab">Crontasks</button></li>
|
||||
<li role="presentation"><button class="dropdown-item" data-bs-target="#tab-archive-logs" aria-selected="false" aria-controls="tab-archive-logs" role="tab" data-bs-toggle="tab">{{ lang.admin.archive }}</button></li>
|
||||
<li role="presentation"><span class="dropdown-header fs-6">{{ lang.debug.external_logs }}</span></li>
|
||||
<li role="presentation"><button class="dropdown-item" data-bs-target="#tab-rspamd-history" aria-selected="false" aria-controls="tab-rspamd-history" role="tab" data-bs-toggle="tab">Rspamd</button></li>
|
||||
<li role="presentation"><span class="dropdown-header fs-6">{{ lang.debug.static_logs }}</span></li>
|
||||
@@ -547,6 +548,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tab-archive-logs">
|
||||
<div class="debug-log-info">{{ lang.admin.archive_log_info }}</div>
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center fs-5">
|
||||
<span class="mt-2 ms-2">{{ lang.admin.archive }}</span>
|
||||
<div class="btn-group ms-auto">
|
||||
<button class="btn btn-sm btn-secondary refresh_table" data-table="archive_log">{{ lang.admin.refresh }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table id="archive_log" class="table table-striped dt-responsive w-100"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /tab-content -->
|
||||
</div> <!-- /col-md-12 -->
|
||||
</div> <!-- /row -->
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<input type="hidden" value="0" name="active">
|
||||
<input type="hidden" value="0" name="backupmx">
|
||||
<input type="hidden" value="0" name="gal">
|
||||
<input type="hidden" value="0" name="archive">
|
||||
<input type="hidden" value="0" name="relay_all_recipients">
|
||||
<input type="hidden" value="0" name="relay_unknown_only">
|
||||
|
||||
@@ -70,6 +71,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row{% if not archive_enabled %} d-none{% endif %}">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="archive"{% if template.attributes.archive == '1' %} checked{% endif %}> {{ lang.edit.archive_domain }}</label>
|
||||
<small class="text-muted">{{ lang.edit.archive_domain_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<input type="hidden" value="0" name="active">
|
||||
<input type="hidden" value="0" name="backupmx">
|
||||
<input type="hidden" value="0" name="gal">
|
||||
<input type="hidden" value="0" name="archive">
|
||||
<input type="hidden" value="0" name="relay_all_recipients">
|
||||
<input type="hidden" value="0" name="relay_unknown_only">
|
||||
<div class="row mb-4">
|
||||
@@ -133,6 +134,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row{% if not archive_enabled %} d-none{% endif %}" data-acl="{{ acl.archive }}">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="archive"{% if result.archive == '1' %} checked{% endif %}{% if acl.archive != 1 %} disabled{% endif %}> {{ lang.edit.archive_domain }}</label>
|
||||
<small class="text-muted">{{ lang.edit.archive_domain_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row mb-2">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
|
||||
@@ -114,6 +114,17 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2{% if not archive_enabled %} d-none{% endif %}">
|
||||
<label class="control-label col-sm-2" for="archive">{{ lang.edit.archive }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" name="archive" id="archive">
|
||||
<option value="inherit"{% if template.attributes.archive not in ['0', '1'] %} selected{% endif %}>{{ lang.edit.archive_inherit }}</option>
|
||||
<option value="1"{% if template.attributes.archive == '1' %} selected{% endif %}>{{ lang.edit.archive_on }}</option>
|
||||
<option value="0"{% if template.attributes.archive == '0' %} selected{% endif %}>{{ lang.edit.archive_off }}</option>
|
||||
</select>
|
||||
<small class="text-muted d-block">{{ lang.edit.archive_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2">ACL</label>
|
||||
<div class="col-sm-10">
|
||||
|
||||
@@ -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>
|
||||
@@ -333,6 +331,19 @@
|
||||
<small class="text-muted">{{ lang.edit.allow_from_smtp_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2{% if not archive_enabled %} d-none{% endif %}" data-acl="{{ acl.archive }}">
|
||||
<label class="control-label col-sm-2" for="archive">{{ lang.edit.archive }}
|
||||
<br><span class="badge {% if result.archive == 1 %}bg-success{% else %}bg-secondary{% endif %}">{% if result.archive == 1 %}{{ lang.edit.archive_active }}{% else %}{{ lang.edit.archive_inactive }}{% endif %}</span>
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" name="archive" id="archive"{% if acl.archive != 1 %} disabled{% endif %}>
|
||||
<option value="inherit"{% if result.attributes.archive not in ['0', '1'] %} selected{% endif %}>{{ lang.edit.archive_inherit }}</option>
|
||||
<option value="1"{% if result.attributes.archive == '1' %} selected{% endif %}>{{ lang.edit.archive_on }}</option>
|
||||
<option value="0"{% if result.attributes.archive == '0' %} selected{% endif %}>{{ lang.edit.archive_off }}</option>
|
||||
</select>
|
||||
<small class="text-muted d-block">{{ lang.edit.archive_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
var csrf_token = '{{ csrf_token }}';
|
||||
var pagination_size = Math.trunc('{{ pagination_size }}');
|
||||
var role = '{{ role }}';
|
||||
var archive_enabled = {{ archive_enabled ? 1 : 0 }};
|
||||
var is_dual = {{ is_dual }};
|
||||
var ALLOW_ADMIN_EMAIL_LOGIN = {{ allow_admin_email_login }};
|
||||
</script>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="description">{{ lang.mailbox.template }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select data-live-search="true" id="mailbox_templates" class="form-control" title="{{ lang.mailbox.template }}">
|
||||
<select data-live-search="true" id="mailbox_templates" name="template" class="form-control" title="{{ lang.mailbox.template }}">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,6 +154,17 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2{% if not archive_enabled %} d-none{% endif %}" data-acl="{{ acl.archive }}">
|
||||
<label class="control-label col-sm-2 text-sm-end" for="addMailbox_archive">{{ lang.edit.archive }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" name="archive" id="addMailbox_archive"{% if acl.archive != 1 %} disabled{% endif %}>
|
||||
<option value="inherit" selected>{{ lang.edit.archive_inherit }}</option>
|
||||
<option value="1">{{ lang.edit.archive_on }}</option>
|
||||
<option value="0">{{ lang.edit.archive_off }}</option>
|
||||
</select>
|
||||
<small class="text-muted d-block">{{ lang.edit.archive_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end">ACL</label>
|
||||
<div class="col-sm-10">
|
||||
@@ -360,6 +371,17 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2{% if not archive_enabled %} d-none{% endif %}">
|
||||
<label class="control-label col-sm-2 text-sm-end" for="addMailboxTemplate_archive">{{ lang.edit.archive }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" name="archive" id="addMailboxTemplate_archive">
|
||||
<option value="inherit" selected>{{ lang.edit.archive_inherit }}</option>
|
||||
<option value="1">{{ lang.edit.archive_on }}</option>
|
||||
<option value="0">{{ lang.edit.archive_off }}</option>
|
||||
</select>
|
||||
<small class="text-muted d-block">{{ lang.edit.archive_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2 text-sm-end text-sm-end">ACL</label>
|
||||
<div class="col-sm-10">
|
||||
@@ -451,6 +473,7 @@
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal" data-cached-form="true" data-id="add_domain" role="form">
|
||||
<input type="hidden" value="0" name="gal">
|
||||
<input type="hidden" value="0" name="archive">
|
||||
<input type="hidden" value="0" name="active">
|
||||
<input type="hidden" value="0" name="backupmx">
|
||||
<input type="hidden" value="0" name="relay_all_recipients">
|
||||
@@ -525,6 +548,14 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row mb-2{% if not archive_enabled %} d-none{% endif %}" data-acl="{{ acl.archive }}">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="archive" id="addDomain_archive"> {{ lang.edit.archive_domain }}</label>
|
||||
<small class="text-muted">{{ lang.edit.archive_domain_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
@@ -664,6 +695,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row{% if not archive_enabled %} d-none{% endif %}">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="archive"> {{ lang.edit.archive_domain }}</label>
|
||||
<small class="text-muted">{{ lang.edit.archive_domain_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
|
||||
+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=06112026_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'));
|
||||
}
|
||||
}
|
||||
Loaded 100 of 154 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user