Compare commits

..
Author SHA1 Message Date
FreddleSpl0it 2a2ffca169 [Web] API: update Swagger UI to 5.34.1, plain inline code 2026-11-10 20:28:43 +01:00
FreddleSpl0it 1a5095c0f4 [Web] API: shorter descriptions 2026-11-10 20:25:51 +01:00
FreddleSpl0it 2418da031f [Web] API: complete the OpenAPI docs, add route tests for json_api.php 2026-11-10 20:04:05 +01:00
FreddleSpl0it 9825c8d290 Merge pull request #7641 from mailcow/fix/7436
[Update.sh] Keep ENABLE_IPV6 as set, do not disable IPv6 on hosts with a public IPv6 address
2026-11-10 17:09:56 +01:00
FreddleSpl0it dc880d861c [Update] Keep ENABLE_IPV6 as set, do not disable IPv6 on hosts with a public IPv6 address 2026-11-10 17:04:50 +01:00
FreddleSpl0it a03fd8aaad Merge pull request #7500 from philippderdiedas/fix/update-sh-masquerade-bridge
[Update] Fix update.sh deleting mailcow's own MASQUERADE rule
2026-11-10 15:39:48 +01:00
FreddleSpl0it e3a90718c2 Merge pull request #7516 from wbilalz400/fix/webauthn-forced-enrollment-empty-credential
[Web] Fix forced WebAuthn TFA enrollment storing an empty credential
2026-11-10 15:21:30 +01:00
FreddleSpl0it 54a85ba439 Merge pull request #7540 from pReya/fix/table-ui-polish
[Web] Table UI polish (icon centering, tag heights, sort headers)
2026-11-10 14:07:39 +01:00
FreddleSpl0it 4ced82d554 Merge pull request #7548 from junkerderprovinz/fix/idp-ignore-ssl-error-key
[Web] Fix ignore_ssl_error key in identity provider settings
2026-11-10 13:55:15 +01:00
FreddleSpl0it f83e02b740 Merge pull request #7640 from mailcow/fix/7562
[Rspamd] Remove the URLhaus and MalwareBazaar maps retired by abuse.ch
2026-11-10 13:51:45 +01:00
FreddleSpl0it 04c9413ed9 [Rspamd] Remove the URLhaus and MalwareBazaar maps retired by abuse.ch 2026-11-10 13:50:48 +01:00
FreddleSpl0it 5e98d8e279 Merge pull request #7581 from peterschwps/fix/trim-logs-redis-password
[Dovecot] Mask Redis password in trim_logs output
2026-11-10 13:43:36 +01:00
FreddleSpl0it 4caecf77e0 Merge pull request #7610 from woodfairy/fix/brotli-cve-2026-111497
[Nginx][Clamd][Postfix] Rebuild images for brotli CVE-2026-111497 (CISA KEV)
2026-11-10 13:40:04 +01:00
FreddleSpl0it 37d304d075 Merge pull request #7544 from jorgeavlobo/fix/keycloak-admin-token-introspect
[Web] Keycloak: skip introspection of an expired admin token and reuse the new one
2026-11-10 01:45:16 +01:00
FreddleSpl0it 25086a9b3f Merge pull request #7488 from apio-sys/fix/rspamd-dqs-dbl-full-rule
[Rspamd] write full DBL rule when SPAMHAUS_DQS_KEY is set
2026-11-10 01:42:29 +01:00
FreddleSpl0it 7a3f1cbd41 Merge pull request #7530 from ThxGiving/fix/eas-auth-challenge
[Nginx] Send Basic auth challenge for unauthenticated ActiveSync requests
2026-11-10 01:39:28 +01:00
FreddleSpl0it 616717bc0d Merge pull request #7639 from mailcow/fix/7580
[Rspamd] Remove MX check overrides that rspamd ignores
2026-11-10 01:28:42 +01:00
FreddleSpl0it 6fd271ee64 [Rspamd] Remove MX check overrides that rspamd ignores 2026-11-10 01:27:52 +01:00
FreddleSpl0it 7459cc046a Merge pull request #7638 from mailcow/fix/7579
[Web] Template lists: sort size columns numerically
2026-11-10 01:08:36 +01:00
FreddleSpl0it 58dad73eee [Web] Template lists: sort size columns by bytes 2026-11-10 01:07:32 +01:00
FreddleSpl0it b21acd4787 Merge pull request #7637 from mailcow/fix/7557
[Web] Domain admins saw sender aliases of other domains
2026-11-10 01:02:39 +01:00
FreddleSpl0it 4ab954dcd1 [Web] Domain admins: show sender ACL and alias entries of their own domains only 2026-11-10 01:01:22 +01:00
FreddleSpl0it eefc17274e Merge pull request #7636 from mailcow/fix/7545
[Rspamd] WHITELISTED_FWD_HOST was skipped at the reject threshold
2026-11-10 00:57:45 +01:00
FreddleSpl0it 4878840bdd [Rspamd] Forwarding hosts: check them as prefilter and scan the mail completely 2026-11-10 00:56:53 +01:00
FreddleSpl0it 869bcb3c42 Merge pull request #7635 from mailcow/fix/7518
[Rspamd] Domain footer broke DKIM for mail without Content-Transfer-Encoding
2026-11-09 23:36:42 +01:00
FreddleSpl0it 0bcde13500 [Rspamd] Domain footer: add the Content-Transfer-Encoding header to the delivered mail 2026-11-09 23:34:57 +01:00
FreddleSpl0it 867b9dddab Merge pull request #7634 from mailcow/fix/7503
[Web] Keycloak: accept Base64-encoded mailcow_password
2026-11-09 23:30:20 +01:00
FreddleSpl0it a4326e6642 [Web] Keycloak Mailpassword Flow: accept Base64-encoded password hashes 2026-11-09 23:28:53 +01:00
FreddleSpl0it c5e89a9e03 Merge pull request #7633 from mailcow/fix/7501
[Web] Forwarding hosts: store IPv6 addresses in canonical form
2026-11-09 23:25:40 +01:00
FreddleSpl0it 45d31b3279 [Web] Forwarding hosts: store IPv6 addresses in canonical form 2026-11-09 23:24:39 +01:00
FreddleSpl0it 24780ea5fb Merge pull request #7632 from mailcow/fix/7489
[Rspamd] Quarantine stored nothing without Q_EXCLUDE_DOMAINS
2026-11-09 23:22:11 +01:00
FreddleSpl0it 38b031e10c [Rspamd] Quarantine: store mail when Q_EXCLUDE_DOMAINS is not set 2026-11-09 23:21:14 +01:00
FreddleSpl0it e2fb833ceb Merge pull request #7631 from mailcow/fix/7482
[Web] Autodiscover: no ActiveSync when autodiscoverType is imap
2026-11-09 23:18:50 +01:00
FreddleSpl0it e27cb83326 [Web] Autodiscover: no ActiveSync when autodiscoverType is imap 2026-11-09 23:17:55 +01:00
FreddleSpl0it f810cecd3a Merge pull request #7630 from mailcow/fix/7476
[Web] force_tfa did not prevent removing 2FA
2026-11-09 23:14:38 +01:00
FreddleSpl0it cfad223824 [Web] force_tfa: read the flag with JSON_VALUE 2026-11-09 23:13:26 +01:00
FreddleSpl0it 6d20463acb Merge pull request #7629 from mailcow/fix/7454
[Nginx] Edit and quarantine links: allow a trailing slash
2026-11-09 23:09:05 +01:00
FreddleSpl0it 1a42c19050 [Nginx] Accept a trailing slash in /edit and /qhandler links 2026-11-09 23:08:14 +01:00
FreddleSpl0it b9f8eb4c31 Merge pull request #7628 from mailcow/fix/7449
[Postfix-TLSPol] Update to 1.14.5
2026-11-09 23:05:35 +01:00
FreddleSpl0it ce4150f921 [Postfix-TLSPol] Update to 1.14.5 2026-11-09 23:04:33 +01:00
FreddleSpl0it 7cc8cac705 Merge pull request #7627 from mailcow/fix/7318
[Rspamd] Mails between 50 and 100 MiB were deferred forever
2026-11-09 23:01:43 +01:00
FreddleSpl0it 3fd1d017cf [Rspamd] Raise max_message above the Postfix message size limit 2026-11-09 23:00:38 +01:00
FreddleSpl0it 8597954f91 Merge pull request #7626 from mailcow/fix/7179
[Rspamd] Sub-addressing: read the tag from the original SMTP recipient
2026-11-09 22:55:05 +01:00
FreddleSpl0it 2b4ffe8808 [Rspamd] Sub-addressing: read the tag from the original SMTP recipient 2026-11-09 22:53:07 +01:00
FreddleSpl0it 1911847c3b Merge pull request #7625 from mailcow/fix/6931
[Web] Update "Last modified" when tags of a domain or mailbox change
2026-11-09 22:48:21 +01:00
FreddleSpl0it 497872080b [Web] Update "Last modified" when tags of a domain or mailbox change 2026-11-09 22:46:15 +01:00
FreddleSpl0it a0dc3447f8 Merge pull request #7624 from mailcow/fix/6853
[Web][Dovecot] Mailbox list and API: show the last SOGo login
2026-11-09 22:42:27 +01:00
FreddleSpl0it e0640949a2 [Web][Dovecot] Show the last SOGo login in the mailbox list and API 2026-11-09 22:39:42 +01:00
milkmaker bf744413bd Translations update from Weblate (#7623)
* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>
2026-11-09 21:41:36 +01:00
FreddleSpl0it 262de81da1 Merge pull request #7621 from mailcow/fix/7606
[Dockerapi] Group IMAP grants: set ACLs only on existing folders
2026-11-09 19:43:38 +01:00
FreddleSpl0it 5f13bb2046 [Dockerapi] Group IMAP grants: set ACLs only on existing folders 2026-11-09 19:41:17 +01:00
woodfairy 0a0686ef19 [Nginx][Clamd][Postfix] Rebuild images for brotli CVE-2026-111497
The current nginx, clamd and postfix images ship a brotli decoder in the
affected range 1.0.3 - 1.2.0 (CVE-2026-111497, CVSS 9.8, listed in CISA KEV):

- nginx:1.30.6-2   brotli-libs 1.2.0-r1 (fixed in Alpine 3.24: 1.2.1-r0)
- clamd:1.4.6-1    brotli-libs 1.2.0-r1 (fixed in Alpine 3.24: 1.2.1-r0)
- postfix:3.10.12-1 libbrotli1 1.1.0-2+b7 (fixed in trixie-security:
  1.1.0-2+deb13u1, DSA-6582-1)

All other mailcow images already contain a fixed version.

clamd and postfix only need a rebuild, which pulls the fixed packages.
The postfix rebuild also picks up Debian's postfix 3.10.13, hence the
new tag postfix:3.10.13-1. The
nginx image never upgraded the packages of its base image, so it only got
fixes when nginx:<version>-alpine was republished. It now runs
apk upgrade like clamd and acme do.
2026-11-08 00:49:55 +01:00
Peter Schwips 24b91f6089 [Dovecot] Mask Redis password in trim_logs output 2026-10-30 23:54:21 +01:00
Junker der Provinzanddiko 5705a48724 [Web] Fix ignore_ssl_error key in identity provider settings
The edit action stores the setting as ignore_ssl_error, but the get
action cast and defaulted ignore_ssl_errors. The stored value was
returned as a string and LDAP setups got an extra ignore_ssl_errors
field that was always false.

Co-authored-by: diko <108140792+admins-little-helper@users.noreply.github.com>
2026-10-24 20:05:10 +02:00
Jorge Lobo 2f76230daf [Web] Keycloak: skip introspection of expired admin token 2026-10-23 00:52:03 +01:00
Moritz Stückler 498434ee3d [Web] Even out spacing of the expanded table row
DataTables removes the top padding of the first entry in the expanded
row, but mailcow's 0.5em padding left 7px under the last entry, so the
list had 7px of space above and 15px below and the last entry (often
Tags) looked taller than the others. Drop the last entry's bottom
padding as well.

Signed-off-by: Moritz Stückler <moritz@bitbetter.de>
2026-10-22 13:10:49 +02:00
Moritz Stückler f662af4c50 [Web] Even out badge and tag sizes in tables
- Use the normal cursor on non-orderable headers. The expand column
  header kept a pointer cursor from sorting_asc although clicking it
  does nothing.
- Let icons in badges inherit the badge font size, like .btn .bi, and
  keep their glyph from adding line height. The global 12pt icon size
  made e.g. the "SSO @" badge in Last mail login taller than the IMAP
  and SMTP badges next to it. Tag badges keep their 12px icon.
- Drop the vertical margin of tag badges inside tables. It is meant for
  the tag editor in edit forms and made the Tags entry of the expanded
  row taller than the others.
- Center tag badge captions: the 12px tag icon stretched the 10.5px
  badge line and pushed the caption below center.

Signed-off-by: Moritz Stückler <moritz@bitbetter.de>
2026-10-22 13:06:00 +02:00
Moritz Stückler 8d4895a54d [Web] Align the table expand control and row checkbox
- Center the +/- glyph in the expand control: its line-height (1.25em)
  was larger than the 1em box, pushing the glyph down. Derive the box's
  margin-top from its size so it is centered in the row (desktop and
  the larger mobile variant).
- Hide sort arrows on non-orderable headers. The default initial order
  targets column 0, the expand column, which got a sorting_asc arrow
  and arrow padding although it cannot be sorted.
- Center row checkboxes level with the expand control by using
  Bootstrap's 0.25em margin-top instead of a fixed 7px.

Signed-off-by: Moritz Stückler <moritz@bitbetter.de>
2026-10-22 12:43:18 +02:00
Moritz Stückler 81d6b1f547 [Web] Clearer captions for domain table columns
Caption the "RL" column "Rate limit" using the existing translated
edit.ratelimit string, and shorten "Max. size of a mailbox" to
"Max. mailbox size" (en-gb only; the key is only used for this column).

Signed-off-by: Moritz Stückler <moritz@bitbetter.de>
2026-10-22 12:43:18 +02:00
Sebastian Müller 8db3cae826 [Nginx] Send Basic auth challenge for unauthenticated ActiveSync requests
Outlook for iOS/Android connects through Microsoft's sync service, which
sends each EAS command without credentials first and only retries with
Basic auth after a 401 + WWW-Authenticate challenge. SOGo answers these
unauthenticated POSTs (Provision, FolderSync, Sync, ...) with 403, so the
Outlook apps never get past Provision and show an empty mailbox (#5846).

Answer requests without an Authorization header with a Basic challenge
in nginx before they reach SOGo. Clients that send credentials up front
are not affected.
2026-10-20 20:48:30 +02:00
Bilal Zahid d93f32072e fix(web): post WebAuthn TFA enrollment through the JSON API
The forced TFA enrollment form submitted the raw credential JSON in the
'token' field, which set_tfa()'s webauthn case never reads. It expects the
parsed \['registration'] object that only the JSON API builds, so the
form POST inserted a row with an empty keyHandle and a NULL publicKey while
still reporting success. Every WebAuthn 2FA login then failed, because
webauthn-tfa-get-args sent an empty credential id in allowCredentials.

- base.twig: POST the attestation to /api/v1/add/webauthn-tfa-registration
  like the regular WebAuthn modal does
- functions.inc.php: refuse incomplete WebAuthn registrations
- lang.en-gb.json: message for the new guard
2026-10-13 19:25:17 +05:00
Philipp Harms 7b658fa65c [Update] Fix update.sh deleting mailcow's own MASQUERADE rule
The "Checking for conflicting bridges" step extracts the bridge name with
`cut -d':' -f2`, which yields " br-mailcow" (leading space). Since the
variable got quoted in `grep -v "${MAILCOW_BRIDGE}"`, the leading space is
significant and never matches "!br-mailcow" in the `iptables -L` output, so
the rule `-s <IPV4_NETWORK>.0/24 ! -o br-mailcow -j MASQUERADE` itself gets
deleted. The mailcow network loses outbound NAT, unbound turns unhealthy and
dependent containers stay in "Created" until Docker is restarted.

Strip whitespace/quotes from the bridge name, match the interface as a whole
token, and skip the cleanup if the bridge name could not be determined.
2026-10-02 18:53:12 +02:00
Joris Le BlanschandClaude Opus 5.5 9f934af3f2 [Rspamd] write full DBL rule when SPAMHAUS_DQS_KEY is set
The generated "DBL" block only set rbl and disable_monitoring, assuming
it would merge into the stock rule. It is included with override=true,
so it replaced the stock rule instead: DBL lost all its checks (urls,
emails, dkim, helo, rdns, replyto), no_ip, selectors and returncodes,
and rspamd fell back to querying the client IP against DBL.

Write the full stock rule pointed at the DQS zone, like the ZRD block.

Fixes #7485

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:20:23 +02:00
133 changed files with 18943 additions and 10928 deletions

No files matched your search

+46 -21
View File
@@ -3,6 +3,11 @@
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! # THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! # DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
# Global IPv6 address that is no unique local address (fc00::/7, e.g. Docker bridges)
_has_public_ipv6() {
ip -6 -o addr show scope global 2>/dev/null | awk '{print $4}' | grep -qviE '^f[cd]'
}
# 1) Check if the host supports IPv6 # 1) Check if the host supports IPv6
get_ipv6_support() { get_ipv6_support() {
# ---- helper: probe external IPv6 connectivity without DNS ---- # ---- helper: probe external IPv6 connectivity without DNS ----
@@ -37,6 +42,9 @@ get_ipv6_support() {
if _probe_ipv6_connectivity; then if _probe_ipv6_connectivity; then
DETECTED_IPV6=true DETECTED_IPV6=true
echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}" echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
elif _has_public_ipv6; then
DETECTED_IPV6=true
echo -e "${YELLOW}External IPv6 connectivity test failed, but the host has a public IPv6 address – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
else else
DETECTED_IPV6=false DETECTED_IPV6=false
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
@@ -192,9 +200,31 @@ EOF
fi fi
} }
_set_enable_ipv6() {
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i "s/^ENABLE_IPV6=.*/ENABLE_IPV6=$1/" "$MAILCOW_CONF"
else
echo "ENABLE_IPV6=$1" >> "$MAILCOW_CONF"
fi
else
export IPV6_BOOL=$1
fi
}
_warn_ipv6_disabled() {
_has_public_ipv6 || return 0
ip -6 route show default 2>/dev/null | grep -qE '^default' || return 0
grep -Eq '"userland-proxy"[[:space:]]*:[[:space:]]*false' /etc/docker/daemon.json 2>/dev/null && return 0
echo -e "${LIGHT_RED}WARNING: ENABLE_IPV6=false, but this host has a public IPv6 address.${NC}"
echo -e "${YELLOW}Docker forwards IPv6 connections to mailcow with an internal source address, which Postfix trusts. This can make your server an open relay over IPv6.${NC}"
echo -e "${YELLOW}Set ENABLE_IPV6=true in mailcow.conf, or set \"userland-proxy\": false in /etc/docker/daemon.json and restart Docker.${NC}"
echo -e "${YELLOW}See https://docs.mailcow.email/post_installation/firststeps-disable_ipv6/${NC}"
sleep 5
}
# 3) Main wrapper for generate_config.sh and update.sh # 3) Main wrapper for generate_config.sh and update.sh
configure_ipv6() { configure_ipv6() {
# detect manual override if mailcow.conf is present
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2) MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
@@ -203,19 +233,23 @@ configure_ipv6() {
MANUAL_SETTING="" MANUAL_SETTING=""
fi fi
if [[ "${MANUAL_SETTING,,}" == "false" ]]; then
_set_enable_ipv6 false
echo "ENABLE_IPV6=false is set, skipping Docker IPv6 configuration."
_warn_ipv6_disabled
echo "IPv6 configuration complete: ENABLE_IPV6=false"
return
fi
get_ipv6_support get_ipv6_support
# if user manually set it, check for mismatch if [[ "$DETECTED_IPV6" != "true" && "${MANUAL_SETTING,,}" == "true" ]] && _has_public_ipv6; then
echo -e "${YELLOW}Keeping ENABLE_IPV6=true, the host has a public IPv6 address.${NC}"
DETECTED_IPV6=true
fi
if [[ "$DETECTED_IPV6" != "true" ]]; then if [[ "$DETECTED_IPV6" != "true" ]]; then
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then _set_enable_ipv6 false
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
else
echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
fi
else
export IPV6_BOOL=false
fi
echo "Skipping Docker IPv6 configuration because host does not support IPv6." echo "Skipping Docker IPv6 configuration because host does not support IPv6."
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6." echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
echo "IPv6 configuration complete: ENABLE_IPV6=false" echo "IPv6 configuration complete: ENABLE_IPV6=false"
@@ -224,16 +258,7 @@ configure_ipv6() {
fi fi
docker_daemon_edit docker_daemon_edit
_set_enable_ipv6 true
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
else
echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
fi
else
export IPV6_BOOL=true
fi
echo "IPv6 configuration complete: ENABLE_IPV6=true" echo "IPv6 configuration complete: ENABLE_IPV6=true"
} }
@@ -586,8 +586,8 @@ class DockerApi:
if not user or id in ('user=', 'group='): if not user or id in ('user=', 'group='):
return Response(content=json.dumps({ 'type': 'danger', 'msg': 'user and id required' }, indent=4), media_type="application/json") return Response(content=json.dumps({ 'type': 'danger', 'msg': 'user and id required' }, indent=4), media_type="application/json")
action = 'doveadm acl set -u "$ACL_USER" -- "$folder" "$ACL_ID" $ACL_RIGHTS' if rights else 'doveadm acl delete -u "$ACL_USER" -- "$folder" "$ACL_ID"' action = 'doveadm acl set -u "$ACL_USER" -- "$folder" "$ACL_ID" $ACL_RIGHTS' if rights else 'doveadm acl delete -u "$ACL_USER" -- "$folder" "$ACL_ID"'
script = ('set -o pipefail; doveadm mailbox list -u "$ACL_USER" | while IFS= read -r folder; do ' script = ('set -o pipefail; doveadm -f tab mailbox status -u "$ACL_USER" guid "*" | tail -n +2 | cut -f1 | while IFS= read -r folder; do '
'case "$folder" in Shared/*) continue;; esac; ' + action + ' || exit 1; done && doveadm acl recalc -u "$ACL_USER"') 'case "$folder" in Shared|Shared/*) continue;; esac; ' + action + ' || exit 1; done && doveadm acl recalc -u "$ACL_USER"')
acl_return = container.exec_run(["/bin/bash", "-c", script], acl_return = container.exec_run(["/bin/bash", "-c", script],
environment={ 'ACL_USER': user, 'ACL_ID': id, 'ACL_RIGHTS': ' '.join(rights) }) environment={ 'ACL_USER': user, 'ACL_ID': id, 'ACL_RIGHTS': ' '.join(rights) })
return self.exec_run_handler('generic', acl_return) return self.exec_run_handler('generic', acl_return)
+1 -1
View File
@@ -4,7 +4,7 @@ catch_non_zero() {
${CMD} > /dev/null ${CMD} > /dev/null
EC=$? EC=$?
if [ ${EC} -ne 0 ]; then if [ ${EC} -ne 0 ]; then
echo "Command ${CMD} failed to execute, exit code was ${EC}" echo "Command ${CMD//${REDISPASS}/***} failed to execute, exit code was ${EC}"
fi fi
} }
source /source_env.sh source /source_env.sh
+2 -1
View File
@@ -3,7 +3,8 @@ LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
ENV PIP_BREAK_SYSTEM_PACKAGES=1 ENV PIP_BREAK_SYSTEM_PACKAGES=1
RUN apk add --no-cache nginx \ RUN apk upgrade --no-cache && \
apk add --no-cache nginx \
python3 \ python3 \
py3-pip && \ py3-pip && \
pip install --upgrade pip && \ pip install --upgrade pip && \
+1 -1
View File
@@ -4,7 +4,7 @@ WORKDIR /src
ENV CGO_ENABLED=0 \ ENV CGO_ENABLED=0 \
GO111MODULE=on \ GO111MODULE=on \
NOOPT=1 \ NOOPT=1 \
VERSION=1.14.0 VERSION=1.14.5
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \ RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
cd /src/postfix-tlspol && \ cd /src/postfix-tlspol && \
-3
View File
@@ -26,7 +26,6 @@ RUN groupadd -g 102 postfix \
postfix \ postfix \
postfix-mysql \ postfix-mysql \
postfix-pcre \ postfix-pcre \
python3 \
redis-tools \ redis-tools \
sasl2-bin \ sasl2-bin \
sudo \ sudo \
@@ -47,7 +46,6 @@ COPY postfix.sh /opt/postfix.sh
COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham
COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam
COPY whitelist_forwardinghosts.sh /usr/local/bin/whitelist_forwardinghosts.sh COPY whitelist_forwardinghosts.sh /usr/local/bin/whitelist_forwardinghosts.sh
COPY mailcow-policy.py /usr/local/bin/mailcow-policy.py
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
COPY docker-entrypoint.sh /docker-entrypoint.sh COPY docker-entrypoint.sh /docker-entrypoint.sh
@@ -55,7 +53,6 @@ RUN chmod +x /opt/postfix.sh \
/usr/local/bin/rspamd-pipe-ham \ /usr/local/bin/rspamd-pipe-ham \
/usr/local/bin/rspamd-pipe-spam \ /usr/local/bin/rspamd-pipe-spam \
/usr/local/bin/whitelist_forwardinghosts.sh \ /usr/local/bin/whitelist_forwardinghosts.sh \
/usr/local/bin/mailcow-policy.py \
/usr/local/sbin/stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
RUN rm -rf /tmp/* /var/tmp/* RUN rm -rf /tmp/* /var/tmp/*
@@ -1,54 +0,0 @@
#!/usr/bin/python3
# Policy service of the mail policies (mailcow-policy in master.cf, spawned by Postfix): reads the policy requests
# of smtpd and asks the mailcow UI for the action (nginx:8081/mailpolicy.php). Without an answer the recipient is
# deferred; smtpd only asks while policies or internal aliases exist (mysql_mail_policy_maps.cf).
import http.client
import re
import socket
import sys
from urllib.parse import urlencode
# one line: DUNNO or a rejection with code and text
valid_action = re.compile(rb'DUNNO|[45]\d\d [45]\.\d{1,3}\.\d{1,3} [\x20-\x7e]{1,200}')
unavailable = b'DEFER_IF_PERMIT Mail policy check temporarily unavailable'
# address of nginx, resolved once per process (a lookup costs more than the check)
nginx = None
def check(request):
global nginx
body = urlencode(sorted(request.items())).encode()
try:
if nginx is None:
nginx = socket.gethostbyname('nginx')
# http.client sets TCP_NODELAY, so the body is not held back until the headers are acknowledged
# below the timeout of Postfix (main.cf), so an answer always arrives
conn = http.client.HTTPConnection(nginx, 8081, timeout=8)
try:
conn.request('POST', '/mailpolicy.php', body, {'Host': 'nginx', 'Content-Type': 'application/x-www-form-urlencoded'})
response = conn.getresponse()
action = response.read()
finally:
conn.close()
except (OSError, http.client.HTTPException):
# nginx may have a new address after a restart
nginx = None
return unavailable
return action if response.status == 200 and valid_action.fullmatch(action) else unavailable
def main():
request = {}
# attributes as bytes: addresses may contain any 8-bit characters
for line in sys.stdin.buffer:
line = line.rstrip(b'\r\n')
if line:
name, separator, value = line.partition(b'=')
if separator:
request[name] = value
continue
sys.stdout.buffer.write(b'action=' + check(request) + b'\n\n')
sys.stdout.buffer.flush()
request = {}
if __name__ == '__main__':
main()
-28
View File
@@ -15,21 +15,6 @@ until dig +short mailcow.email > /dev/null; do
sleep 1 sleep 1
done 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 cat <<EOF > /etc/aliases
# Autogenerated by mailcow # Autogenerated by mailcow
null: /dev/null null: /dev/null
@@ -239,19 +224,6 @@ query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM transports
LIMIT 1; LIMIT 1;
EOF EOF
# Mail policies: while policies or internal aliases exist, smtpd asks the policy service (mailcow_mail_policy in main.cf)
cat <<EOF > /opt/postfix/conf/sql/mysql_mail_policy_maps.cf
# Autogenerated by mailcow
user = ${DBUSER}
password = ${DBPASS}
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT 'mailcow_mail_policy' FROM DUAL WHERE '%s' != ''
AND (EXISTS (SELECT 1 FROM mail_policy_member JOIN mail_policy ON mail_policy.id = mail_policy_member.policy_id
WHERE mail_policy.active = '1')
OR EXISTS (SELECT 1 FROM alias WHERE internal = '1' AND active IN ('1', '2')));
EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf
# Autogenerated by mailcow # Autogenerated by mailcow
user = ${DBUSER} user = ${DBUSER}
+26 -1
View File
@@ -277,9 +277,34 @@ if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
} }
} }
"DBL" { "DBL" {
# override the defaults for DBL defined in modules.d/rbl.conf # full copy of the DBL rule from modules.d/rbl.conf, pointed at DQS:
# the override include replaces the stock rule instead of merging into it
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
disable_monitoring = true; disable_monitoring = true;
checks = ["emails", "dkim", "helo", "rdns", "replyto", "urls", "content_urls"];
no_ip = true;
ignore_defaults = true;
exclude_users = false;
emails_domainonly = true;
selector = {
from_smtp = "from('smtp'):domain";
from_mime = "from('mime'):domain";
mid = "header(Message-Id).regexp('@([^\.]+\.[^>]+)').last";
}
returncodes = {
DBL_SPAM = "127.0.1.2";
DBL_PHISH = "127.0.1.4";
DBL_MALWARE = "127.0.1.5";
DBL_BOTNET = "127.0.1.6";
DBL_ABUSE = "127.0.1.102";
DBL_ABUSE_REDIR = "127.0.1.103";
DBL_ABUSE_PHISH = "127.0.1.104";
DBL_ABUSE_MALWARE = "127.0.1.105";
DBL_ABUSE_BOTNET = "127.0.1.106";
DBL_PROHIBIT = "127.0.1.255";
DBL_BLOCKED_OPENRESOLVER = "127.255.255.254";
DBL_BLOCKED = "127.255.255.255";
}
} }
"ZRD" { "ZRD" {
ignore_defaults = true; ignore_defaults = true;
+2 -2
View File
@@ -116,8 +116,8 @@ if ($result === false){
exit; exit;
} }
if ($result) { if ($result) {
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $client_ip); error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $service . " from IP " . $client_ip);
set_sasl_log($post['username'], $client_ip, $post['service']); set_sasl_log($post['username'], $client_ip, $service);
} }
} }
+1 -4
View File
@@ -199,9 +199,6 @@ plugin {
sieve_plugins = sieve_imapsieve sieve_extprograms sieve_plugins = sieve_imapsieve sieve_extprograms
sieve_vacation_send_from_recipient = yes sieve_vacation_send_from_recipient = yes
sieve_redirect_envelope_from = recipient sieve_redirect_envelope_from = recipient
# Notifications are sent with the address of the mailbox (not :from or postmaster), so mail policies apply;
# the old notify extension always sends as postmaster and is on by default, so sieve_extensions disables it
sieve_notify_mailto_envelope_from = recipient
# From elsewhere to Spam folder # From elsewhere to Spam folder
imapsieve_mailbox1_name = Junk imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY imapsieve_mailbox1_causes = COPY
@@ -218,7 +215,7 @@ plugin {
quota_warning2 = storage=80%% quota-warning 80 %u quota_warning2 = storage=80%% quota-warning 80 %u
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve sieve_pipe_bin_dir = /usr/lib/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
sieve_extensions = -notify +imapflags +vacation-seconds +editheader sieve_extensions = +notify +imapflags +vacation-seconds +editheader
sieve_max_script_size = 1M sieve_max_script_size = 1M
sieve_max_redirects = 100 sieve_max_redirects = 100
sieve_max_actions = 101 sieve_max_actions = 101
@@ -94,11 +94,11 @@ location / {
} }
location /qhandler { location /qhandler {
rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2; rewrite ^/qhandler/([^/]+)/(.+?)/?$ /qhandler.php?action=$1&hash=$2;
} }
location /edit { location /edit {
rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2; rewrite ^/edit/([^/]+)/(.+?)/?$ /edit.php?$1=$2;
} }
location ~ ^/api/v1/(.*)$ { location ~ ^/api/v1/(.*)$ {
@@ -187,6 +187,12 @@ location /sogo-auth-verify {
} }
location ^~ /Microsoft-Server-ActiveSync { location ^~ /Microsoft-Server-ActiveSync {
# Outlook for iOS/Android (Microsoft's sync service) sends EAS commands without credentials
# and only authenticates after a 401 Basic challenge. SOGo answers those with 403, so challenge here.
if ($http_authorization = "") {
add_header WWW-Authenticate 'Basic realm="SOGo"' always;
return 401;
}
auth_request /sogo-auth-verify; auth_request /sogo-auth-verify;
auth_request_set $user $upstream_http_x_user; auth_request_set $user $upstream_http_x_user;
auth_request_set $auth $upstream_http_x_auth; auth_request_set $auth $upstream_http_x_auth;
-8
View File
@@ -50,7 +50,6 @@ postscreen_greet_wait = 3s
postscreen_non_smtp_command_enable = no postscreen_non_smtp_command_enable = no
postscreen_pipelining_enable = no postscreen_pipelining_enable = no
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf, proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_mail_policy_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
$sender_dependent_default_transport_maps, $sender_dependent_default_transport_maps,
@@ -91,7 +90,6 @@ smtpd_hard_error_limit = ${stress?1}${stress:5}
smtpd_helo_required = yes smtpd_helo_required = yes
smtpd_proxy_timeout = 600s smtpd_proxy_timeout = 600s
smtpd_recipient_restrictions = check_recipient_mx_access proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf, smtpd_recipient_restrictions = check_recipient_mx_access proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_mail_policy_maps.cf,
permit_sasl_authenticated, permit_sasl_authenticated,
permit_mynetworks, permit_mynetworks,
check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
@@ -172,11 +170,5 @@ parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks
# This Option is added to correctly set the X-Original-To Header when mails are send to lmtp (dovecot) # This Option is added to correctly set the X-Original-To Header when mails are send to lmtp (dovecot)
lmtp_destination_recipient_limit=1 lmtp_destination_recipient_limit=1
# Mail policies: while policies or internal aliases exist, mysql_mail_policy_maps.cf returns this class and smtpd
# asks the policy service for every recipient (mailcow-policy in master.cf); no answer defers the recipient
smtpd_restriction_classes = mailcow_mail_policy
mailcow_mail_policy = check_policy_service { unix:private/mailcow-policy, timeout=10s, try_limit=1, default_action=DEFER_IF_PERMIT }
mailcow-policy_time_limit = 3600s
# DO NOT EDIT ANYTHING BELOW # # DO NOT EDIT ANYTHING BELOW #
# Overrides # # Overrides #
+1 -5
View File
@@ -117,14 +117,10 @@ smtp_sender_cleanup unix n - y - 0 cleanup
127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh 127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh
# end whitelist_fwd # end whitelist_fwd
# mail policies: policy service of smtpd, asks the mailcow UI (mailcow_mail_policy in main.cf)
mailcow-policy unix - n n - 0 spawn user=nobody argv=/usr/local/bin/mailcow-policy.py
# start watchdog-specific # start watchdog-specific
# logs to local7 (hidden), no mail policy check # logs to local7 (hidden)
589 inet n - n - - smtpd 589 inet n - n - - smtpd
-o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o syslog_name=watchdog -o syslog_name=watchdog
-o syslog_facility=local7 -o syslog_facility=local7
-o smtpd_milters= -o smtpd_milters=
-39
View File
@@ -1,39 +0,0 @@
<?php
// Mail policy check for one recipient, asked by the Postfix policy service (mailcow-policy in master.cf)
// Request: POST with the attributes of the Postfix policy request; answer: the action (DUNNO or the rejection)
header('Content-Type: text/plain');
require_once "vars.inc.php";
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
// Do not show errors, we log to using error_log
ini_set('error_reporting', 0);
// Init database
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
}
catch (PDOException $e) {
error_log("MAILPOLICY: " . $e . PHP_EOL);
http_response_code(501);
exit;
}
if (!isset($_POST['request']) || $_POST['request'] !== 'smtpd_access_policy') {
http_response_code(400);
exit;
}
$value = fn(string $key): string => is_string($_POST[$key] ?? null) ? $_POST[$key] : '';
try {
$policy = \Mailcow\MailPolicy\MailPolicyModule::create($pdo, \Mailcow\Core\Context::system());
echo $policy->api->check($value('sender'), $value('recipient'), $value('sasl_username'), (int)$value('server_port'), $value('helo_name'));
}
catch (Throwable $e) {
// the policy service answers with a temporary error
error_log("MAILPOLICY: " . $e->getMessage() . PHP_EOL);
http_response_code(500);
}
+30
View File
@@ -469,5 +469,35 @@ while ($row = array_shift($rows)) {
} }
?> ?>
<?php
// Start internal aliases
$stmt = $pdo->query("SELECT `id`, `address`, `domain` FROM `alias` WHERE `active` = '1' AND `internal` = '1'");
$aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($alias = array_shift($aliases)) {
// build allowed_domains regex and add target domain and alias domains
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `active` = '1' AND `target_domain` = :target_domain");
$stmt->execute(array(':target_domain' => $alias['domain']));
$allowed_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
$allowed_domains = array_map(function($item) {
return str_replace('.', '\.', $item['alias_domain']);
}, $allowed_domains);
$allowed_domains[] = str_replace('.', '\.', $alias['domain']);
$allowed_domains = implode('|', $allowed_domains);
?>
internal_alias_<?=$alias['id'];?> {
priority = 10;
rcpt = "<?=$alias['address'];?>";
from = "/^((?!.*@(<?=$allowed_domains;?>)).)*$/";
apply "default" {
MAILCOW_INTERNAL_ALIAS = 9999.0;
}
symbols [
"MAILCOW_INTERNAL_ALIAS"
]
}
<?php
}
?>
} }
-4
View File
@@ -1,7 +1,3 @@
MX_IMPLICIT {
expression = "MX_GOOD & MX_MISSING";
score = -0.01;
}
VIRUS_FOUND { VIRUS_FOUND {
expression = "CLAM_VIRUS & !MAILCOW_WHITE"; expression = "CLAM_VIRUS & !MAILCOW_WHITE";
score = 2000.0; score = 2000.0;
-18
View File
@@ -23,24 +23,6 @@ symbols {
} }
} }
group "MX" {
"MX_INVALID" {
score = 0.5;
description = "No connectable MX";
one_shot = true;
}
"MX_MISSING" {
score = 2.0;
description = "No MX record";
one_shot = true;
}
"MX_GOOD" {
score = -0.01;
description = "MX was ok";
one_shot = true;
}
}
group "reputation" { group "reputation" {
symbols = { symbols = {
"IP_REPUTATION_HAM" { "IP_REPUTATION_HAM" {
-20
View File
@@ -5,12 +5,6 @@ RCPT_MAILCOW_DOMAIN {
symbols_set = ["RCPT_MAILCOW_DOMAIN"]; symbols_set = ["RCPT_MAILCOW_DOMAIN"];
} }
WHITELISTED_FWD_HOST {
type = "ip";
map = "redis://WHITELISTED_FWD_HOST";
symbols_set = ["WHITELISTED_FWD_HOST"];
}
BULK_HEADER { BULK_HEADER {
type = "content"; type = "content";
map = "${LOCAL_CONFDIR}/custom/bulk_header.map"; map = "${LOCAL_CONFDIR}/custom/bulk_header.map";
@@ -151,20 +145,6 @@ BAD_LANG {
score = 5.0; score = 5.0;
} }
BAZAAR_ABUSE_CH {
type = "selector";
selector = "attachments(hex,md5)";
map = "https://bazaar.abuse.ch/export/txt/md5/recent/";
score = 10.0;
}
URLHAUS_ABUSE_CH {
type = "selector";
selector = "urls";
map = "https://urlhaus.abuse.ch/downloads/text_online/";
score = 10.0;
}
SMTP_LIMITED_ACCESS { SMTP_LIMITED_ACCESS {
type = "user"; type = "user";
map = "redis://SMTP_LIMITED_ACCESS"; map = "redis://SMTP_LIMITED_ACCESS";
-1
View File
@@ -1,6 +1,5 @@
timeout = 8.0; timeout = 8.0;
symbol_bad_mx = "MX_INVALID"; symbol_bad_mx = "MX_INVALID";
symbol_no_mx = "MX_MISSING";
symbol_good_mx = "MX_GOOD"; symbol_good_mx = "MX_GOOD";
expire = 86400; expire = 86400;
key_prefix = "rmx"; key_prefix = "rmx";
+2
View File
@@ -9,3 +9,5 @@ disable_monitoring = true;
# instead of silently accepting the message without further processing. # instead of silently accepting the message without further processing.
soft_reject_on_timeout = true; soft_reject_on_timeout = true;
local_addrs = /etc/rspamd/custom/mailcow_networks.map; local_addrs = /etc/rspamd/custom/mailcow_networks.map;
# Must not be lower than message_size_limit in postfix main.cf
max_message = 134217728;
+63 -6
View File
@@ -362,6 +362,61 @@ rspamd_config:register_symbol({
priority = 19 priority = 19
}) })
rspamd_config:register_symbol({
name = 'WHITELISTED_FWD_HOST',
type = 'prefilter',
score = 0.0,
callback = function(task)
local rspamd_logger = require "rspamd_logger"
local redis_params = rspamd_parse_redis_server('fwd_host')
local ip = task:get_from_ip()
if ip == nil or not ip:is_valid() then
return false
end
local from_ip_string = tostring(ip)
local ip_check_table = {'WHITELISTED_FWD_HOST', from_ip_string}
local maxbits = 128
local minbits = 7
if ip:get_version() == 4 then
maxbits = 32
minbits = 8
end
for i=maxbits,minbits,-1 do
table.insert(ip_check_table, ip:apply_mask(i):to_string() .. "/" .. i)
end
local function fwd_host_cb(err, data)
if err or type(data) ~= 'table' then
rspamd_logger.infox(rspamd_config, "fwd_host query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
return
end
for _,v in pairs(data) do
if type(v) == 'string' then
task:insert_result('WHITELISTED_FWD_HOST', 1.0, from_ip_string)
task:set_flag('pass_all')
return
end
end
end
local redis_ret = rspamd_redis_make_request(task,
redis_params, -- connect params
'WHITELISTED_FWD_HOST', -- hash key
false, -- is write
fwd_host_cb, --callback
'HMGET', -- command
ip_check_table -- arguments
)
if not redis_ret then
rspamd_logger.infox(rspamd_config, "cannot check fwd_host redis map")
end
end,
priority = 18
})
rspamd_config:register_symbol({ rspamd_config:register_symbol({
name = 'TLS_HEADER', name = 'TLS_HEADER',
type = 'postfilter', type = 'postfilter',
@@ -390,9 +445,8 @@ rspamd_config:register_symbol({
local rspamd_logger = require "rspamd_logger" local rspamd_logger = require "rspamd_logger"
local redis_params = rspamd_parse_redis_server('taghandler') local redis_params = rspamd_parse_redis_server('taghandler')
local rspamd_http = require "rspamd_http" local rspamd_http = require "rspamd_http"
local rcpts = task:get_recipients('smtp') local rcpts = task:get_recipients({'smtp', 'orig'})
local lua_util = require "lua_util" local lua_util = require "lua_util"
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
local function remove_moo_tag() local function remove_moo_tag()
local moo_tag_header = task:get_header('X-Moo-Tag', false) local moo_tag_header = task:get_header('X-Moo-Tag', false)
@@ -417,8 +471,9 @@ rspamd_config:register_symbol({
-- Check if recipient has a tag (contains '+') -- Check if recipient has a tag (contains '+')
local tag = nil local tag = nil
if tagged_rcpt ~= nil then local base_user, tag_part = rcpt_user:match('^([^%+]+)%+(.+)$')
tag = tagged_rcpt if base_user and tag_part then
tag = tag_part
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag) rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
end end
@@ -498,8 +553,7 @@ rspamd_config:register_symbol({
else else
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail") rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
local sbj = task:get_header('Subject') or '' local sbj = task:get_header('Subject') or ''
local tag_value = tag[1] and tag[1].options and tag[1].options[1] or '' new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag_value .. '] ' .. sbj)) .. '?='
task:set_milter_reply({ task:set_milter_reply({
remove_headers = { remove_headers = {
['Subject'] = 1, ['Subject'] = 1,
@@ -907,6 +961,9 @@ rspamd_config:register_symbol({
if not seen_cte and rewrite.need_rewrite_ct then if not seen_cte and rewrite.need_rewrite_ct then
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', new_cte) out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', new_cte)
task:set_milter_reply({
add_headers = {['Content-Transfer-Encoding'] = new_cte}
})
end end
-- End of headers -- End of headers
+1 -1
View File
@@ -109,7 +109,7 @@ foreach ($rcpts as $rcpt) {
} }
// Skip if domain is excluded // Skip if domain is excluded
if (in_array($parsed_rcpt['domain'], $exclude_domains)) { if (is_array($exclude_domains) && in_array($parsed_rcpt['domain'], $exclude_domains)) {
error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL); error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL);
continue; continue;
} }
-2
View File
@@ -11,7 +11,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$js_minifier->add('/web/js/site/imapsync.js'); $js_minifier->add('/web/js/site/imapsync.js');
$js_minifier->add('/web/js/site/groups.js'); $js_minifier->add('/web/js/site/groups.js');
$js_minifier->add('/web/js/site/mail-policy.js');
$js_minifier->add('/web/js/site/mailbox.js'); $js_minifier->add('/web/js/site/mailbox.js');
$js_minifier->add('/web/js/presets/sieveMailbox.js'); $js_minifier->add('/web/js/presets/sieveMailbox.js');
$js_minifier->add('/web/js/site/pwgen.js'); $js_minifier->add('/web/js/site/pwgen.js');
@@ -47,7 +46,6 @@ $template_data = [
'lang_edit' => json_encode($lang['edit']), 'lang_edit' => json_encode($lang['edit']),
'lang_datatables' => json_encode($lang['datatables']), 'lang_datatables' => json_encode($lang['datatables']),
'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(), 'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(),
'mail_policy_groups' => \Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->policies->groupChoices($domains),
]; ];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
+16
View File
@@ -14,3 +14,19 @@ body {
margin: 0; margin: 0;
background: #fafafa; background: #fafafa;
} }
/* mailcow: inline code in descriptions in the text colour instead of bold purple */
.swagger-ui .markdown code,
.swagger-ui .renderedMarkdown code {
color: inherit;
font-weight: normal;
font-size: 0.9em;
background: rgba(0, 0, 0, 0.04);
padding: 0 3px;
}
html.dark-mode .swagger-ui .markdown code,
html.dark-mode .swagger-ui .renderedMarkdown code {
color: inherit;
background: rgba(255, 255, 255, 0.08);
}
+1 -74
View File
@@ -1,79 +1,6 @@
<!doctype html> <!doctype html>
<html lang="en-US"> <html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body> <body>
<script> <script src="oauth2-redirect.js"></script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body> </body>
</html> </html>
+1
View File
@@ -0,0 +1 @@
"use strict";function run(){var e,r,t,a=window.opener.swaggerUIRedirectOauth2,o=a.state,n=a.redirectUrl;if((t=(r=/code|token|error/.test(window.location.hash)?window.location.hash.substring(1).replace("?","&"):location.search.substring(1)).split("&")).forEach((function(e,r,t){t[r]='"'+e.replace("=",'":"')+'"'})),e=(r=r?JSON.parse("{"+t.join()+"}",(function(e,r){return""===e?r:decodeURIComponent(r)})):{}).state===o,"accessCode"!==a.auth.schema.get("flow")&&"authorizationCode"!==a.auth.schema.get("flow")&&"authorization_code"!==a.auth.schema.get("flow")||a.auth.code)a.callback({auth:a.auth,token:r,isValid:e,redirectUrl:n});else if(e||a.errCb({authId:a.auth.name,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),r.code)delete a.state,a.auth.code=r.code,a.callback({auth:a.auth,redirectUrl:n});else{let e;r.error&&(e="["+r.error+"]: "+(r.error_description?r.error_description+". ":"no accessCode received from the server. ")+(r.error_uri?"More info: "+r.error_uri:"")),a.errCb({authId:a.auth.name,source:"auth",level:"error",message:e||"[Authorization failed]: no accessCode received from the server"})}window.close()}"loading"!==document.readyState?run():document.addEventListener("DOMContentLoaded",(function(){run()}));
+13966 -7868
View File
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -1,3 +1,2 @@
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ /*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(()=>(()=>{var e={17967:(e,t)=>{"use strict";t.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,o=/&(newline|tab);/gi,s=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i=/^.+(:|&colon;)/gim,a=[".","/"];t.N=function(e){var t,l=(t=e||"",t.replace(r,(function(e,t){return String.fromCharCode(t)}))).replace(o,"").replace(s,"").trim();if(!l)return"about:blank";if(function(e){return a.indexOf(e[0])>-1}(l))return l;var c=l.match(i);if(!c)return l;var u=c[0];return n.test(u)?"about:blank":l}},53795:(e,t,n)=>{"use strict";n.d(t,{Z:()=>P});var r=n(23101),o=n.n(r),s=n(61125),i=n.n(s),a=n(11882),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294),h=n(43393);function f(e){return f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},f(e)}function d(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function m(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function g(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function y(e){return y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},y(e)}function v(e,t){return v=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},v(e,t)}function b(e,t){return!t||"object"!=typeof t&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}var w={};function E(e,t,n){return function(e){return null==e}(e)?n:function(e){return null!==e&&"object"===f(e)&&"function"==typeof e.get&&"function"==typeof e.has}(e)?e.has(t)?e.get(t):n:hasOwnProperty.call(e,t)?e[t]:n}function x(e,t,n){for(var r=0;r!==t.length;)if((e=E(e,t[r++],w))===w)return n;return e}function S(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=function(e,t){return function(n){if("string"==typeof n)return(0,h.is)(t[n],e[n]);if(Array.isArray(n))return(0,h.is)(x(t,n),x(e,n));throw new TypeError("Invalid key: expected Array or string: "+n)}}(t,n),o=e||Object.keys(function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?g(n,!0).forEach((function(t){m(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):g(n).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},n,{},t));return o.every(r)}const _=function(e){function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),b(this,y(t).apply(this,arguments))}var n,r,o;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&v(e,t)}(t,e),n=t,r=[{key:"shouldComponentUpdate",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return!S(this.updateOnProps,this.props,e,"updateOnProps")||!S(this.updateOnStates,this.state,t,"updateOnStates")}}],r&&d(n.prototype,r),o&&d(n,o),t}(p.Component);var j=n(23930),O=n.n(j),k=n(45697),A=n.n(k);const C=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class P extends _{constructor(){super(...arguments),i()(this,"getModelName",(e=>-1!==l()(e).call(e,"#/definitions/")?C(e.replace(/^.*#\/definitions\//,"")):-1!==l()(e).call(e,"#/components/schemas/")?C(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),i()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:r,schema:s,required:i,name:a,isRef:l,specPath:c,displayName:u,includeReadOnly:h,includeWriteOnly:f}=this.props;const d=e("ObjectModel"),m=e("ArrayModel"),g=e("PrimitiveModel");let y="object",v=s&&s.get("$$ref");if(!a&&v&&(a=this.getModelName(v)),!s&&v&&(s=this.getRefSchema(a)),!s)return p.createElement("span",{className:"model model-title"},p.createElement("span",{className:"model-title__text"},u||a),p.createElement("img",{src:n(2517),height:"20px",width:"20px"}));const b=r.isOAS3()&&s.get("deprecated");switch(l=void 0!==l?l:!!v,y=s&&s.get("type")||y,y){case"object":return p.createElement(d,o()({className:"object"},this.props,{specPath:c,getConfigs:t,scLine truncated !function webpackUniversalModuleDefinition(s,o){"object"==typeof exports&&"object"==typeof module?module.exports=o():"function"==typeof define&&define.amd?define([],o):"object"==typeof exports?exports.SwaggerUIBundle=o():s.SwaggerUIBundle=o()}(this,(()=>(()=>{var s={67526(s,o){"use strict";o.byteLength=function byteLength(s){var o=getLens(s),i=o[0],a=o[1];return 3*(i+a)/4-a},o.toByteArray=function toByteArray(s){var o,i,_=getLens(s),w=_[0],x=_[1],C=new u(function _byteLength(s,o,i){return 3*(o+i)/4-i}(0,w,x)),j=0,L=x>0?w-4:w;for(i=0;i<L;i+=4)o=a[s.charCodeAt(i)]<<18|a[s.charCodeAt(i+1)]<<12|a[s.charCodeAt(i+2)]<<6|a[s.charCodeAt(i+3)],C[j++]=o>>16&255,C[j++]=o>>8&255,C[j++]=255&o;2===x&&(o=a[s.charCodeAt(i)]<<2|a[s.charCodeAt(i+1)]>>4,C[j++]=255&o);1===x&&(o=a[s.charCodeAt(i)]<<10|a[s.charCodeAt(i+1)]<<4|a[s.charCodeAt(i+2)]>>2,C[j++]=o>>8&255,C[j++]=255&o);return C},o.fromByteArray=function fromByteArray(s){for(var o,a=s.length,u=a%3,_=[],w=16383,x=0,C=a-u;x<C;x+=w)_.push(encodeChunk(s,x,x+w>C?C:x+w));1===u?(o=s[a-1],_.push(i[o>>2]+i[o<<4&63]+"==")):2===u&&(o=(s[a-2]<<8)+s[a-1],_.push(i[o>>10]+i[o>>4&63]+i[o<<2&63]+"="));return _.join("")};for(var i=[],a=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",w=0;w<64;++w)i[w]=_[w],a[_.charCodeAt(w)]=w;function getLens(s){var o=s.length;if(o%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=s.indexOf("=");return-1===i&&(i=o),[i,i===o?0:4-i%4]}function encodeChunk(s,o,a){for(var u,_,w=[],x=o;x<a;x+=3)u=(s[x]<<16&16711680)+(s[x+1]<<8&65280)+(255&s[x+2]),w.push(i[(_=u)>>18&63]+i[_>>12&63]+i[_>>6&63]+i[63&_]);return w.join("")}a["-".charCodeAt(0)]=62,a["_".charCodeAt(0)]=63},48287(s,o,i){"use strict";const a=i(67526),u=i(251),_="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;o.Buffer=Buffer,o.SlowBuffer=function SlowBuffer(s){+s!=s&&(s=0);return Buffer.alloc(+s)},o.INSPECT_MAX_BYTES=50;const w=2147483647;function createBuffer(s){if(s>w)throw new RangeError('The value "'+s+'" is invalid for option "size"');const o=new Uint8Array(s);return Object.setPrototypeOf(o,Buffer.prototype),o}function Buffer(s,o,i){if("number"==typeof s){if("string"==typeof o)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(s)}return from(s,o,i)}function from(s,o,i){if("string"==typeof s)return function fromString(s,o){"string"==typeof o&&""!==o||(o="utf8");if(!Buffer.isEncoding(o))throw new TypeError("Unknown encoding: "+o);const i=0|byteLength(s,o);let a=createBuffer(i);const u=a.write(s,o);u!==i&&(a=a.slice(0,u));return a}(s,o);if(ArrayBuffer.isView(s))return function fromArrayView(s){if(isInstance(s,Uint8Array)){const o=new Uint8Array(s);return fromArrayBuffer(o.buffer,o.byteOffset,o.byteLength)}return fromArrayLike(s)}(s);if(null==s)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s);if(isInstance(s,ArrayBuffer)||s&&isInstance(s.buffer,ArrayBuffer))return fromArrayBuffer(s,o,i);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(s,SharedArrayBuffer)||s&&isInstance(s.buffer,SharedArrayBuffer)))return fromArrayBuffer(s,o,i);if("number"==typeof s)throw new TypeError('The "value" argument must not be of type number. Received type number');const a=s.valueOf&&s.valueOf();if(null!=a&&a!==s)return Buffer.from(a,o,i);const u=function fromObject(s){if(Buffer.isBuffer(s)){const o=0|checked(s.length),i=createBuffer(o);return 0===i.length||s.copy(i,0,0,o),i}if(void 0!==s.length)return"number"!=typeof s.length||numberIsNaN(s.length)?createBuffer(0):fromArrayLike(s);if("Buffer"===s.type&&Array.isArray(s.data))return fromArrayLike(s.data)}(s);if(u)return u;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof s[Symbol.toPrimitive])return Buffer.from(s[Symbol.toPrimitive]("string"),o,i);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s)}function assertSize(s){if("number"!=typeof s)throw new TypeError('"size" argument must be of type number');if(s<0)throw new RangeError('The value "'+s+'" is invalid for option "size"')}function allocUnsafe(s){return assertSize(s),createBuffer(s<0?0:0|checked(s))}function fromArrayLike(s){const o=s.length<0?0:0|checked(s.length),i=createBuffer(o);for(let a=0;a<o;a+=1)i[a]=255&s[a];return i}function fromArrayBuffer(s,o,i){if(o<0||s.byteLength<o)throw new RangeError('"offset" is outside of buffer bounds');if(s.byteLength<o+(i||0))throw new RangeError('"length" is outside of buffer bounds');let a;return a=void 0===o&&void 0===i?new Uint8Array(s):void 0===i?new Uint8Array(s,o):new Uint8Array(s,o,i),Object.setPrototypeOf(a,Buffer.prototype),a}function checked(s){if(s>=w)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+w.toString(16)+" Line truncated
//# sourceMappingURL=swagger-ui-bundle.js.map
@@ -0,0 +1,131 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* @description Recursive object extending
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
* @license MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*!
* https://github.com/Starcounter-Jack/JSON-Patch
* (c) 2017-2021 Joachim Wester
* MIT license
*/
/*!
* https://github.com/Starcounter-Jack/JSON-Patch
* (c) 2017-2022 Joachim Wester
* MIT licensed
*/
/*!
* repeat-string <https://github.com/jonschlinkert/repeat-string>
*
* Copyright (c) 2014-2015, Jon Schlinkert.
* Licensed under the MIT License.
*/
/*! @license DOMPurify 3.4.17 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.17/LICENSE */
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* @license
* MIT License
*
* Copyright (c) 2014-present, Lee Byron and other contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* use-sync-external-store-with-selector.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","mappings":"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 truncated {"version":3,"file":"swagger-ui-bundle.js","mappings":"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 truncated
+1 -1
View File
@@ -1,3 +1,3 @@
/*! For license information please see swagger-ui-es-bundle-core.js.LICENSE.txt */ /*! For license information please see swagger-ui-es-bundle-core.js.LICENSE.txt */
import*as e from"react-immutable-pure-component";import*as t from"remarkable/linkify";import*as a from"dompurify";import*as n from"zenscroll";import*as r from"lodash/reduce";import*as s from"@babel/runtime-corejs3/core-js-stable/string/raw";import*as o from"@babel/runtime-corejs3/core-js-stable/number/epsilon";import*as l from"@babel/runtime-corejs3/core-js-stable/instance/repeat";import*as i from"@babel/runtime-corejs3/core-js-stable/instance/fill";import*as c from"lodash/zipObject";import*as u from"@babel/runtime-corejs3/core-js-stable/promise";import*as d from"@babel/runtime-corejs3/core-js-stable/date/now";import*as p from"lodash/isString";import*as m from"lodash/debounce";import*as f from"lodash/set";import*as h from"lodash/fp/assocPath";import*as g from"swagger-client/es/resolver/strategies/generic";import*as y from"swagger-client/es/resolver/strategies/openapi-2";import*as v from"swagger-client/es/resolver/strategies/openapi-3-0";import*as E from"swagger-client/es/resolver/strategies/openapi-3-1-apidom";import*as S from"swagger-client/es/resolver";import*as w from"swagger-client/es/execute";import*as b from"swagger-client/es/http";import*as x from"swagger-client/es/subtree-resolver";import*as _ from"react-dom";import*as C from"react-redux";import*as j from"lodash/omit";import*as O from"react-syntax-highlighter/dist/esm/light";import*as N from"react-syntax-highlighter/dist/esm/languages/hljs/javascript";import*as k from"react-syntax-highlighter/dist/esm/languages/hljs/json";import*as A from"react-syntax-highlighter/dist/esm/languages/hljs/xml";import*as I from"react-syntax-highlighter/dist/esm/languages/hljs/bash";import*as R from"react-syntax-highlighter/dist/esm/languages/hljs/yaml";import*as P from"react-syntax-highlighter/dist/esm/languages/hljs/http";import*as T from"react-syntax-highlighter/dist/esm/languages/hljs/powershell";import*as M from"react-syntax-highlighter/dist/esm/styles/hljs/agate";import*as D from"react-syntax-highlighter/dist/esm/styles/hljs/arta";import*as L from"react-syntax-highlighter/dist/esm/styles/hljs/monokai";import*as q from"react-syntax-highlighter/dist/esm/styles/hljs/nord";import*as B from"react-syntax-highlighter/dist/esm/styles/hljs/obsidian";import*as U from"react-syntax-highlighter/dist/esm/styles/hljs/tomorrow-night";import*as J from"@babel/runtime-corejs3/core-js-stable/instance/starts-with";import*as $ from"@braintree/sanitize-url";import*as K from"lodash/camelCase";import*as V from"lodash/upperFirst";import*as F from"lodash/find";import*as z from"lodash/eq";import*as W from"css.escape";import*as H from"@babel/runtime-corejs3/core-js-stable/instance/find-index";import*as G from"@babel/runtime-corejs3/core-js-stable/array/from";import*as Z from"@babel/runtime-corejs3/core-js-stable/array/is-array";import*as Y from"@babel/runtime-corejs3/core-js-stable/instance/bind";import*as X from"@babel/runtime-corejs3/core-js-stable/instance/concat";import*as Q from"@babel/runtime-corejs3/core-js-stable/instance/entries";import*as ee from"@babel/runtime-corejs3/core-js-stable/instance/every";import*as te from"@babel/runtime-corejs3/core-js-stable/instance/filter";import*as ae from"@babel/runtime-corejs3/core-js-stable/instance/find";import*as ne from"@babel/runtime-corejs3/core-js-stable/instance/for-each";import*as re from"@babel/runtime-corejs3/core-js-stable/instance/includes";import*as se from"@babel/runtime-corejs3/core-js-stable/instance/index-of";import*as oe from"@babel/runtime-corejs3/core-js-stable/instance/keys";import*as le from"@babel/runtime-corejs3/core-js-stable/instance/map";import*as ie from"@babel/runtime-corejs3/core-js-stable/instance/reduce";import*as ce from"@babel/runtime-corejs3/core-js-stable/instance/slice";import*as ue from"@babel/runtime-corejs3/core-js-stable/instance/some";import*as de from"@babel/runtime-corejs3/core-js-stable/instance/sort";import*as pe from"@babel/runtime-corejs3/core-js-stable/instance/trim";import*as me from"@babel/runtime-corejs3/core-js-stable/json/stringify";import*as fe from"@babel/runtime-corejs3/core-js-stable/map";import*as he from"@babel/runtime-corejs3/core-js-stable/number/is-integer";import*as ge from"@babel/runtime-corejs3/core-js-stable/object/assign";import*as ye from"@babel/runtime-corejs3/core-js-stable/object/entries";import*as ve from"@babel/runtime-corejs3/core-js-stable/object/from-entries";import*as Ee from"@babel/runtime-corejs3/core-js-stable/object/keys";import*as Se from"@babel/runtime-corejs3/core-js-stable/object/values";import*as we from"@babel/runtime-corejs3/core-js-stable/set";import*as be from"@babel/runtime-corejs3/core-js-stable/set-timeout";import*as xe from"@babel/runtime-corejs3/core-js-stable/url";import*as _e from"@babel/runtime-corejs3/core-js-stable/weak-map";import*as Ce from"@babel/runtime-corejs3/core-js-stable/weak-set";import*as je from"@babel/runtime-corejs3/helpers/classPrivateFieldGet";import*as Oe from"@babel/runtime-corejs3/helpers/defineProperty";import*as Ne from"@babelLine truncated import*as e from"base64-js";import*as t from"ieee754";import*as r from"to-buffer";import{Component as n,PureComponent as a,createContext as s,createElement as o,default as i,forwardRef as l,useCallback as c,useContext as u,useEffect as p,useLayoutEffect as m,useMemo as d,useRef as h,useState as f}from"react";import{applyMiddleware as g,bindActionCreators as y,compose as S,createStore as v}from"redux";import*as b from"immutable";import{serializeError as w}from"serialize-error";import{default as _}from"lodash/merge";import"lodash/camelCase";import"lodash/upperFirst";import{default as C}from"lodash/memoize";import{default as x}from"lodash/find";import{default as O}from"lodash/some";import{default as N}from"lodash/eq";import{default as A}from"lodash/isFunction";import{default as k}from"css.escape";import{default as I}from"url-parse";import{createSelector as j}from"reselect";import{default as T}from"prop-types";import{default as R}from"lodash/omit";import{JSON_SCHEMA as M,default as P}from"js-yaml";import{default as B}from"zenscroll";import{default as L}from"react-immutable-proptypes";import{default as q}from"lodash/reduce";import{default as U}from"lodash/get";import{default as D}from"@babel/runtime-corejs3/helpers/extends";import{default as J}from"classnames";import{CopyToClipboard as $}from"react-copy-to-clipboard";import{default as V}from"react-immutable-pure-component";import{useVirtualizer as F,useWindowVirtualizer as z}from"@tanstack/react-virtual";import{default as K}from"react-debounce-input";import{default as W}from"lodash/isPlainObject";import{default as H}from"randexp";import{default as G}from"lodash/isEmpty";import{default as X}from"lodash/constant";import{default as Y}from"lodash/isString";import{default as Q}from"lodash/debounce";import{default as Z}from"lodash/set";import{default as ee}from"lodash/fp/assocPath";import{default as te}from"swagger-client/es/resolver/strategies/generic";import{default as re}from"swagger-client/es/resolver/strategies/openapi-2";import{default as ne}from"swagger-client/es/resolver/strategies/openapi-3-0";import{default as ae}from"swagger-client/es/resolver/strategies/openapi-3-1-apidom";import{default as se}from"swagger-client/es/resolver/strategies/openapi-3-2-apidom";import{makeResolve as oe}from"swagger-client/es/resolver";import{buildRequest as ie,execute as le}from"swagger-client/es/execute";import{default as ce,makeHttp as ue,serializeRes as pe}from"swagger-client/es/http";import{makeResolveSubtree as me}from"swagger-client/es/subtree-resolver";import{opId as de}from"swagger-client/es/helpers";import{default as he}from"react-dom";import{Provider as fe,connect as ge}from"react-redux";import{default as ye}from"lodash/identity";import{default as Se}from"react-syntax-highlighter/dist/esm/light";import{default as Ee}from"react-syntax-highlighter/dist/esm/languages/hljs/javascript";import{default as ve}from"react-syntax-highlighter/dist/esm/languages/hljs/json";import{default as be}from"react-syntax-highlighter/dist/esm/languages/hljs/xml";import{default as we}from"react-syntax-highlighter/dist/esm/languages/hljs/bash";import{default as _e}from"react-syntax-highlighter/dist/esm/languages/hljs/yaml";import{default as Ce}from"react-syntax-highlighter/dist/esm/languages/hljs/http";import{default as xe}from"react-syntax-highlighter/dist/esm/languages/hljs/powershell";import{default as Oe}from"react-syntax-highlighter/dist/esm/styles/hljs/agate";import{default as Ne}from"react-syntax-highlighter/dist/esm/styles/hljs/arta";import{default as Ae}from"react-syntax-highlighter/dist/esm/styles/hljs/monokai";import{default as ke}from"react-syntax-highlighter/dist/esm/styles/hljs/nord";import{default as Ie}from"react-syntax-highlighter/dist/esm/styles/hljs/obsidian";import{default as je}from"react-syntax-highlighter/dist/esm/styles/hljs/tomorrow-night";import{default as Te}from"react-syntax-highlighter/dist/esm/styles/hljs/idea";import{default as Re}from"js-file-download";import{default as Me}from"lodash/zipObject";import{default as Pe}from"lodash/toString";import{default as Be}from"xml-but-prettier";import{default as Le}from"lodash/toLower";import{Remarkable as qe}from"remarkable";import{linkify as Ue}from"remarkable/linkify";import{default as De}from"dompurify";import{default as Je}from"lodash/escapeRegExp";import{default as $e}from"lodash/has";import{default as Ve}from"lodash/fp/set";var Fe={287:function(e,t,r){const n=r(987),a=r(362),s="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;t.Buffer=Buffer,t.SlowBuffer=function SlowBuffer(e){+e!=e&&(e=0);return Buffer.alloc(+e)},t.INSPECT_MAX_BYTES=50;const o=2147483647;function createBuffer(e){if(e>o)throw new RangeError('The value "'+e+'" is invalid for option "size"');const t=new Uint8Array(e);return Object.setPrototypeOf(t,Buffer.prototype),t}function Buffer(e,t,r){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. ReLine truncated
//# sourceMappingURL=swagger-ui-es-bundle-core.js.map //# sourceMappingURL=swagger-ui-es-bundle-core.js.map
@@ -0,0 +1,35 @@
/*!
* @description Recursive object extending
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
* @license MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
@@ -1 +1 @@
{"version":3,"file":"swagger-ui-es-bundle-core.js","mappings":"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 truncated {"version":3,"file":"swagger-ui-es-bundle-core.js","mappings":"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 truncated
+1 -2
View File
@@ -1,3 +1,2 @@
/*! For license information please see swagger-ui-es-bundle.js.LICENSE.txt */ /*! For license information please see swagger-ui-es-bundle.js.LICENSE.txt */
(()=>{var e={17967:(e,t)=>{"use strict";t.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,o=/&(newline|tab);/gi,s=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i=/^.+(:|&colon;)/gim,a=[".","/"];t.N=function(e){var t,l=(t=e||"",t.replace(r,(function(e,t){return String.fromCharCode(t)}))).replace(o,"").replace(s,"").trim();if(!l)return"about:blank";if(function(e){return a.indexOf(e[0])>-1}(l))return l;var c=l.match(i);if(!c)return l;var u=c[0];return n.test(u)?"about:blank":l}},53795:(e,t,n)=>{"use strict";n.d(t,{Z:()=>P});var r=n(23101),o=n.n(r),s=n(61125),i=n.n(s),a=n(11882),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294),h=n(43393);function f(e){return f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},f(e)}function d(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function m(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function g(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function y(e){return y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},y(e)}function v(e,t){return v=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},v(e,t)}function b(e,t){return!t||"object"!=typeof t&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}var w={};function E(e,t,n){return function(e){return null==e}(e)?n:function(e){return null!==e&&"object"===f(e)&&"function"==typeof e.get&&"function"==typeof e.has}(e)?e.has(t)?e.get(t):n:hasOwnProperty.call(e,t)?e[t]:n}function x(e,t,n){for(var r=0;r!==t.length;)if((e=E(e,t[r++],w))===w)return n;return e}function S(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=function(e,t){return function(n){if("string"==typeof n)return(0,h.is)(t[n],e[n]);if(Array.isArray(n))return(0,h.is)(x(t,n),x(e,n));throw new TypeError("Invalid key: expected Array or string: "+n)}}(t,n),o=e||Object.keys(function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?g(n,!0).forEach((function(t){m(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):g(n).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},n,{},t));return o.every(r)}const _=function(e){function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),b(this,y(t).apply(this,arguments))}var n,r,o;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&v(e,t)}(t,e),n=t,r=[{key:"shouldComponentUpdate",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return!S(this.updateOnProps,this.props,e,"updateOnProps")||!S(this.updateOnStates,this.state,t,"updateOnStates")}}],r&&d(n.prototype,r),o&&d(n,o),t}(p.Component);var j=n(23930),O=n.n(j),k=n(45697),A=n.n(k);const C=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class P extends _{constructor(){super(...arguments),i()(this,"getModelName",(e=>-1!==l()(e).call(e,"#/definitions/")?C(e.replace(/^.*#\/definitions\//,"")):-1!==l()(e).call(e,"#/components/schemas/")?C(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),i()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:r,schema:s,required:i,name:a,isRef:l,specPath:c,displayName:u,includeReadOnly:h,includeWriteOnly:f}=this.props;const d=e("ObjectModel"),m=e("ArrayModel"),g=e("PrimitiveModel");let y="object",v=s&&s.get("$$ref");if(!a&&v&&(a=this.getModelName(v)),!s&&v&&(s=this.getRefSchema(a)),!s)return p.createElement("span",{className:"model model-title"},p.createElement("span",{className:"model-title__text"},u||a),p.createElement("img",{src:n(2517),height:"20px",width:"20px"}));const b=r.isOAS3()&&s.get("deprecated");switch(l=void 0!==l?l:!!v,y=s&&s.get("type")||y,y){case"object":return p.createElement(d,o()({className:"object"},this.props,{specPath:c,getConfigs:t,schema:s,name:a,deprecated:b,isRef:l,includeReadOnly:h,includeWriteOnly:f}));case"array":return p.createElement(m,o()({className:"array"},this.props,{getConfigs:t,schema:s,name:a,deprecated:b,required:i,includeReadOnly:h,inLine truncated (()=>{var s={67526(s,o){"use strict";o.byteLength=function byteLength(s){var o=getLens(s),i=o[0],a=o[1];return 3*(i+a)/4-a},o.toByteArray=function toByteArray(s){var o,i,_=getLens(s),w=_[0],x=_[1],C=new u(function _byteLength(s,o,i){return 3*(o+i)/4-i}(0,w,x)),j=0,L=x>0?w-4:w;for(i=0;i<L;i+=4)o=a[s.charCodeAt(i)]<<18|a[s.charCodeAt(i+1)]<<12|a[s.charCodeAt(i+2)]<<6|a[s.charCodeAt(i+3)],C[j++]=o>>16&255,C[j++]=o>>8&255,C[j++]=255&o;2===x&&(o=a[s.charCodeAt(i)]<<2|a[s.charCodeAt(i+1)]>>4,C[j++]=255&o);1===x&&(o=a[s.charCodeAt(i)]<<10|a[s.charCodeAt(i+1)]<<4|a[s.charCodeAt(i+2)]>>2,C[j++]=o>>8&255,C[j++]=255&o);return C},o.fromByteArray=function fromByteArray(s){for(var o,a=s.length,u=a%3,_=[],w=16383,x=0,C=a-u;x<C;x+=w)_.push(encodeChunk(s,x,x+w>C?C:x+w));1===u?(o=s[a-1],_.push(i[o>>2]+i[o<<4&63]+"==")):2===u&&(o=(s[a-2]<<8)+s[a-1],_.push(i[o>>10]+i[o>>4&63]+i[o<<2&63]+"="));return _.join("")};for(var i=[],a=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",w=0;w<64;++w)i[w]=_[w],a[_.charCodeAt(w)]=w;function getLens(s){var o=s.length;if(o%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=s.indexOf("=");return-1===i&&(i=o),[i,i===o?0:4-i%4]}function encodeChunk(s,o,a){for(var u,_,w=[],x=o;x<a;x+=3)u=(s[x]<<16&16711680)+(s[x+1]<<8&65280)+(255&s[x+2]),w.push(i[(_=u)>>18&63]+i[_>>12&63]+i[_>>6&63]+i[63&_]);return w.join("")}a["-".charCodeAt(0)]=62,a["_".charCodeAt(0)]=63},48287(s,o,i){"use strict";const a=i(67526),u=i(251),_="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;o.Buffer=Buffer,o.SlowBuffer=function SlowBuffer(s){+s!=s&&(s=0);return Buffer.alloc(+s)},o.INSPECT_MAX_BYTES=50;const w=2147483647;function createBuffer(s){if(s>w)throw new RangeError('The value "'+s+'" is invalid for option "size"');const o=new Uint8Array(s);return Object.setPrototypeOf(o,Buffer.prototype),o}function Buffer(s,o,i){if("number"==typeof s){if("string"==typeof o)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(s)}return from(s,o,i)}function from(s,o,i){if("string"==typeof s)return function fromString(s,o){"string"==typeof o&&""!==o||(o="utf8");if(!Buffer.isEncoding(o))throw new TypeError("Unknown encoding: "+o);const i=0|byteLength(s,o);let a=createBuffer(i);const u=a.write(s,o);u!==i&&(a=a.slice(0,u));return a}(s,o);if(ArrayBuffer.isView(s))return function fromArrayView(s){if(isInstance(s,Uint8Array)){const o=new Uint8Array(s);return fromArrayBuffer(o.buffer,o.byteOffset,o.byteLength)}return fromArrayLike(s)}(s);if(null==s)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s);if(isInstance(s,ArrayBuffer)||s&&isInstance(s.buffer,ArrayBuffer))return fromArrayBuffer(s,o,i);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(s,SharedArrayBuffer)||s&&isInstance(s.buffer,SharedArrayBuffer)))return fromArrayBuffer(s,o,i);if("number"==typeof s)throw new TypeError('The "value" argument must not be of type number. Received type number');const a=s.valueOf&&s.valueOf();if(null!=a&&a!==s)return Buffer.from(a,o,i);const u=function fromObject(s){if(Buffer.isBuffer(s)){const o=0|checked(s.length),i=createBuffer(o);return 0===i.length||s.copy(i,0,0,o),i}if(void 0!==s.length)return"number"!=typeof s.length||numberIsNaN(s.length)?createBuffer(0):fromArrayLike(s);if("Buffer"===s.type&&Array.isArray(s.data))return fromArrayLike(s.data)}(s);if(u)return u;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof s[Symbol.toPrimitive])return Buffer.from(s[Symbol.toPrimitive]("string"),o,i);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s)}function assertSize(s){if("number"!=typeof s)throw new TypeError('"size" argument must be of type number');if(s<0)throw new RangeError('The value "'+s+'" is invalid for option "size"')}function allocUnsafe(s){return assertSize(s),createBuffer(s<0?0:0|checked(s))}function fromArrayLike(s){const o=s.length<0?0:0|checked(s.length),i=createBuffer(o);for(let a=0;a<o;a+=1)i[a]=255&s[a];return i}function fromArrayBuffer(s,o,i){if(o<0||s.byteLength<o)throw new RangeError('"offset" is outside of buffer bounds');if(s.byteLength<o+(i||0))throw new RangeError('"length" is outside of buffer bounds');let a;return a=void 0===o&&void 0===i?new Uint8Array(s):void 0===i?new Uint8Array(s,o):new Uint8Array(s,o,i),Object.setPrototypeOf(a,Buffer.prototype),a}function checked(s){if(s>=w)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+w.toString(16)+" bytes");return 0|s}function byteLength(s,o){if(Buffer.isBuffer(s))return s.length;if(ArrayBuffer.isView(s)||isInstance(s,ArrayBuffer))return s.byteLength;if("string"!=typeof s)throw new TypeError('The "string" argument must be one of type string, Buffer,Line truncated
//# sourceMappingURL=swagger-ui-es-bundle.js.map
@@ -0,0 +1,131 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* @description Recursive object extending
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
* @license MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*!
* https://github.com/Starcounter-Jack/JSON-Patch
* (c) 2017-2021 Joachim Wester
* MIT license
*/
/*!
* https://github.com/Starcounter-Jack/JSON-Patch
* (c) 2017-2022 Joachim Wester
* MIT licensed
*/
/*!
* repeat-string <https://github.com/jonschlinkert/repeat-string>
*
* Copyright (c) 2014-2015, Jon Schlinkert.
* Licensed under the MIT License.
*/
/*! @license DOMPurify 3.4.17 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.17/LICENSE */
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* @license
* MIT License
*
* Copyright (c) 2014-present, Lee Byron and other contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* use-sync-external-store-with-selector.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"swagger-ui-es-bundle.js","mappings":"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 truncated {"version":3,"file":"swagger-ui-es-bundle.js","mappings":"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 truncated
+1 -2
View File
@@ -1,3 +1,2 @@
/*! For license information please see swagger-ui-standalone-preset.js.LICENSE.txt */ /*! For license information please see swagger-ui-standalone-preset.js.LICENSE.txt */
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SwaggerUIStandalonePreset=e():t.SwaggerUIStandalonePreset=e()}(this,(()=>(()=>{var t={7967:(t,e)=>{"use strict";var r=/^([^\w]*)(javascript|data|vbscript)/im,n=/&#(\w+)(^\w|;)?/g,i=/&(newline|tab);/gi,o=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,u=/^.+(:|&colon;)/gim,s=[".","/"]},9742:(t,e)=>{"use strict";e.byteLength=function(t){var e=s(t),r=e[0],n=e[1];return 3*(r+n)/4-n},e.toByteArray=function(t){var e,r,o=s(t),u=o[0],a=o[1],c=new i(function(t,e,r){return 3*(e+r)/4-r}(0,u,a)),f=0,l=a>0?u-4:u;for(r=0;r<l;r+=4)e=n[t.charCodeAt(r)]<<18|n[t.charCodeAt(r+1)]<<12|n[t.charCodeAt(r+2)]<<6|n[t.charCodeAt(r+3)],c[f++]=e>>16&255,c[f++]=e>>8&255,c[f++]=255&e;2===a&&(e=n[t.charCodeAt(r)]<<2|n[t.charCodeAt(r+1)]>>4,c[f++]=255&e);1===a&&(e=n[t.charCodeAt(r)]<<10|n[t.charCodeAt(r+1)]<<4|n[t.charCodeAt(r+2)]>>2,c[f++]=e>>8&255,c[f++]=255&e);return c},e.fromByteArray=function(t){for(var e,n=t.length,i=n%3,o=[],u=16383,s=0,c=n-i;s<c;s+=u)o.push(a(t,s,s+u>c?c:s+u));1===i?(e=t[n-1],o.push(r[e>>2]+r[e<<4&63]+"==")):2===i&&(e=(t[n-2]<<8)+t[n-1],o.push(r[e>>10]+r[e>>4&63]+r[e<<2&63]+"="));return o.join("")};for(var r=[],n=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",u=0;u<64;++u)r[u]=o[u],n[o.charCodeAt(u)]=u;function s(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");return-1===r&&(r=e),[r,r===e?0:4-r%4]}function a(t,e,n){for(var i,o,u=[],s=e;s<n;s+=3)i=(t[s]<<16&16711680)+(t[s+1]<<8&65280)+(255&t[s+2]),u.push(r[(o=i)>>18&63]+r[o>>12&63]+r[o>>6&63]+r[63&o]);return u.join("")}n["-".charCodeAt(0)]=62,n["_".charCodeAt(0)]=63},8764:(t,e,r)=>{"use strict";const n=r(9742),i=r(645),o="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;e.Buffer=a,e.SlowBuffer=function(t){+t!=t&&(t=0);return a.alloc(+t)},e.INSPECT_MAX_BYTES=50;const u=2147483647;function s(t){if(t>u)throw new RangeError('The value "'+t+'" is invalid for option "size"');const e=new Uint8Array(t);return Object.setPrototypeOf(e,a.prototype),e}function a(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new TypeError('The "string" argument must be of type string. Received type number');return l(t)}return c(t,e,r)}function c(t,e,r){if("string"==typeof t)return function(t,e){"string"==typeof e&&""!==e||(e="utf8");if(!a.isEncoding(e))throw new TypeError("Unknown encoding: "+e);const r=0|M(t,e);let n=s(r);const i=n.write(t,e);i!==r&&(n=n.slice(0,i));return n}(t,e);if(ArrayBuffer.isView(t))return function(t){if($(t,Uint8Array)){const e=new Uint8Array(t);return p(e.buffer,e.byteOffset,e.byteLength)}return h(t)}(t);if(null==t)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if($(t,ArrayBuffer)||t&&$(t.buffer,ArrayBuffer))return p(t,e,r);if("undefined"!=typeof SharedArrayBuffer&&($(t,SharedArrayBuffer)||t&&$(t.buffer,SharedArrayBuffer)))return p(t,e,r);if("number"==typeof t)throw new TypeError('The "value" argument must not be of type number. Received type number');const n=t.valueOf&&t.valueOf();if(null!=n&&n!==t)return a.from(n,e,r);const i=function(t){if(a.isBuffer(t)){const e=0|y(t.length),r=s(e);return 0===r.length||t.copy(r,0,0,e),r}if(void 0!==t.length)return"number"!=typeof t.length||X(t.length)?s(0):h(t);if("Buffer"===t.type&&Array.isArray(t.data))return h(t.data)}(t);if(i)return i;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof t[Symbol.toPrimitive])return a.from(t[Symbol.toPrimitive]("string"),e,r);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t)}function f(t){if("number"!=typeof t)throw new TypeError('"size" argument must be of type number');if(t<0)throw new RangeError('The value "'+t+'" is invalid for option "size"')}function l(t){return f(t),s(t<0?0:0|y(t))}function h(t){const e=t.length<0?0:0|y(t.length),r=s(e);for(let n=0;n<e;n+=1)r[n]=255&t[n];return r}function p(t,e,r){if(e<0||t.byteLength<e)throw new RangeError('"offset" is outside of buffer bounds');if(t.byteLength<e+(r||0))throw new RangeError('"length" is outside of buffer bounds');let n;return n=void 0===e&&void 0===r?new Uint8Array(t):void 0===r?new Uint8Array(t,e):new Uint8Array(t,e,r),Object.setPrototypeOf(n,a.prototype),n}function y(t){if(t>=u)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+u.toString(16)+" bytes");return 0|t}function M(t,e){if(a.isBuffer(t))return t.length;if(ArrayBuffer.isView(t)||$(t,ArrayBuffer))return t.byteLength;if("string"!=typeof t)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof t);const r=t.leLine truncated !function webpackUniversalModuleDefinition(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SwaggerUIStandalonePreset=e():t.SwaggerUIStandalonePreset=e()}(this,(()=>(()=>{var t={7526(t,e){"use strict";e.byteLength=function byteLength(t){var e=getLens(t),r=e[0],n=e[1];return 3*(r+n)/4-n},e.toByteArray=function toByteArray(t){var e,r,i=getLens(t),a=i[0],s=i[1],u=new o(function _byteLength(t,e,r){return 3*(e+r)/4-r}(0,a,s)),c=0,l=s>0?a-4:a;for(r=0;r<l;r+=4)e=n[t.charCodeAt(r)]<<18|n[t.charCodeAt(r+1)]<<12|n[t.charCodeAt(r+2)]<<6|n[t.charCodeAt(r+3)],u[c++]=e>>16&255,u[c++]=e>>8&255,u[c++]=255&e;2===s&&(e=n[t.charCodeAt(r)]<<2|n[t.charCodeAt(r+1)]>>4,u[c++]=255&e);1===s&&(e=n[t.charCodeAt(r)]<<10|n[t.charCodeAt(r+1)]<<4|n[t.charCodeAt(r+2)]>>2,u[c++]=e>>8&255,u[c++]=255&e);return u},e.fromByteArray=function fromByteArray(t){for(var e,n=t.length,o=n%3,i=[],a=16383,s=0,u=n-o;s<u;s+=a)i.push(encodeChunk(t,s,s+a>u?u:s+a));1===o?(e=t[n-1],i.push(r[e>>2]+r[e<<4&63]+"==")):2===o&&(e=(t[n-2]<<8)+t[n-1],i.push(r[e>>10]+r[e>>4&63]+r[e<<2&63]+"="));return i.join("")};for(var r=[],n=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0;a<64;++a)r[a]=i[a],n[i.charCodeAt(a)]=a;function getLens(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");return-1===r&&(r=e),[r,r===e?0:4-r%4]}function encodeChunk(t,e,n){for(var o,i,a=[],s=e;s<n;s+=3)o=(t[s]<<16&16711680)+(t[s+1]<<8&65280)+(255&t[s+2]),a.push(r[(i=o)>>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return a.join("")}n["-".charCodeAt(0)]=62,n["_".charCodeAt(0)]=63},8287(t,e,r){"use strict";const n=r(7526),o=r(251),i="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;e.Buffer=Buffer,e.SlowBuffer=function SlowBuffer(t){+t!=t&&(t=0);return Buffer.alloc(+t)},e.INSPECT_MAX_BYTES=50;const a=2147483647;function createBuffer(t){if(t>a)throw new RangeError('The value "'+t+'" is invalid for option "size"');const e=new Uint8Array(t);return Object.setPrototypeOf(e,Buffer.prototype),e}function Buffer(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(t)}return from(t,e,r)}function from(t,e,r){if("string"==typeof t)return function fromString(t,e){"string"==typeof e&&""!==e||(e="utf8");if(!Buffer.isEncoding(e))throw new TypeError("Unknown encoding: "+e);const r=0|byteLength(t,e);let n=createBuffer(r);const o=n.write(t,e);o!==r&&(n=n.slice(0,o));return n}(t,e);if(ArrayBuffer.isView(t))return function fromArrayView(t){if(isInstance(t,Uint8Array)){const e=new Uint8Array(t);return fromArrayBuffer(e.buffer,e.byteOffset,e.byteLength)}return fromArrayLike(t)}(t);if(null==t)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if(isInstance(t,ArrayBuffer)||t&&isInstance(t.buffer,ArrayBuffer))return fromArrayBuffer(t,e,r);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(t,SharedArrayBuffer)||t&&isInstance(t.buffer,SharedArrayBuffer)))return fromArrayBuffer(t,e,r);if("number"==typeof t)throw new TypeError('The "value" argument must not be of type number. Received type number');const n=t.valueOf&&t.valueOf();if(null!=n&&n!==t)return Buffer.from(n,e,r);const o=function fromObject(t){if(Buffer.isBuffer(t)){const e=0|checked(t.length),r=createBuffer(e);return 0===r.length||t.copy(r,0,0,e),r}if(void 0!==t.length)return"number"!=typeof t.length||numberIsNaN(t.length)?createBuffer(0):fromArrayLike(t);if("Buffer"===t.type&&Array.isArray(t.data))return fromArrayLike(t.data)}(t);if(o)return o;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof t[Symbol.toPrimitive])return Buffer.from(t[Symbol.toPrimitive]("string"),e,r);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t)}function assertSize(t){if("number"!=typeof t)throw new TypeError('"size" argument must be of type number');if(t<0)throw new RangeError('The value "'+t+'" is invalid for option "size"')}function allocUnsafe(t){return assertSize(t),createBuffer(t<0?0:0|checked(t))}function fromArrayLike(t){const e=t.length<0?0:0|checked(t.length),r=createBuffer(e);for(let n=0;n<e;n+=1)r[n]=255&t[n];return r}function fromArrayBuffer(t,e,r){if(e<0||t.byteLength<e)throw new RangeError('"offset" is outside of buffer bounds');if(t.byteLength<e+(r||0))throw new RangeError('"length" is outside of buffer bounds');let n;return n=void 0===e&&void 0===r?new Uint8Array(t):void 0===r?new Uint8Array(t,e):new Uint8Array(t,e,r),Object.setPrototypeOf(n,Buffer.prototype),n}function checked(t){if(t>=a)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+Line truncated
//# sourceMappingURL=swagger-ui-standalone-preset.js.map
@@ -0,0 +1,45 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* @license
* MIT License
*
* Copyright (c) 2014-present, Lee Byron and other contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
@@ -1 +1 @@
{"version":3,"file":"swagger-ui-standalone-preset.js","mappings":"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 truncated {"version":3,"file":"swagger-ui-standalone-preset.js","mappings":"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 truncated
+1 -1
View File
@@ -1,3 +1,3 @@
.swagger-ui{color:#3b4151;font-family:sans-serif/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */}.swagger-ui html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{-webkit-text-decoration-skip:objects;background-color:transparent}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+ILine truncated .swagger-ui{color:#3b4151;container-name:swagger-ui;container-type:inline-size;/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */font-family:sans-serif}.swagger-ui html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{background-color:transparent;-webkit-text-decoration-skip:objects}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpLine truncated
/*# sourceMappingURL=swagger-ui.css.map*/ /*# sourceMappingURL=swagger-ui.css.map*/
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"swagger-ui.css","mappings":"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,CDrDb,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,oDC8BW,CD7BX,qDC8BW,CD7BX,uDC8BY,CD7BZ,wDC8Ba,CD5Bb,6CCyCW,CDxCX,wCCyCM,CDxCN,8CCyCY,CDxCZ,2CCyCS,CDxCT,yCCyCO,CDxCP,wCCyCS,CDxCT,iDCyCe,CDxCf,2CCyCS,CDxCT,iDCyCe,CDxCf,8CCyCY,CDxCZ,6CCyCW,CDxCX,yCCyCO,CDxCP,+CCyCa,CDxCb,+CCyCa,CDxCb,0CCyCQ,CDxCR,gDCyCc,CDxCd,yCCyCO,CDxCP,8CCyCY,CDxCZ,yCCyCO,CDxCP,+CCyCa,CDxCb,kDCyCgB,CDxChB,gDCyCc,CDxCd,iDCyCe,CDxCf,kDCyCgB,CDxChB,+CCyCa,CDvCb,oDCVc,CDWd,6CE3DE,gCDmBmB,CClBnB,sCDmBgB,CClBhB,qCDmBgB,CClBhB,oCDmBgB,CClBhB,mCDmBgB,CClBhB,sCDmBqB,CClBrB,yCDmBmB,CClBnB,wBACI,yBACA,0BAEJ,qBACI,4BACA,6BAEJ,uBAEI,4BADA,wBACA,CAEJ,sBAEI,6BADA,yBACA,CAGN,mCACE,mCDNmB,CCOnB,yCDNgB,CCOhB,wCDNgB,CCOhB,uCDNgB,CCOhB,sCDNgB,CCOhB,yCDNqB,CCOrB,4CDNmB,CCOnB,2BACI,yBACA,0BAEJ,wBACI,4BACA,6BAEJ,0BAEI,4BADA,wBACA,CAEJ,yBAEI,6BADA,yBACA,EAIN,uDACE,kCDhCmB,CCiCnB,wCDhCgB,CCiChB,uCDhCgB,CCiChB,sCDhCgB,CCiChB,qCDhCgB,CCiChB,wCDhCqB,CCiCrB,2CDhCmB,CCiCnB,0BACI,yBACA,0BAEJ,uBACI,4BACA,6BAEJ,yBAEI,4BADA,wBACA,CAEJ,wBAEI,6BADA,yBACA,EAIN,mCACE,kCD1DmB,CC2DnB,wCD1DgB,CC2DhB,uCD1DgB,CC2DhB,sCD1DgB,CC2DhB,qCD1DgB,CC2DhB,wCD1DqB,CC2DrB,2CD1DmB,CC2DnB,0BACI,yBACA,0BAEJ,uBACI,4BACA,6BAEJ,yBAEI,4BADA,wBACA,CAEJ,wBAEI,6BADA,yBACA,ECrGN,2CACA,2CACA,yCACA,uCAEA,mCACE,8CACA,8CACA,4CACA,2CAGF,uDACE,6CACA,6CACA,2CACA,0CAGF,mCACE,6CACA,6CACA,2CACA,0CCvBF,+BH6BoB,CG5BpB,qCH6BiB,CG5BjB,oCH6BiB,CG5BjB,mCH6BiB,CG5BjB,kCH6BiB,CG5BjB,kCH6BiB,CG1BjB,oCHqBoB,CGpBpB,sCHoBoB,CGnBpB,uCHmBoB,CGlBpB,qCHkBoB,CGhBpB,mCACE,kCHekB,CGdlB,wCHee,CGdf,uCHee,CGdf,sCHee,CGdf,qCHee,CGdf,qCHee,CGdf,uCHSkB,CGRlB,yCHQkB,CGPlB,0CHOkB,CGNlB,wCHMkB,EGHpB,uDACE,iCHEkB,CGDlB,uCHEe,CGDf,sCHEe,CGDf,qCHEe,CGDf,oCHEe,CGDf,oCHEe,CGDf,sCHJkB,CGKlB,wCHLkB,CGMlB,yCHNkB,CGOlB,uCHPkB,EGUpB,mCACE,iCHXkB,CGYlB,uCHXe,CGYf,sCHXe,CGYf,qCHXe,CGYf,oCHXe,CGYf,oCHXe,CGYf,sCHjBkB,CGkBlB,wCHlBkB,CGmBlB,yCHnBkB,CGoBlB,uCHpBkB,EIxCpB,2DJ8Ce,CI7Cf,2DJ8Ce,CI7Cf,+DJ8Ce,CI7Cf,6DJ8Ce,CI7Cf,6DJ8Ce,CI5Cf,mCACE,8DJuCa,CItCb,8DJuCa,CItCb,kEJuCa,CItCb,gEJuCa,CItCb,gEJuCa,EIpCf,uDACE,6DJ+Ba,CI9Bb,6DJ+Ba,CI9Bb,iEJ+Ba,CI9Bb,+DJ+Ba,CI9Bb,+DJ+Ba,EI5Bf,mCACE,6DJuBa,CItBb,6DJuBa,CItBb,iEJuBa,CItBb,+DJuBa,CItBb,+DJuBa,EKxDf,iBACE,gBACA,kBACA,gBCkBF,yBACA,6BACA,+BACA,2BAEA,4BACA,gCACA,kCACA,8BAEA,4BACA,gCACA,kCACA,8BAEA,8BACA,kCACA,oCACA,gCAEA,8BACA,kCACA,oCACA,gCAGA,4BAGE,SACA,OAFA,QADA,KAGA,CAGF,mCACE,4BACA,8BACA,gCACA,kCACA,+BACA,iCACA,mCACA,qCACA,+BACA,iCACA,mCACA,qCACA,iCACA,qCACA,uCACA,mCACA,iCACA,qCACA,uCACA,mCACA,+BAGE,SACA,OAFA,QADA,KAGA,EAIJ,uDACE,2BACA,6BACA,+BACA,iLine truncated {"version":3,"file":"swagger-ui.css","mappings":"AAAA,YCII,aCcU,CFfd,yBACE,2BACA,4EGEF,CFJE,sBEIF,CDWc,iCCCZ,0BACA,8BACA,kBAUF,QACE,iHAOF,aAME,gBAQF,aACE,eACA,4DAWF,aAGE,oBAOF,eACE,gBAQF,sBACE,SACA,iBACA,iBAQF,+BACE,cACA,eAWF,4BACE,qCACA,yBAQF,kBACE,CACA,yBACA,CADA,wCACA,CADA,gCACA,kCAOF,mBAEE,CAOF,kBAPE,CASA,kDAQF,+BAGE,cACA,iBAOF,iBACE,kBAOF,qBACE,WACA,mBAOF,aACE,iCAQF,aAEE,cACA,kBACA,wBACA,iBAGF,aACE,iBAGF,SACE,qCAUF,oBAEE,mCAOF,YACE,SACA,iBAOF,iBACE,4BAOF,eACE,mGAWF,sBAKE,eACA,iBACA,SACA,sCAQF,gBAEE,uCAQF,mBAEE,sGASF,yBAIE,yKAOF,iBAIE,UACA,6JAOF,6BAIE,sBAOF,0BACE,oBAUF,qBACE,cACA,cACA,eACA,UACA,mBACA,sBAQF,oBACE,wBACA,sBAOF,aACE,sDAQF,qBAEE,UACA,2GAOF,WAEE,2BAQF,4BACE,oBACA,8GAOF,uBAEE,0CAQF,yBACE,aACA,sCAWF,aAEE,qBAOF,iBACE,oBAUF,oBACE,CAQA,0CAUF,YACE,sBCnbF,kDACA,kDACA,+CCDA,yEACE,4BAGF,yEACE,iCAGF,kEACE,kCAGF,kEACE,2tBClBF,qBAkCE,2BCrBF,QACE,kBACA,iCAGF,sDACA,sDAEA,kDACA,sDAEA,oDACA,mDAEA,oDACA,mDAEA,qDACA,mDAEA,sDAKI,QACA,CAEA,WACA,CAHA,MACA,CALJ,iBACI,CACA,OACA,CAFA,KACA,CAGA,UACA,CACA,WACA,oCAGJ,6BACI,QACE,kBACA,oCAEF,yDACA,yDACA,qDACA,yDACA,uDACA,sDACA,uDACA,sDACA,wDACA,sDACA,yDAII,QACA,CAEA,WACA,CAHA,MACA,CALJ,iBACI,CACA,OACA,CAFA,KACA,CAGA,UACA,CACA,WACA,yDAIR,4BACI,QACE,kBACA,mCAEF,wDACA,wDACA,oDACA,wDACA,sDACA,qDACA,sDACA,qDACA,uDACA,qDACA,wDAII,QACA,CAEA,WACA,CAHA,MACA,CALJ,iBACI,CACA,OACA,CAFA,KACA,CAGA,UACA,CACA,WACA,qCAIR,4BACI,QACE,kBACA,mCAEF,wDACA,wDACA,oDACA,wDACA,sDACA,qDACA,sDACA,qDACA,uDACA,qDACA,wDAII,QACA,CAEA,WACA,CAHA,MACA,CALJ,iBACI,CACA,OACA,CAFA,KACA,CAGA,UACA,CACA,WACA,kBC3HR,kCCQE,qDACA,qEAEF,sBACE,wDACA,0FAGF,qBACE,uDACA,sEAGF,qBACE,uDACA,0DCVA,uBACA,CAFF,2BAEE,qBAIA,uBACA,CAFF,2BAEE,uBAIA,wBACA,CAFF,2BAEE,wBAIA,0BACA,CAFF,2BAEE,sBAIA,qBACA,CAFF,2BAEE,oCAGF,0BAEI,uBACA,CAFF,2BAEE,wBAIA,uBACA,CAFF,2BAEE,0BAIA,wBACA,CAFF,2BAEE,2BAIA,0BACA,CAFF,2BAEE,yBAIA,qBACA,CAFF,2BAEE,yDAIJ,yBAEI,uBACA,CAFF,2BAEE,uBAIA,uBACA,CAFF,2BAEE,yBAIA,wBACA,CAFF,2BAEE,0BAIA,0BACA,CAFF,2BAEE,wBAIA,qBACA,CAFF,2BAEE,qCAIJ,yBAEI,uBACA,CAFF,2BAEE,uBAIA,uBACA,CAFF,2BAEE,yBAIA,wBACA,CAFF,2BAEE,0BAIA,0BACA,CAFF,2BAEE,wBAIA,qBACA,CAFF,2BAEE,uBChHJ,mDACA,qDACA,6CAEA,wBACE,sDACA,wDACA,kEAGF,uBACE,qDACA,uDACA,8CAGF,uBACE,qDACA,uDACA,2BCPA,oDACA,4DACA,gEACA,kEACA,8DACA,oEAGF,mBACE,uDACA,+DACA,mEACA,qEACA,iEACA,yFAGF,kBACE,sDACA,8DACA,kEACA,oEACA,gEACA,qEAGF,kBACE,sDACA,8DACA,kEACA,oEACA,gEACA,wDCnCF,iBC2CQ,4BD1CR,iBC2Ca,2BD1Cb,iBC2CY,0BD1CZ,iBC2CW,sBD1CX,iBC2CO,wBD1CP,iBC2CS,8BD1CT,iBC2Ce,2BD1Cf,iBC2CY,4BD1CZ,iBC2Ca,4BD1Cb,oBC2Ca,uBD1Cb,iBC2CQ,0BDzCR,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,+BCuDW,0BDtDX,gCCuDW,2BDtDX,iCCuDY,4BDtDZ,iCCuDa,0BDrDb,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,2BC8BW,0BD7BX,4BC8BW,2BD7BX,6BC8BY,4BD7BZ,6BC8Ba,0BD5Bb,oBCyCW,qBDxCX,oBCyCM,2BDxCN,oBCyCY,wBDxCZ,oBCyCS,sBDxCT,oBCyCO,wBDxCP,iBCyCS,8BDxCT,oBCyCe,wBDxCf,oBCyCS,8BDxCT,oBCyCe,2BDxCf,oBCyCY,0BDxCZ,oBCyCW,sBDxCX,oBCyCO,4BDxCP,oBCyCa,4BDxCb,oBCyCa,uBDxCb,oBCyCQ,6BDxCR,oBCyCc,sBDxCd,oBCyCO,2BDxCP,oBCyCY,sBDxCZ,oBCyCO,4BDxCP,oBCyCa,+BDxCb,oBCyCgB,6BDxChB,oBCyCc,8BDxCd,oBCyCe,+BDxCf,oBCyCgB,4BDxChB,oBCyCa,6BDvCb,wBCVc,yBDWd,sCE3DE,eDmBmB,kBClBnB,qBDmBgB,kBClBhB,oBDmBgB,kBClBhB,mBDmBgB,kBClBhB,kBDmBgB,qBClBhB,kBDmBqB,sBClBrB,oBDmBmB,yBClBnB,wBACI,0BACA,sBAEJ,2BACI,6BACA,wBAGA,2BACA,CAFJ,wBAEI,uBAGA,4BACA,CAFJ,yBAEI,oCAGN,oBACE,eDNmB,qBCOnB,qBDNgB,qBCOhB,oBDNgB,qBCOhB,mBDNgB,qBCOhB,kBDNgB,wBCOhB,kBDNqB,yBCOrB,oBDNmB,4BCOnB,wBACI,0BACA,yBAEJ,2BACI,6BACA,2BAGA,2BACA,CAFJ,wBAEI,0BAGA,4BACA,CAFJ,yBAEI,yDAIN,mBACE,eDhCmB,oBCiCnB,qBDhCgB,oBCiChB,oBDhCgB,oBCiChB,mBDhCgB,oBCiChB,kBDhCgB,uBCiChB,kBDhCqB,wBCiCrB,oBDhCmB,2BCiCnB,wBACI,0BACA,wBAEJ,2BACI,6BACA,0BAGA,2BACA,CAFJ,wBAEI,yBAGA,4BACA,CAFJ,yBAEI,qCAIN,mBACE,eD1DmB,oBC2DnB,qBD1DgB,oBC2DhB,oBD1DgB,oBC2DhB,mBD1DgB,oBC2DhB,kBD1DgB,uBC2DhB,kBD1DqB,wBC2DrB,oBD1DmB,2BC2DnB,wBACI,0BACA,wBAEJ,2BACI,6BACA,0BAGA,2BACA,CAFJ,wBAEI,yBAGA,4BACA,CAFJ,yBAEI,yBCrGN,2CACA,0CACA,wCACA,qDAEA,0BACE,8CACA,6CACA,2CACA,0EAGF,yBACE,6CACA,4CACA,0CACA,sDAGF,yBACE,6CACA,4CACA,0CACA,oCCvBF,cH6BoB,kBG5BpB,oBH6BiB,kBG5BjB,mBH6BiB,kBG5BjB,kBH6BiB,kBG5BjB,iBH6BiB,kBG5BjB,iBH6BiB,mBG1BjB,kBHqBoB,mBGpBpB,oBHoBoB,mBGnBpB,qBHmBoB,mBGlBpB,mBHkBoB,oCGhBpB,oBACE,cHekB,qBGdlB,oBHee,qBGdf,mBHee,qBGdf,kBHee,qBGdf,iBHee,qBGdf,iBHee,sBGdf,kBHSkB,sBGRlB,oBHQkB,sBGPlB,qBHOkB,sBGNlB,mBHMkB,yDGHpB,mBACE,cHEkB,oBGDlB,oBHEe,oBGDf,mBHEe,oBGDf,kBHEe,oBGDf,iBHEe,oBGDf,iBHEe,qBGDf,kBHJkB,qBGKlB,oBHLkB,qBGMlB,qBHNkB,qBGOlB,mBHPkB,qCGUpB,mBACE,cHXkB,oBGYlB,oBHXe,oBGYf,mBHXe,oBGYf,kBHXe,oBGYf,iBHXe,oBGYf,iBHXe,qBGYf,kBHjBkB,qBGkBlB,oBHlBkB,qBGmBlB,qBHnBkB,qBGoBlB,mBHpBkB,wBIxCpB,qCJ8Ce,uBI7Cf,qCJ8Ce,uBI7Cf,yCJ8Ce,uBI7Cf,uCJ8Ce,uBI7Cf,uCJ8Ce,oCI5Cf,yBACE,qCJuCa,0BItCb,qCJuCa,0BItCb,yCJuCa,0BItCb,uCJuCa,0BItCb,uCJuCa,yDIpCf,wBACE,qCJ+Ba,yBI9Bb,qCJ+Ba,yBI9Bb,yCJ+Ba,yBI9Bb,uCJ+Ba,yBI9Bb,uCJ+Ba,qCI5Bf,wBACE,qCJuBa,yBItBb,qCJuBa,yBItBb,yCJuBa,yBItBb,uCJuBa,yBItBb,uCJuBa,mBKxDf,eACE,kBACA,gBACA,oBCkBF,2BACA,8BACALine truncated
+1 -1
View File
@@ -1,2 +1,2 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUICore=t():e.SwaggerUICore=t()}(this,(()=>(()=>{var e={6024:(e,t,n)=>{"use strict";n.d(t,{Z:()=>E});var s=n(4250),r=n.n(s),a=n(1093),o=n.n(a),l=n(8493),c=n.n(l),i=n(3942),u=n.n(i),p=n(6689),m=n.n(p);const d=require("react-immutable-pure-component");var h=n.n(d),g=n(8082),f=n.n(g),y=n(580),S=n.n(y);const v=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class E extends(h()){constructor(){super(...arguments),o()(this,"getModelName",(e=>-1!==c()(e).call(e,"#/definitions/")?v(e.replace(/^.*#\/definitions\//,"")):-1!==c()(e).call(e,"#/components/schemas/")?v(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),o()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:s,schema:a,required:o,name:l,isRef:c,specPath:i,displayName:u,includeReadOnly:p,includeWriteOnly:d}=this.props;const h=e("ObjectModel"),g=e("ArrayModel"),f=e("PrimitiveModel");let y="object",S=a&&a.get("$$ref");if(!l&&S&&(l=this.getModelName(S)),!a&&S&&(a=this.getRefSchema(l)),!a)return m().createElement("span",{className:"model model-title"},m().createElement("span",{className:"model-title__text"},u||l),m().createElement("img",{src:n(2517),height:"20px",width:"20px"}));const v=s.isOAS3()&&a.get("deprecated");switch(c=void 0!==c?c:!!S,y=a&&a.get("type")||y,y){case"object":return m().createElement(h,r()({className:"object"},this.props,{specPath:i,getConfigs:t,schema:a,name:l,deprecated:v,isRef:c,includeReadOnly:p,includeWriteOnly:d}));case"array":return m().createElement(g,r()({className:"array"},this.props,{getConfigs:t,schema:a,name:l,deprecated:v,required:o,includeReadOnly:p,includeWriteOnly:d}));default:return m().createElement(f,r()({},this.props,{getComponent:e,getConfigs:t,schema:a,name:l,deprecated:v,required:o}))}}}o()(E,"propTypes",{schema:u()(f()).isRequired,getComponent:S().func.isRequired,getConfigs:S().func.isRequired,specSelectors:S().object.isRequired,name:S().string,displayName:S().string,isRef:S().bool,required:S().bool,expandDepth:S().number,depth:S().number,specPath:f().list.isRequired,includeReadOnly:S().bool,includeWriteOnly:S().bool})},5623:(e,t,n)=>{"use strict";n.d(t,{Z:()=>d});var s=n(1093),r=n.n(s),a=n(7252),o=n.n(a),l=n(6689),c=n.n(l),i=n(3883),u=n.n(i),p=(n(580),n(1669)),m=n(7504);class d extends c().Component{constructor(e,t){super(e,t),r()(this,"getDefinitionUrl",(()=>{let{specSelectors:e}=this.props;return new(u())(e.url(),m.Z.location).toString()}));let{getConfigs:n}=e,{validatorUrl:s}=n();this.state={url:this.getDefinitionUrl(),validatorUrl:void 0===s?"https://validator.swagger.io/validator":s}}UNSAFE_componentWillReceiveProps(e){let{getConfigs:t}=e,{validatorUrl:n}=t();this.setState({url:this.getDefinitionUrl(),validatorUrl:void 0===n?"https://validator.swagger.io/validator":n})}render(){let{getConfigs:e}=this.props,{spec:t}=e(),n=(0,p.Nm)(this.state.validatorUrl);return"object"==typeof t&&o()(t).length?null:this.state.url&&(0,p.hW)(this.state.validatorUrl)&&(0,p.hW)(this.state.url)?c().createElement("span",{className:"float-right"},c().createElement("a",{target:"_blank",rel:"noopener noreferrer",href:`${n}/debug?url=${encodeURIComponent(this.state.url)}`},c().createElement(h,{src:`${n}?url=${encodeURIComponent(this.state.url)}`,alt:"Online validator badge"}))):null}}class h extends c().Component{constructor(e){super(e),this.state={loaded:!1,error:!1}}componentDidMount(){const e=new Image;e.onload=()=>{this.setState({loaded:!0})},e.onerror=()=>{this.setState({error:!0})},e.src=this.props.src}UNSAFE_componentWillReceiveProps(e){if(e.src!==this.props.src){const t=new Image;t.onload=()=>{this.setState({loaded:!0})},t.onerror=()=>{this.setState({error:!0})},t.src=e.src}}render(){return this.state.error?c().createElement("img",{alt:"Error"}):this.state.loaded?c().createElement("img",{src:this.props.src,alt:this.props.alt}):null}}},2552:(e,t,n)=>{"use strict";n.d(t,{Z:()=>m,s:()=>d});var s=n(6689),r=n.n(s),a=(n(580),n(963));const o=require("remarkable/linkify"),l=require("dompurify");var c=n.n(l),i=n(9003),u=n.n(i);function p(e){let{source:t,className:n="",getConfigs:s}=e;if("string"!=typeof t)return null;const l=new a.Remarkable({html:!0,typographer:!0,breaks:!0,linkTarget:"_blank"}).use(o.linkify);l.core.ruler.disable(["replacements","smartquotes"]);const{useUnsafeMarkdown:c}=s(),i=l.render(t),p=d(i,{useUnsafeMarkdown:c});return t&&i&&p?r().createElement("div",{className:u()(n,"markdown"),dangerouslySetInnerHTML:{__html:p}}):null}c().addHook&&c().addHook("beforeSanitizeElements",(function(e){return e.href&&e.setAttribute("rel","noopener noreferrer"),e})),p.defaultProps={getConfigs:()=>({useUnsafeMarkdown:!1})};const m=p;function d(e){let{useUnsafeMarkdown:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguLine truncated !function webpackUniversalModuleDefinition(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUICore=t():e.SwaggerUICore=t()}(this,(()=>(()=>{"use strict";var e={158(e){e.exports=require("buffer")}},t={};function __webpack_require__(a){var r=t[a];if(void 0!==r)return r.exports;var n=t[a]={exports:{}};return e[a](n,n.exports,__webpack_require__),n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var a in t)__webpack_require__.o(t,a)&&!__webpack_require__.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var a={};__webpack_require__.d(a,{default:()=>Cl});var r={};__webpack_require__.r(r),__webpack_require__.d(r,{CLEAR:()=>$,CLEAR_BY:()=>L,NEW_AUTH_ERR:()=>V,NEW_SPEC_ERR:()=>R,NEW_SPEC_ERR_BATCH:()=>J,NEW_THROWN_ERR:()=>P,NEW_THROWN_ERR_BATCH:()=>T,clear:()=>clear,clearBy:()=>clearBy,newAuthErr:()=>newAuthErr,newSpecErr:()=>newSpecErr,newSpecErrBatch:()=>newSpecErrBatch,newThrownErr:()=>newThrownErr,newThrownErrBatch:()=>newThrownErrBatch});var n={};__webpack_require__.r(n),__webpack_require__.d(n,{AUTHORIZE:()=>ue,AUTHORIZE_OAUTH2:()=>he,CONFIGURE_AUTH:()=>ge,LOGOUT:()=>de,RESTORE_AUTHORIZATION:()=>ye,SHOW_AUTH_POPUP:()=>me,authPopup:()=>authPopup,authorize:()=>authorize,authorizeAccessCodeWithBasicAuthentication:()=>authorizeAccessCodeWithBasicAuthentication,authorizeAccessCodeWithFormParams:()=>authorizeAccessCodeWithFormParams,authorizeApplication:()=>authorizeApplication,authorizeOauth2:()=>authorizeOauth2,authorizeOauth2WithPersistOption:()=>authorizeOauth2WithPersistOption,authorizePassword:()=>authorizePassword,authorizeRequest:()=>authorizeRequest,authorizeWithPersistOption:()=>authorizeWithPersistOption,configureAuth:()=>configureAuth,logout:()=>logout,logoutWithPersistOption:()=>logoutWithPersistOption,persistAuthorizationIfNeeded:()=>persistAuthorizationIfNeeded,preAuthorizeImplicit:()=>preAuthorizeImplicit,restoreAuthorization:()=>restoreAuthorization,showDefinitions:()=>showDefinitions});var s={};__webpack_require__.r(s),__webpack_require__.d(s,{authorized:()=>ve,definitionsForRequirements:()=>definitionsForRequirements,definitionsToAuthorize:()=>_e,getConfigs:()=>we,getDefinitionsByNames:()=>getDefinitionsByNames,isAuthorized:()=>isAuthorized,selectAuthPath:()=>selectAuthPath,shownDefinitions:()=>Ee});var o={};__webpack_require__.r(o),__webpack_require__.d(o,{TOGGLE_CONFIGS:()=>qe,UPDATE_CONFIGS:()=>Ie,downloadConfig:()=>downloadConfig,getConfigByUrl:()=>getConfigByUrl,loaded:()=>actions_loaded,toggle:()=>toggle,update:()=>update});var l={};__webpack_require__.r(l),__webpack_require__.d(l,{get:()=>get});var c={};__webpack_require__.r(c),__webpack_require__.d(c,{transform:()=>transform});var i={};__webpack_require__.r(i),__webpack_require__.d(i,{transform:()=>parameter_oneof_transform});var p={};__webpack_require__.r(p),__webpack_require__.d(p,{allErrors:()=>Ye,lastError:()=>Qe});var m={};__webpack_require__.r(m),__webpack_require__.d(m,{SHOW:()=>nt,UPDATE_FILTER:()=>at,UPDATE_LAYOUT:()=>tt,UPDATE_MODE:()=>rt,changeMode:()=>changeMode,show:()=>actions_show,updateFilter:()=>updateFilter,updateLayout:()=>updateLayout});var u={};__webpack_require__.r(u),__webpack_require__.d(u,{current:()=>current,currentFilter:()=>currentFilter,isShown:()=>isShown,showSummary:()=>ot,whatMode:()=>whatMode});var d={};__webpack_require__.r(d),__webpack_require__.d(d,{taggedOperations:()=>taggedOperations});var h={};__webpack_require__.r(h),__webpack_require__.d(h,{getActiveLanguage:()=>it,getDefaultExpanded:()=>pt,getGenerators:()=>ct,getSnippetGenerators:()=>getSnippetGenerators});var g={};__webpack_require__.r(g),__webpack_require__.d(g,{JsonSchemaArrayItemFile:()=>JsonSchemaArrayItemFile,JsonSchemaArrayItemText:()=>JsonSchemaArrayItemText,JsonSchemaForm:()=>JsonSchemaForm,JsonSchema_array:()=>JsonSchema_array,JsonSchema_boolean:()=>JsonSchema_boolean,JsonSchema_object:()=>JsonSchema_object,JsonSchema_string:()=>JsonSchema_string});var y={};__webpack_require__.r(y),__webpack_require__.d(y,{allowTryItOutFor:()=>allowTryItOutFor,basePath:()=>ga,canExecuteScheme:()=>canExecuteScheme,consumes:()=>pa,consumesOptionsFor:()=>consumesOptionsFor,contentTypeValues:()=>contentTypeValues,currentProducesFor:()=>currentProducesFor,definitions:()=>ha,externalDocs:()=>na,findDefinition:()=>findDefinition,getOAS3RequiredRequestBodyContentType:()=>getOAS3RequiredRequestBodyContentType,getParameter:()=>getParameter,hasHost:()=>Ca,host:()=>ya,info:()=>ra,isMediaTypeSchemaPropertiesEqual:()=>isMediaTypeSchemaPropertiesEqual,isOAS3:()=>aa,lastError:()=>Ht,mutatedLine truncated
//# sourceMappingURL=swagger-ui.js.map //# sourceMappingURL=swagger-ui.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"swagger-ui.js","mappings":"CAAA,SAA2CA,EAAMC,GAC1B,iBAAZC,SAA0C,iBAAXC,OACxCA,OAAOD,QAAUD,IACQ,mBAAXG,QAAyBA,OAAOC,IAC9CD,OAAO,GAAIH,GACe,iBAAZC,QACdA,QAAuB,cAAID,IAE3BD,EAAoB,cAAIC,GACzB,CATD,CASGK,MAAM,I,6JCTT,MAAM,EAA+BC,QAAQ,kC,kDCK7C,MAAMC,EAAgBC,IACpB,MAAMC,EAAYD,EAAIE,QAAQ,MAAO,KAAKA,QAAQ,MAAO,KAEzD,IACE,OAAOC,mBAAmBF,EAC5B,CAAE,MACA,OAAOA,CACT,GAGa,MAAMG,UAAcC,KAAuBC,cAAA,SAAAC,WAAAC,IAAA,qBAiBxCC,IAC0B,IAAnCC,IAAAD,GAAGE,KAAHF,EAAY,kBACRV,EAAcU,EAAIP,QAAQ,sBAAuB,MAEX,IAA1CQ,IAAAD,GAAGE,KAAHF,EAAY,yBACRV,EAAcU,EAAIP,QAAQ,8BAA+B,UADlE,IAGDM,IAAA,qBAEeI,IACd,IAAI,cAAEC,GAAkBhB,KAAKiB,MAE7B,OAAOD,EAAcE,eAAeH,EAAM,GAC3C,CAEDI,SACE,IAAI,aAAEC,EAAY,WAAEC,EAAU,cAAEL,EAAa,OAAEM,EAAM,SAAEC,EAAQ,KAAEC,EAAI,MAAEC,EAAK,SAAEC,EAAQ,YAAEC,EAAW,gBACjGC,EAAe,iBAAEC,GAAoB7B,KAAKiB,MAC5C,MAAMa,EAAcV,EAAa,eAC3BW,EAAaX,EAAa,cAC1BY,EAAiBZ,EAAa,kBACpC,IAAIa,EAAO,SACPC,EAAQZ,GAAUA,EAAOa,IAAI,SAWjC,IARMX,GAAQU,IACZV,EAAOxB,KAAKoC,aAAcF,KAGtBZ,GAAUY,IACdZ,EAAStB,KAAKqC,aAAcb,KAG1BF,EACF,OAAOgB,IAAAA,cAAA,QAAMC,UAAU,qBACfD,IAAAA,cAAA,QAAMC,UAAU,qBAAsBZ,GAAeH,GACrDc,IAAAA,cAAA,OAAKE,IAAKvC,EAAQ,MAAiCwC,OAAQ,OAAQC,MAAO,UAIpF,MAAMC,EAAa3B,EAAc4B,UAAYtB,EAAOa,IAAI,cAIxD,OAHAV,OAAkBoB,IAAVpB,EAAsBA,IAAUS,EACxCD,EAAOX,GAAUA,EAAOa,IAAI,SAAWF,EAEhCA,GACL,IAAK,SACH,OAAOK,IAAAA,cAACR,EAAWgB,IAAA,CACjBP,UAAU,UAAcvC,KAAKiB,MAAK,CAClCS,SAAUA,EACVL,WAAaA,EACbC,OAASA,EACTE,KAAOA,EACPmB,WAAYA,EACZlB,MAAQA,EACRG,gBAAmBA,EACnBC,iBAAoBA,KACxB,IAAK,QACH,OAAOS,IAAAA,cAACP,EAAUe,IAAA,CAChBP,UAAU,SAAavC,KAAKiB,MAAK,CACjCI,WAAaA,EACbC,OAASA,EACTE,KAAOA,EACPmB,WAAYA,EACZpB,SAAWA,EACXK,gBAAmBA,EACnBC,iBAAoBA,KAKxB,QACE,OAAOS,IAAAA,cAACN,EAAcc,IAAA,GACf9C,KAAKiB,MAAK,CACfG,aAAeA,EACfC,WAAaA,EACbC,OAASA,EACTE,KAAOA,EACPmB,WAAYA,EACZpB,SAAWA,KAEnB,EACDZ,IAlGoBJ,EAAK,YACL,CACjBe,OAAQyB,IAAAC,KAAgBC,WACxB7B,aAAc8B,IAAAA,KAAeD,WAC7B5B,WAAY6B,IAAAA,KAAeD,WAC3BjC,cAAekC,IAAAA,OAAiBD,WAChCzB,KAAM0B,IAAAA,OACNvB,YAAauB,IAAAA,OACbzB,MAAOyB,IAAAA,KACP3B,SAAU2B,IAAAA,KACVC,YAAaD,IAAAA,OACbE,MAAOF,IAAAA,OACPxB,SAAUsB,IAAAA,KAAiBC,WAC3BrB,gBAAiBsB,IAAAA,KACjBrB,iBAAkBqB,IAAAA,M,4JCtBP,MAAMG,UAA6Bf,IAAAA,UAO9C7B,YAAYQ,EAAOqC,GACfC,MAAMtC,EAAOqC,GAAQ3C,IAAA,yBASN,KAEjB,IAAI,cAAEK,GAAkBhB,KAAKiB,MAG7B,OADkB,IAAIuC,IAAJ,CAAQxC,EAAcyC,MAAOC,EAAAA,EAAIC,UAClCC,UAAU,IAbzB,IAAI,WAAEvC,GAAeJ,GACjB,aAAE4C,GAAiBxC,IACvBrB,KAAK8D,MAAQ,CACTL,IAAKzD,KAAK+D,mBACVF,kBAA+BhB,IAAjBgB,EAA6B,yCAA2CA,EAE9F,CAUFG,iCAAiCC,GAC3B,IAAI,WAAE5C,GAAe4C,GACjB,aAAEJ,GAAiBxC,IAEvBrB,KAAKkE,SAAS,CACVT,IAAKzD,KAAK+D,mBACVF,kBAA+BhB,IAAjBgB,EAA6B,yCAA2CA,GAE9F,CAEA1C,SACI,IAAI,WAAEE,GAAerB,KAAKiB,OACtB,KAAEkD,GAAS9C,IAEX+C,GAAwBC,EAAAA,EAAAA,IAAYrE,KAAK8D,MAAMD,cAEnD,MAAqB,iBAATM,GAAqBG,IAAYH,GAAMI,OAAe,KAE7DvE,KAAK8D,MAAML,MAAQe,EAAAA,EAAAA,IAAsBxE,KAAK8D,MAAMD,gBACjCW,EAAAA,EAAAA,IAAsBxE,KAAK8D,MAAML,KAIjDnB,IAAAA,cAAA,QAAMC,UAAU,eAChBD,IAAAA,cAAA,KAAGmC,OAAO,SAASC,IAAI,sBAAsBC,KAAO,GAAGP,eAAqCQ,mBAAmB5E,KAAK8D,MAAML,QACtHnB,IAAAA,cAACuC,EAAc,CAACrC,IAAM,GAAG4B,SAA+BQ,mBAAmB5E,KAAK8D,MAAML,OAASqB,IAAI,6BALtG,IAQb,EAIJ,MAAMD,UAAuBvC,IAAAA,UAM3B7B,YAAYQ,GACVsC,MAAMtC,GACNjB,KAAK8D,MAAQ,CACXiB,QAAQ,EACRC,OAAO,EAEX,CAEAC,oBACE,MAAMC,EAAM,IAAIC,MAChBD,EAAIE,OAAS,KACXpF,KAAKkE,SAAS,CACZa,QAAQ,GACR,EAEJG,EAAIG,QAAU,KACZrF,KAAKkE,SAAS,CACZc,OAAO,GACP,EAEJE,EAAI1C,IAAMxC,KAAKiB,MAAMuB,GACvB,CAEAwB,iCAAiCC,GAC/B,GAAIA,EAAUzB,MAAQxC,KAAKiB,MAAMuB,IAAK,CACpC,MAAM0C,EAAM,IAAIC,MAChBD,EAAIE,OAAS,KACXpF,KAAKkE,SAAS,CACZa,QAAQ,GACR,EAEJG,EAAIG,QAAU,KACZrF,KAAKkE,SAAS,CACZc,OAAO,GACP,EAEJE,EAAI1C,IAAMyB,EAAUzB,GACtB,CACF,CAEArB,SACE,OAAInB,KAAK8D,MAAMkB,MACN1C,IAAAA,cAAA,OAAKwC,IAAK,UACP9E,KAAK8D,MAAMiB,OAGhBzC,IAAAA,cAAA,OAAKE,IAAKxC,KAAKiB,MAAMuB,IAAKsC,IAAK9E,KAAKiB,MAAM6D,MAFxC,IAGX,E,gGCrHF,MAAM,EAA+B7E,QAAQ,sBCAvC,EAA+BA,QAAQ,a,gCCoB7C,SAASqF,EAAQC,GAA0C,IAAzC,OAAEC,EAAM,UAAEjD,EAAY,GAAE,WAAElB,GAAYkE,EACtD,GAAsB,iBAAXC,EACT,OAAO,KAGT,MAAMC,EAAK,IAAIC,EAAAA,WAAW,CACxBC,MAAM,EACNC,aAAa,EACbC,QAAQ,EACRC,WAAY,WACXC,IAAIC,EAAAA,SAEPP,EAAGQ,KAAKC,MAAMC,QAAQ,CAAC,eAAgB,gBAEvC,MAAM,kBAAEC,GAAsB/E,IACxBsE,EAAOF,EAAGtE,OAAOqE,GACjBa,EAAYC,EAAUX,EAAM,CAAES,sBAEpC,OAAKZ,GAAWG,GAASU,EAKvB/D,IAAAA,cAAA,OAAKC,UAAWgE,IAAGhE,EAAW,YAAaiE,wBAAyB,CAAEC,OAAQJ,KAJvE,IAMX,CAtCIK,IAAAA,SACFA,IAAAA,QAAkB,0BAA0B,SAAUC,GAQpD,OAHIA,EAAQhC,MACVgC,EAAQC,aAAa,MAAO,uBAEvBD,CACT,IAoCFrB,EAASuB,aAAe,CACtBxF,WAAYA,KAAA,CAAS+E,mBAAmB,KAG1C,UAEO,SAASE,EAAUQ,GAA0C,IAArC,kBAAEV,GAAoB,GAAO1F,UAAA6D,OAAA,QAAA1B,IAAAnC,UAAA,GAAAA,UAAA,GAAG,CAAC,EAC9D,MAAMqG,EAAkBX,EAClBY,EAAcZ,EAAoB,GAAK,CAAC,QAAS,SAOvD,OALIA,IAAsBE,EAAUW,4BAClCC,QAAQC,KAAM,gHACdb,EAAUW,2BAA4B,GAGjCP,IAAAA,SAAmBI,EAAK,CAC7BM,SAAU,CAAC,UACXC,YAAa,CAAC,QAAS,QACvBN,kBACAC,eAEJ,CACAV,EAAUW,2BAA4B,C,2HCxEtC,MAAMK,EAAUrH,EAAAA,MAEVsH,EAAa,CAAC,EAEpB,IAEAC,IAAAC,EAAAC,IAAAJ,GAAOxG,KAAPwG,IAAcxG,KAAA2G,GAAU,SAAUE,GAChC,GAAY,eAARA,EACF,OAQF,IAAIC,EAAMN,EAAQK,GAClBJ,GAAWM,EAAAA,EAAAA,IAAmBF,IAAQC,EAAIE,QAAUF,EAAIE,QAAUF,CACpE,IAEAL,EAAWQ,WAAaA,EAAAA,O,mvBCnBjB,MAAMC,EAAkB,aAClBC,EAAY,YACZC,EAAS,SACTC,EAAuB,uBACvBC,EAAmB,mBACnBC,EAAW,WACXC,EAAiB,iBACjBC,EAAwB,wBAI9B,SAASCLine truncated {"version":3,"file":"swagger-ui.js","mappings":"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 truncated
+1 -1
View File
@@ -8,7 +8,7 @@ if(file_exists('inc/vars.local.inc.php')) {
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config); $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
header('Content-type: application/json'); header('Content-type: application/json');
if (strtolower($_GET['Protocol']) == 'activesync' && getenv('SKIP_SOGO') != "y") { if (strtolower($_GET['Protocol']) == 'activesync' && $autodiscover_config['autodiscoverType'] == 'activesync' && getenv('SKIP_SOGO') != "y") {
echo '{"Protocol":"ActiveSync","Url":"' . $autodiscover_config['activesync']['url'] . '"}'; echo '{"Protocol":"ActiveSync","Url":"' . $autodiscover_config['activesync']['url'] . '"}';
} }
elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') { elseif (strtolower($_GET['Protocol']) == 'autodiscoverv1') {
+1 -1
View File
@@ -31,7 +31,7 @@ error_reporting(0);
$data = trim(file_get_contents("php://input")); $data = trim(file_get_contents("php://input"));
if (strpos($data, 'autodiscover/outlook/responseschema') !== false) { if (strpos($data, 'autodiscover/outlook/responseschema') !== false && $autodiscover_config['autodiscoverType'] == 'activesync') {
$autodiscover_config['autodiscoverType'] = 'imap'; $autodiscover_config['autodiscoverType'] = 'imap';
if ($autodiscover_config['useEASforOutlook'] == 'yes' && if ($autodiscover_config['useEASforOutlook'] == 'yes' &&
// Office for macOS does not support EAS // Office for macOS does not support EAS
+25 -2
View File
@@ -15,6 +15,10 @@ table.dataTable>tbody>tr.child ul.dtr-details>li {
border-bottom: 1px solid rgba(0, 0, 0, 0.129); border-bottom: 1px solid rgba(0, 0, 0, 0.129);
padding: 0.5em 0; padding: 0.5em 0;
} }
/* like the first entry has no top padding, the last one needs no bottom padding */
table.dataTable>tbody>tr.child ul.dtr-details>li:last-child {
padding-bottom: 0;
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover, table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover { table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
background-color: var(--mc-dtr-hover-bg); background-color: var(--mc-dtr-hover-bg);
@@ -28,7 +32,8 @@ table.dataTable td.dt-control:before {
color: var(--mc-dtr-color); color: var(--mc-dtr-color);
height: 1em; height: 1em;
width: 1em; width: 1em;
line-height: 1.25em; line-height: 1em;
margin-top: calc(-0.5em - 1.5px);
border-radius: 0px; border-radius: 0px;
box-shadow: none; box-shadow: none;
font-size: 14px; font-size: 14px;
@@ -39,11 +44,29 @@ table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before,
table.dataTable td.dt-control:before { table.dataTable td.dt-control:before {
background-color: var(--mc-dtr-active-bg) !important; background-color: var(--mc-dtr-active-bg) !important;
} }
/* The default initial order targets column 0, which is usually the non-orderable expand column */
table.dataTable thead > tr > th.sorting_disabled.sorting_asc,
table.dataTable thead > tr > th.sorting_disabled.sorting_desc {
padding-right: 0.5rem;
cursor: auto;
}
table.dataTable thead > tr > th.sorting_disabled.sorting_asc:before,
table.dataTable thead > tr > th.sorting_disabled.sorting_asc:after,
table.dataTable thead > tr > th.sorting_disabled.sorting_desc:before,
table.dataTable thead > tr > th.sorting_disabled.sorting_desc:after {
display: none;
}
table.dataTable.table-striped>tbody>tr>td { table.dataTable.table-striped>tbody>tr>td {
vertical-align: middle; vertical-align: middle;
} }
table.dataTable.table-striped>tbody>tr>td>input[type="checkbox"] { table.dataTable.table-striped>tbody>tr>td>input[type="checkbox"] {
margin-top: 7px; /* centers the 1em checkbox in the 1.5em line, level with the expand control */
margin-top: 0.25em;
}
/* the vertical margin is meant for the tag editor in edit forms, in tables it only adds height */
table.dataTable .tag-badge {
margin-top: 0;
margin-bottom: 0;
} }
td.dtr-col-lg { td.dtr-col-lg {
min-width: 350px; min-width: 350px;
+8
View File
@@ -139,6 +139,13 @@ body {
display: inline-block; display: inline-block;
font-size: inherit; font-size: inherit;
} }
.badge .bi {
font-size: inherit;
}
/* keeps icons from making a badge taller than one with text only */
.badge .bi::before {
line-height: 0;
}
.btn-group-xs > .btn, .btn-xs { .btn-group-xs > .btn, .btn-xs {
padding: .25rem .4rem; padding: .25rem .4rem;
font-size: .875rem; font-size: .875rem;
@@ -343,6 +350,7 @@ code {
} }
.tag-badge .bi { .tag-badge .bi {
font-size: 12px; font-size: 12px;
line-height: 0;
} }
.tag-badge.btn-badge:hover { .tag-badge.btn-badge:hover {
filter: brightness(0.9); filter: brightness(0.9);
+1 -1
View File
@@ -191,7 +191,7 @@
height: 2rem; height: 2rem;
width: 2rem; width: 2rem;
line-height: 2rem; line-height: 2rem;
margin-top: -15px; margin-top: calc(-1rem - 1.5px);
} }
li .dtr-data { li .dtr-data {
-2
View File
@@ -11,7 +11,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$js_minifier->add('/web/js/site/imapsync.js'); $js_minifier->add('/web/js/site/imapsync.js');
$js_minifier->add('/web/js/site/groups.js'); $js_minifier->add('/web/js/site/groups.js');
$js_minifier->add('/web/js/site/mail-policy.js');
$js_minifier->add('/web/js/site/mailbox.js'); $js_minifier->add('/web/js/site/mailbox.js');
$js_minifier->add('/web/js/presets/sieveMailbox.js'); $js_minifier->add('/web/js/presets/sieveMailbox.js');
$js_minifier->add('/web/js/site/pwgen.js'); $js_minifier->add('/web/js/site/pwgen.js');
@@ -46,7 +45,6 @@ $template_data = [
'lang_edit' => json_encode($lang['edit']), 'lang_edit' => json_encode($lang['edit']),
'lang_datatables' => json_encode($lang['datatables']), 'lang_datatables' => json_encode($lang['datatables']),
'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(), 'imapsync_redirect_uri' => \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->oauth->redirectUri(),
'mail_policy_groups' => \Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->policies->groupChoices($domains),
]; ];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
-11
View File
@@ -115,17 +115,6 @@ if (isset($_SESSION['mailcow_cc_role'])) {
'mbox_templates' => ($_SESSION['mailcow_cc_role'] == 'admin') ? mailbox('get', 'mailbox_templates') : [], 'mbox_templates' => ($_SESSION['mailcow_cc_role'] == 'admin') ? mailbox('get', 'mailbox_templates') : [],
]; ];
} }
elseif (isset($_GET['mail-policy']) &&
is_numeric($_GET['mail-policy'])) {
$mail_policy = \Mailcow\MailPolicy\MailPolicyModule::fromGlobals();
$result = $mail_policy->api->getPolicy($_GET['mail-policy']);
$template = 'edit/mail-policy.twig';
// Choices: mailboxes and groups of the domain of the policy
$template_data = [
'mailboxes' => !empty($result) ? mailbox('get', 'mailboxes', $result['domain']) : [],
'mail_policy_groups' => !empty($result) ? $mail_policy->policies->groupChoices([$result['domain']]) : [],
];
}
elseif (isset($_GET['api-key']) && elseif (isset($_GET['api-key']) &&
is_numeric($_GET['api-key'])) { is_numeric($_GET['api-key'])) {
$result = \Mailcow\ApiKey\ApiKeyModule::fromGlobals()->api->getKey($_GET['api-key']); $result = \Mailcow\ApiKey\ApiKeyModule::fromGlobals()->api->getKey($_GET['api-key']);
-1
View File
@@ -74,7 +74,6 @@ $globalVariables = [
'lang_danger' => json_encode($lang['danger']), 'lang_danger' => json_encode($lang['danger']),
'lang_syncjobs' => json_encode($lang['syncjobs']), 'lang_syncjobs' => json_encode($lang['syncjobs']),
'lang_groups' => json_encode($lang['groups']), 'lang_groups' => json_encode($lang['groups']),
'lang_mail_policy' => json_encode($lang['mail_policy'] ?? []),
'docker_timeout' => $DOCKER_TIMEOUT, 'docker_timeout' => $DOCKER_TIMEOUT,
'session_lifetime' => (int)$SESSION_LIFETIME, 'session_lifetime' => (int)$SESSION_LIFETIME,
'csrf_token' => $_SESSION['CSRF']['TOKEN'], 'csrf_token' => $_SESSION['CSRF']['TOKEN'],
+7
View File
@@ -601,6 +601,13 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
// validate mailcow_password // validate mailcow_password
$mailcow_password = $user_res['attributes']['mailcow_password'][0]; $mailcow_password = $user_res['attributes']['mailcow_password'][0];
// Keycloak returns binary LDAP attributes like userPassword Base64 encoded
if (substr($mailcow_password, 0, 1) != '{') {
$decoded_password = base64_decode($mailcow_password, true);
if ($decoded_password !== false && substr($decoded_password, 0, 1) == '{') {
$mailcow_password = $decoded_password;
}
}
if (!verify_hash($mailcow_password, $pass)) { if (!verify_hash($mailcow_password, $pass)) {
return false; return false;
} }
+7
View File
@@ -35,6 +35,13 @@ function fwdhost($_action, $_data = null) {
); );
return false; return false;
} }
$hosts = array_unique(array_map(function ($host) {
$net = explode('/', $host, 2);
if (filter_var($net[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$net[0] = inet_ntop(inet_pton($net[0]));
}
return implode('/', $net);
}, $hosts));
foreach ($hosts as $host) { foreach ($hosts as $host) {
try { try {
$redis->hSet('WHITELISTED_FWD_HOST', $host, $source); $redis->hSet('WHITELISTED_FWD_HOST', $host, $source);
+31 -7
View File
@@ -1394,6 +1394,18 @@ function set_tfa($_data) {
case "webauthn": case "webauthn":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
// Refuse incomplete WebAuthn registrations. Without this guard a request
// that does not contain the parsed credential data (e.g. a plain form
// POST) would silently insert an empty keyHandle while reporting success.
if (empty($_data['registration']) || empty($_data['registration']->credentialId)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'webauthn_registration_failed'
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')"); VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
$stmt->execute(array( $stmt->execute(array(
@@ -1416,11 +1428,11 @@ function set_tfa($_data) {
// Block TFA removal if force_tfa policy is active // Block TFA removal if force_tfa policy is active
$is_forced_tfa = false; $is_forced_tfa = false;
if ($_SESSION['mailcow_cc_role'] === 'user') { if ($_SESSION['mailcow_cc_role'] === 'user') {
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
$stmt_check->execute(array($username)); $stmt_check->execute(array($username));
$is_forced_tfa = ($stmt_check->fetchColumn() == '1'); $is_forced_tfa = ($stmt_check->fetchColumn() == '1');
} else { } else {
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
$stmt_check->execute(array($username)); $stmt_check->execute(array($username));
$is_forced_tfa = ($stmt_check->fetchColumn() == '1'); $is_forced_tfa = ($stmt_check->fetchColumn() == '1');
} }
@@ -1687,11 +1699,11 @@ function unset_tfa_key($_data) {
// Block key removal if force_tfa policy is active // Block key removal if force_tfa policy is active
$is_forced_tfa = false; $is_forced_tfa = false;
if ($_SESSION['mailcow_cc_role'] === 'user') { if ($_SESSION['mailcow_cc_role'] === 'user') {
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?");
$stmt_check->execute(array($username)); $stmt_check->execute(array($username));
$is_forced_tfa = ($stmt_check->fetchColumn() == '1'); $is_forced_tfa = ($stmt_check->fetchColumn() == '1');
} else { } else {
$stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?"); $stmt_check = $pdo->prepare("SELECT JSON_VALUE(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?");
$stmt_check->execute(array($username)); $stmt_check->execute(array($username));
$is_forced_tfa = ($stmt_check->fetchColumn() == '1'); $is_forced_tfa = ($stmt_check->fetchColumn() == '1');
} }
@@ -2420,7 +2432,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
case "use_ssl": case "use_ssl":
case "use_tls": case "use_tls":
case "login_provisioning": case "login_provisioning":
case "ignore_ssl_errors": case "ignore_ssl_error":
$settings[$row["key"]] = boolval($row["value"]); $settings[$row["key"]] = boolval($row["value"]);
break; break;
default: default:
@@ -2444,7 +2456,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
if ($settings["authsource"] == "ldap"){ if ($settings["authsource"] == "ldap"){
$settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl']; $settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl'];
$settings['use_tls'] = !isset($settings['use_tls']) ? false : $settings['use_tls']; $settings['use_tls'] = !isset($settings['use_tls']) ? false : $settings['use_tls'];
$settings['ignore_ssl_errors'] = !isset($settings['ignore_ssl_errors']) ? false : $settings['ignore_ssl_errors']; $settings['ignore_ssl_error'] = !isset($settings['ignore_ssl_error']) ? false : $settings['ignore_ssl_error'];
} }
return $settings; return $settings;
break; break;
@@ -3106,7 +3118,17 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
case "get-keycloak-admin-token": case "get-keycloak-admin-token":
// get access_token for service account of mailcow client // get access_token for service account of mailcow client
if ($iam_settings['authsource'] !== 'keycloak') return false; if ($iam_settings['authsource'] !== 'keycloak') return false;
if (isset($iam_settings['access_token'])) { // only introspect a cached access_token that is not (about to be) expired,
// introspecting an expired token makes Keycloak log INTROSPECT_TOKEN_ERROR
$token_not_expired = false;
if (!empty($iam_settings['access_token'])) {
$token_parts = explode('.', $iam_settings['access_token']);
if (count($token_parts) === 3) {
$token_payload = json_decode(base64_decode(strtr($token_parts[1], '-_', '+/')), true);
$token_not_expired = is_array($token_payload) && isset($token_payload['exp']) && intval($token_payload['exp']) > time() + 30;
}
}
if ($token_not_expired) {
// check if access_token is valid // check if access_token is valid
$url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect"; $url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect";
$req = http_build_query(array( $req = http_build_query(array(
@@ -3157,6 +3179,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
':key' => 'access_token', ':key' => 'access_token',
':value' => $res['access_token'] ':value' => $res['access_token']
)); ));
// keep the cached token in sync, so subsequent calls in the same run reuse it
$iam_settings['access_token'] = $res['access_token'];
return $res['access_token']; return $res['access_token'];
break; break;
} }
+45 -6
View File
@@ -2432,7 +2432,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if ($address != $is_now['address']) { if ($address != $is_now['address']) {
// Send-as grants of groups follow the address // Send-as grants of groups follow the address
\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->addressRenamed($is_now['address'], $address); \Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->addressRenamed($is_now['address'], $address);
\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->addressRenamed($is_now['address'], $address);
} }
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -2525,6 +2524,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain, ':domain' => $domain,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -2693,6 +2696,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain, ':domain' => $domain,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -3171,6 +3178,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':username' => $username, ':username' => $username,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
@@ -3296,8 +3307,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'mailbox_group_imap_grant' => ['owner'], 'mailbox_group_imap_grant' => ['owner'],
'mailbox_group_sogo_grant' => ['owner'], 'mailbox_group_sogo_grant' => ['owner'],
'mailbox_group_sogo_acl' => ['c_uid'], 'mailbox_group_sogo_acl' => ['c_uid'],
'mailbox_group_subscription' => ['username', 'owner'], 'mailbox_group_subscription' => ['username', 'owner']
'mail_policy_member' => ['username']
]; ];
foreach ($tables as $table => $columns) { foreach ($tables as $table => $columns) {
foreach ($columns as $column) { foreach ($columns as $column) {
@@ -3313,8 +3323,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
\Mailcow\Imapsync\ImapsyncModule::fromGlobals()->sources->onAccountRenamed($old_username, $new_username); \Mailcow\Imapsync\ImapsyncModule::fromGlobals()->sources->onAccountRenamed($old_username, $new_username);
// Group memberships and send-as grants follow, group aliases are rebuilt // Group memberships and send-as grants follow, group aliases are rebuilt
\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->hooks->onMailboxRenamed($old_username, $new_username); \Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->hooks->onMailboxRenamed($old_username, $new_username);
// Address entries of mail policies follow
\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->addressRenamed($old_username, $new_username);
// Update c_uid, c_name and mail in _sogo_static_view table // Update c_uid, c_name and mail in _sogo_static_view table
$pdo->prepare("UPDATE _sogo_static_view SET c_uid = :new_username, c_name = :new_username2, mail = :new_username3 WHERE c_uid = :old_username") $pdo->prepare("UPDATE _sogo_static_view SET c_uid = :new_username, c_name = :new_username2, mail = :new_username3 WHERE c_uid = :old_username")
@@ -3992,6 +4000,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)')); $stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) { while ($row = array_shift($rows)) {
if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
continue;
}
// Keep old array for backward compatibility // Keep old array for backward compatibility
$data['fixed_sender_aliases'][] = $row['address']; $data['fixed_sender_aliases'][] = $row['address'];
// Split into allowed/blocked for proper display // Split into allowed/blocked for proper display
@@ -4927,6 +4938,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else if ($SaslLogs['service'] == 'SSO') { else if ($SaslLogs['service'] == 'SSO') {
$last_sso_login = strtotime($SaslLogs['datetime']); $last_sso_login = strtotime($SaslLogs['datetime']);
} }
else if ($SaslLogs['service'] == 'SOGO') {
$last_sogo_login = strtotime($SaslLogs['datetime']);
}
} }
if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_imap_login = 0; $last_imap_login = 0;
@@ -4940,10 +4954,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_sso_login = 0; $last_sso_login = 0;
} }
if (!isset($last_sogo_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_sogo_login = 0;
}
$mailboxdata['last_imap_login'] = $last_imap_login; $mailboxdata['last_imap_login'] = $last_imap_login;
$mailboxdata['last_smtp_login'] = $last_smtp_login; $mailboxdata['last_smtp_login'] = $last_smtp_login;
$mailboxdata['last_pop3_login'] = $last_pop3_login; $mailboxdata['last_pop3_login'] = $last_pop3_login;
$mailboxdata['last_sso_login'] = $last_sso_login; $mailboxdata['last_sso_login'] = $last_sso_login;
$mailboxdata['last_sogo_login'] = $last_sogo_login;
if (!isset($_extra) || $_extra != 'reduced') { if (!isset($_extra) || $_extra != 'reduced') {
$rl = ratelimit('get', 'mailbox', $_data); $rl = ratelimit('get', 'mailbox', $_data);
@@ -4981,7 +4999,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array(':username' => $_data)); $stmt->execute(array(':username' => $_data));
$sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($sender_acl_rows as $sender_acl_row) { foreach ($sender_acl_rows as $sender_acl_row) {
$mailboxdata['sender_acl'][] = $sender_acl_row['send_as']; $send_as = $sender_acl_row['send_as'];
if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $send_as != '*') {
$in_scope = (substr($send_as, 0, 1) == '@')
? hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr($send_as, 1))
: hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $send_as);
if (!$in_scope) {
continue;
}
}
$mailboxdata['sender_acl'][] = $send_as;
} }
} }
$stmt = $pdo->prepare("SELECT `tag_name` $stmt = $pdo->prepare("SELECT `tag_name`
@@ -5920,6 +5947,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain, ':domain' => $domain,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
if ($stmt->rowCount() > 0) {
$stmt = $pdo->prepare("UPDATE `domain` SET `modified` = NOW() WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
}
} }
} }
@@ -5972,6 +6005,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':username' => $username, ':username' => $username,
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
if ($stmt->rowCount() > 0) {
$stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = NOW() WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
}
} }
} }
+2 -84
View File
@@ -491,86 +491,6 @@ function init_db_schema()
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
), ),
// Mail policies: whom the assigned mailboxes and groups may send to and receive from (Mailcow\MailPolicy)
"mail_policy" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"domain" => "VARCHAR(255) NOT NULL",
"name" => "VARCHAR(100) NOT NULL",
"description" => "VARCHAR(255) NOT NULL DEFAULT ''",
// all, internal or list; the entries are JSON lists of addresses, domains and group:<id>
"send_mode" => "VARCHAR(16) NOT NULL DEFAULT 'all'",
"send_allow" => "MEDIUMTEXT NOT NULL",
"send_block" => "MEDIUMTEXT NOT NULL",
"receive_mode" => "VARCHAR(16) NOT NULL DEFAULT 'all'",
"receive_allow" => "MEDIUMTEXT NOT NULL",
"receive_block" => "MEDIUMTEXT NOT NULL",
"reject_text" => "VARCHAR(255) NOT NULL DEFAULT ''",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"unique" => array(
"domain_name" => array("domain", "name")
),
"fkey" => array(
"fk_mail_policy_domain" => array(
"col" => "domain",
"ref" => "domain.domain",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
// a mailbox or a group (its members and its address) a policy is assigned to
"mail_policy_member" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"policy_id" => "INT NOT NULL",
"username" => "VARCHAR(255) NULL DEFAULT NULL",
"group_id" => "INT NULL DEFAULT NULL"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"unique" => array(
"policy_mailbox" => array("policy_id", "username"),
"policy_group" => array("policy_id", "group_id")
),
"key" => array(
"username" => array("username"),
"group_id" => array("group_id")
),
"fkey" => array(
"fk_mail_policy_member_policy" => array(
"col" => "policy_id",
"ref" => "mail_policy.id",
"delete" => "CASCADE",
"update" => "NO ACTION"
),
"fk_mail_policy_member_mailbox" => array(
"col" => "username",
"ref" => "mailbox.username",
"delete" => "CASCADE",
"update" => "NO ACTION"
),
"fk_mail_policy_member_group" => array(
"col" => "group_id",
"ref" => "mailbox_group.id",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"mailbox_group_imap_grant" => array( "mailbox_group_imap_grant" => array(
"cols" => array( "cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT", "id" => "INT NOT NULL AUTO_INCREMENT",
@@ -783,8 +703,7 @@ function init_db_schema()
"group_id" => array("group_id") "group_id" => array("group_id")
), ),
"key" => array( "key" => array(
"domain" => array("domain"), "domain" => array("domain")
"internal" => array("internal", "active")
), ),
"fkey" => array( "fkey" => array(
"fk_alias_mailbox_group" => array( "fk_alias_mailbox_group" => array(
@@ -1134,8 +1053,7 @@ function init_db_schema()
"domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'", "domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'",
"domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'", "domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'",
"alias_external_goto" => "TINYINT(1) NOT NULL DEFAULT '1'", "alias_external_goto" => "TINYINT(1) NOT NULL DEFAULT '1'",
"mailbox_groups" => "TINYINT(1) NOT NULL DEFAULT '1'", "mailbox_groups" => "TINYINT(1) NOT NULL DEFAULT '1'"
"mail_policies" => "TINYINT(1) NOT NULL DEFAULT '0'"
), ),
"keys" => array( "keys" => array(
"primary" => array( "primary" => array(
@@ -1,132 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\MailPolicy\Controller;
use Mailcow\Core\AccessDenied;
use Mailcow\Core\ApiResponse;
use Mailcow\Core\UserError;
use Mailcow\MailPolicy\Service\PolicyEngine;
use Mailcow\MailPolicy\Service\PolicyService;
use PDOException;
/**
* HTTP API adapter for json_api.php and edit.php (routes `mail-policy`), and the answer to the Postfix policy
* service (data/conf/rspamd/dynmaps/mailpolicy.php). Messages are logged as ['mail_policy', action, 'policy', data].
*/
class MailPolicyApiController
{
public function __construct(private PolicyService $policies, private PolicyEngine $engine, private ApiResponse $response)
{
}
/** POST add/mail-policy */
public function addPolicy(array $attr): bool
{
return $this->handle('add', $attr, function (array $log) use ($attr): bool {
$policy = $this->policies->create($attr);
return $this->response->success(['mail_policy_added', htmlspecialchars($policy->name)], $log);
});
}
/** POST edit/mail-policy: one message per id */
public function editPolicies(?array $items, array $attr): bool
{
$data = array_merge(['id' => $items], $attr);
return $this->handle('edit', $data, function (array $log) use ($data, $attr): bool {
$this->policies->requireEditor();
return $this->batch((array)($data['id'] ?? []), $log,
fn(int $id): array => ['mail_policy_modified', htmlspecialchars($this->policies->update($id, $attr)->name)]);
});
}
/** POST delete/mail-policy: one message per id */
public function deletePolicies(array $items): bool
{
return $this->handle('delete', ['id' => $items], function (array $log) use ($items): bool {
$this->policies->requireEditor();
return $this->batch($items, $log, fn(int $id): array => ['mail_policy_removed', htmlspecialchars($this->policies->delete($id)->name)]);
});
}
/** GET mail-policy/all[/<domain,domain>] */
public function getPolicies(?string $domains = null): array
{
$list = array_values(array_filter(array_map('trim', explode(',', (string)$domains)), 'strlen'));
return array_values(array_filter(array_map(fn(int $id): array => $this->getPolicy($id), $this->policies->visibleIds($list))));
}
/** GET mail-policy/<id>, also edit.php */
public function getPolicy(mixed $id): array
{
$id = $this->toId($id);
$policy = ($id === null) ? null : $this->policies->get($id);
return ($policy === null) ? [] : $this->policies->toArray($policy);
}
/** GET mail-policy/test/<sender>/<recipient>[/login]: would the mail be accepted? Sender "-" = null sender; [] when not allowed to ask */
public function test(string $sender, string $recipient, bool $loggedIn): array
{
try {
return $this->policies->test(($sender === '-') ? '' : $sender, $recipient, $loggedIn);
} catch (UserError) {
return [];
}
}
/** functions.mailbox.inc.php: a mailbox or alias was renamed */
public function addressRenamed(string $old, string $new): void
{
$this->policies->addressRenamed($old, $new);
}
/** Answer for the Postfix policy service: DUNNO or the rejection */
public function check(string $sender, string $recipient, string $login, int $port, string $helo = ''): string
{
return $this->engine->check($sender, $recipient, $login, $port, $helo)->action();
}
/**
* Runs one write call: $work gets the log entry and returns the result for process_*_return().
* A UserError becomes a danger message, a database error stops the call with one `mysql_error` message.
*/
private function handle(string $action, mixed $data, callable $work): bool
{
$log = ['mail_policy', $action, 'policy', $data];
try {
$result = $work($log);
} catch (UserError $e) {
$result = $this->response->error($e, $log);
} catch (PDOException $e) {
$result = $this->response->danger(['mysql_error', $e->getMessage()], $log);
}
$this->response->flush();
return $result;
}
/** One message per id: the success message returned by $work(id), or the UserError */
private function batch(array $ids, array $log, callable $work): bool
{
foreach ($ids as $id) {
try {
$this->response->success($work($this->requireId($id)), $log);
} catch (UserError $e) {
$this->response->error($e, $log);
}
}
return true;
}
/** Integer id from request input ("12", 12); null for anything else */
private function toId(mixed $id): ?int
{
return (is_numeric($id) && (float)$id == (int)(float)$id) ? (int)(float)$id : null;
}
/** @throws AccessDenied for ids that cannot exist */
private function requireId(mixed $id): int
{
return $this->toId($id) ?? throw new AccessDenied();
}
}
@@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\MailPolicy;
use Mailcow\Core\ApiResponse;
use Mailcow\Core\Context;
use Mailcow\MailPolicy\Controller\MailPolicyApiController;
use Mailcow\MailPolicy\Repository\LookupRepository;
use Mailcow\MailPolicy\Repository\PolicyRepository;
use Mailcow\MailPolicy\Service\PolicyEngine;
use Mailcow\MailPolicy\Service\PolicyService;
use PDO;
/**
* Composition root of the mail policies: whom mailboxes and groups may send to and receive from, checked by
* Postfix at RCPT TO through the policy service (mailcow-policy in master.cf, data/conf/rspamd/dynmaps/mailpolicy.php).
*
* process_get_return(MailPolicyModule::fromGlobals()->api->getPolicies());
* echo MailPolicyModule::create($pdo, Context::system())->api->check($sender, $recipient, $login, $port); // policy service
*/
class MailPolicyModule
{
public function __construct(
public readonly PolicyService $policies,
public readonly PolicyEngine $engine,
public readonly MailPolicyApiController $api
) {
}
public static function create(PDO $pdo, Context $context): self
{
$engine = new PolicyEngine(new LookupRepository($pdo));
$policies = new PolicyService(new PolicyRepository($pdo), $engine, $context);
return new self($policies, $engine, new MailPolicyApiController($policies, $engine, new ApiResponse()));
}
/** Legacy entry point for procedural callers: reads $pdo and $_SESSION */
public static function fromGlobals(?Context $context = null): self
{
return self::create($GLOBALS['pdo'], $context ?? Context::fromSession($_SESSION ?? []));
}
}
@@ -1,56 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\MailPolicy\Model;
/**
* Result of the policy check for one recipient: allowed, refused by the send or receive part of a policy, or refused
* because the recipient is written in a form the check cannot follow (routing through another address).
*/
class Decision
{
private const TEXT = [
'send' => 'Sending to this recipient is not allowed by the mail policy of the sender',
'receive' => 'This recipient does not accept mail from this sender',
'address' => 'This form of the recipient address is not allowed by the mail policy',
];
private function __construct(public readonly bool $allowed, public readonly string $side = '', public readonly ?Policy $policy = null)
{
}
public static function allow(): self
{
return new self(true);
}
/** $side send or receive; without $policy the default text is used */
public static function deny(string $side, ?Policy $policy = null): self
{
return new self(false, $side, $policy);
}
/** The recipient routes through another address (user%domain, domain!user, a second @) */
public static function routedAddress(): self
{
return new self(false, 'address');
}
/** Text of the rejection: the text of the policy or the default */
public function text(): string
{
return ($this->policy !== null && $this->policy->rejectText !== '') ? $this->policy->rejectText : self::defaultText($this->side);
}
public static function defaultText(string $side): string
{
return self::TEXT[$side] ?? '';
}
/** Answer for the Postfix policy protocol */
public function action(): string
{
return $this->allowed ? 'DUNNO' : '550 5.7.1 ' . $this->text();
}
}
@@ -1,111 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\MailPolicy\Model;
/**
* A mail policy (table `mail_policy`): whom its mailboxes and groups may send to and receive from.
*
* Each direction has a mode: `all` (only the blocked entries are refused), `internal` (the own domain and its
* alias domains, plus the allowed entries) or `list` (only the allowed entries). Blocked entries are always
* refused. Entries are addresses, domains or `group:<id>`. A sender counts as internal only when it is logged in
* to this mailcow (SASL) with a mailbox of the domain, never because of its address.
*/
class Policy
{
public const MODES = ['all', 'internal', 'list'];
public ?int $id = null;
public string $domain = '';
public string $name = '';
public string $description = '';
public string $sendMode = 'all';
/** @var string[] */
public array $sendAllow = [];
/** @var string[] */
public array $sendBlock = [];
public string $receiveMode = 'all';
/** @var string[] */
public array $receiveAllow = [];
/** @var string[] */
public array $receiveBlock = [];
/** Text of the rejection, '' = default text */
public string $rejectText = '';
public int $active = 1;
/** @var string[] usernames the policy is assigned to */
public array $mailboxes = [];
/** @var int[] groups the policy is assigned to: their members and their address */
public array $groups = [];
public ?string $created = null;
public ?string $modified = null;
/** Row of `mail_policy`; assignments are set by the repository */
public static function fromRow(array $row): self
{
$policy = new self();
$policy->id = (int)$row['id'];
$policy->domain = (string)$row['domain'];
$policy->name = (string)$row['name'];
$policy->description = (string)($row['description'] ?? '');
$policy->sendMode = (string)$row['send_mode'];
$policy->sendAllow = self::list($row['send_allow'] ?? null);
$policy->sendBlock = self::list($row['send_block'] ?? null);
$policy->receiveMode = (string)$row['receive_mode'];
$policy->receiveAllow = self::list($row['receive_allow'] ?? null);
$policy->receiveBlock = self::list($row['receive_block'] ?? null);
$policy->rejectText = (string)($row['reject_text'] ?? '');
$policy->active = (int)$row['active'];
$policy->created = $row['created'] ?? null;
$policy->modified = $row['modified'] ?? null;
return $policy;
}
/** Column values for `mail_policy` */
public function toRow(): array
{
return [
'domain' => $this->domain,
'name' => $this->name,
'description' => $this->description,
'send_mode' => $this->sendMode,
'send_allow' => json_encode($this->sendAllow),
'send_block' => json_encode($this->sendBlock),
'receive_mode' => $this->receiveMode,
'receive_allow' => json_encode($this->receiveAllow),
'receive_block' => json_encode($this->receiveBlock),
'reject_text' => $this->rejectText,
'active' => $this->active,
];
}
/** Entry of the API and the edit page */
public function toArray(): array
{
return [
'id' => $this->id,
'domain' => $this->domain,
'name' => $this->name,
'description' => $this->description,
'send_mode' => $this->sendMode,
'send_allow' => $this->sendAllow,
'send_block' => $this->sendBlock,
'receive_mode' => $this->receiveMode,
'receive_allow' => $this->receiveAllow,
'receive_block' => $this->receiveBlock,
'reject_text' => $this->rejectText,
'active' => $this->active,
'mailboxes' => $this->mailboxes,
'groups' => $this->groups,
'created' => $this->created,
'modified' => $this->modified,
];
}
/** @return string[] entries of a JSON list column */
private static function list(mixed $json): array
{
$list = is_string($json) ? json_decode($json, true) : null;
return is_array($list) ? array_values(array_filter($list, 'is_string')) : [];
}
}
@@ -1,141 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\MailPolicy\Repository;
use Mailcow\Core\Repository;
use Mailcow\MailPolicy\Model\Policy;
/**
* Read-only lookups of the policy check: mailboxes, aliases, alias domains, groups and the active policies of a
* mailbox or group. Addresses are passed in lower case.
*/
class LookupRepository extends Repository
{
/** @return array{username: string, domain: string}|null a regular mailbox that is not disabled */
public function mailbox(string $username): ?array
{
$row = $this->query("SELECT `username`, `domain` FROM `mailbox` WHERE `username` = :username AND `kind` = '' AND `active` != '0'",
['username' => $username])->fetch();
return is_array($row) ? ['username' => strtolower((string)$row['username']), 'domain' => strtolower((string)$row['domain'])] : null;
}
/** Target domain of an active alias domain */
public function aliasDomainTarget(string $domain): ?string
{
$target = $this->query("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'",
['domain' => $domain])->fetchColumn();
return is_string($target) ? strtolower($target) : null;
}
/** A domain or alias domain Postfix delivers itself (as mysql_virtual_domains_maps.cf: active, no backup MX) */
public function isLocalDomain(string $domain): bool
{
return (bool)$this->query("SELECT 1 FROM `alias_domain` WHERE `alias_domain` = :d1 AND `active` = '1'
UNION SELECT 1 FROM `domain` WHERE `domain` = :d2 AND `active` = '1' AND `backupmx` = '0'",
['d1' => $domain, 'd2' => $domain])->fetchColumn();
}
/**
* Stored spelling of a mailbox, alias or temporary alias address. MySQL compares without case and accents, so
* Postfix delivers mail for chéf@ to chef@.
*/
public function storedAddress(string $address): ?string
{
$stored = $this->query("SELECT `username` FROM `mailbox` WHERE `username` = :a1 UNION SELECT `address` FROM `alias` WHERE `address` = :a2
UNION SELECT `address` FROM `spamalias` WHERE `address` = :a3 LIMIT 1", ['a1' => $address, 'a2' => $address, 'a3' => $address])->fetchColumn();
return is_string($stored) ? strtolower($stored) : null;
}
/** Stored spelling of a domain or alias domain, see storedAddress() */
public function storedDomain(string $domain): ?string
{
$stored = $this->query("SELECT `domain` FROM `domain` WHERE `domain` = :d1 UNION SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain` = :d2 LIMIT 1",
['d1' => $domain, 'd2' => $domain])->fetchColumn();
return is_string($stored) ? strtolower($stored) : null;
}
/** Active group with this distribution address */
public function groupByAddress(string $address): ?int
{
$id = $this->query("SELECT `id` FROM `mailbox_group` WHERE `address` = :address AND `active` = '1'", ['address' => $address])->fetchColumn();
return ($id === false) ? null : (int)$id;
}
public function groupAddress(int $id): ?string
{
$address = $this->query("SELECT `address` FROM `mailbox_group` WHERE `id` = :id AND `active` = '1'", ['id' => $id])->fetchColumn();
return is_string($address) && $address !== '' ? strtolower($address) : null;
}
/** Member of an active group */
public function isGroupMember(int $groupId, string $username): bool
{
return (bool)$this->query("SELECT 1 FROM `mailbox_group_member` `gm` JOIN `mailbox_group` `g` ON `g`.`id` = `gm`.`group_id`
WHERE `gm`.`group_id` = :id AND `gm`.`username` = :username AND `g`.`active` = '1'",
['id' => $groupId, 'username' => $username])->fetchColumn();
}
/**
* What Postfix' virtual alias maps find for this key, in their order: an alias (also the own alias of a mailbox
* and the alias of a group address) or a temporary alias.
*
* @return array{kind: string, domain?: string, internal?: bool, owner?: string}|null
*/
public function aliasTarget(string $address): ?array
{
$alias = $this->query("SELECT `domain`, `internal` FROM `alias` WHERE `address` = :address AND `active` IN ('1', '2')",
['address' => $address])->fetch();
if (is_array($alias)) {
return ['kind' => 'alias', 'domain' => strtolower((string)$alias['domain']), 'internal' => $alias['internal'] == '1'];
}
$owner = $this->query("SELECT `goto` FROM `spamalias` WHERE `address` = :address AND (`validity` >= UNIX_TIMESTAMP() OR `permanent` != 0)",
['address' => $address])->fetchColumn();
return is_string($owner) ? ['kind' => 'temporary', 'owner' => strtolower($owner)] : null;
}
/** An active catch-all alias of the domain has the internal flag (applies to the whole domain) */
public function internalCatchAll(string $domain): bool
{
return (bool)$this->query("SELECT 1 FROM `alias` WHERE `address` = :address AND `internal` = '1' AND `active` IN ('1', '2')",
['address' => '@' . $domain])->fetchColumn();
}
/** An active policy with members restricts sending */
public function hasSendRules(): bool
{
return (bool)$this->query("SELECT 1 FROM `mail_policy` `p` JOIN `mail_policy_member` `pm` ON `pm`.`policy_id` = `p`.`id`
WHERE `p`.`active` = '1' AND (`p`.`send_mode` != 'all' OR `p`.`send_block` != '[]') LIMIT 1")->fetchColumn();
}
/**
* @return Policy[] active policies of the mailbox: assigned to it or to an active group it is a member of; only
* policies of its own domain, so a group with members of other domains cannot restrict them
*/
public function policiesForMailbox(string $username): array
{
return $this->policies("SELECT DISTINCT `p`.* FROM `mail_policy` `p`
JOIN `mail_policy_member` `pm` ON `pm`.`policy_id` = `p`.`id`
JOIN `mailbox` `m` ON `m`.`username` = :u1 AND `m`.`domain` = `p`.`domain`
WHERE `p`.`active` = '1' AND (`pm`.`username` = :u2 OR `pm`.`group_id` IN (
SELECT `gm`.`group_id` FROM `mailbox_group_member` `gm` JOIN `mailbox_group` `g` ON `g`.`id` = `gm`.`group_id`
WHERE `gm`.`username` = :u3 AND `g`.`active` = '1'))
ORDER BY `p`.`id`", ['u1' => $username, 'u2' => $username, 'u3' => $username]);
}
/** @return Policy[] active policies assigned to the group */
public function policiesForGroup(int $groupId): array
{
return $this->policies("SELECT DISTINCT `p`.* FROM `mail_policy` `p`
JOIN `mail_policy_member` `pm` ON `pm`.`policy_id` = `p`.`id`
WHERE `p`.`active` = '1' AND `pm`.`group_id` = :id
ORDER BY `p`.`id`", ['id' => $groupId]);
}
/** @return Policy[] */
private function policies(string $sql, array $params): array
{
return array_map(fn(array $row): Policy => Policy::fromRow($row), $this->query($sql, $params)->fetchAll());
}
}
@@ -1,148 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\MailPolicy\Repository;
use Mailcow\Core\Context;
use Mailcow\Core\Repository;
use Mailcow\MailPolicy\Model\Policy;
use PDO;
/**
* Tables `mail_policy` and `mail_policy_member` (the mailboxes and groups a policy is assigned to), plus the
* lookups the editing needs. Assignments go with their mailbox, group or domain (foreign keys, ON DELETE CASCADE).
*/
class PolicyRepository extends Repository
{
public function find(int $id): ?Policy
{
$row = $this->query("SELECT * FROM `mail_policy` WHERE `id` = :id", ['id' => $id])->fetch();
return is_array($row) ? $this->withMembers(Policy::fromRow($row)) : null;
}
/**
* @param string[] $domains limit to these domains, [] = all visible
* @return int[] ids of the policies visible to the caller, ordered by domain and name
*/
public function visibleIds(Context $context, array $domains = []): array
{
if ($context->hasAdminRights()) {
[$visible, $params] = ['1=1', []];
} elseif ($context->isDomainAdmin()) {
[$visible, $params] = ["`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :vis_da)",
['vis_da' => $context->username]];
} else {
return [];
}
$sql = "SELECT `id` FROM `mail_policy` WHERE $visible";
if (!empty($domains)) {
$names = [];
foreach (array_values($domains) as $i => $domain) {
$names[] = ':dom' . $i;
$params['dom' . $i] = $domain;
}
$sql .= " AND `domain` IN (" . implode(', ', $names) . ")";
}
return array_map('intval', $this->query($sql . " ORDER BY `domain`, `name`", $params)->fetchAll(PDO::FETCH_COLUMN));
}
public function nameExists(string $domain, string $name, ?int $exceptId = null): bool
{
return (bool)$this->query("SELECT 1 FROM `mail_policy` WHERE `domain` = :domain AND `name` = :name AND `id` != :id",
['domain' => $domain, 'name' => $name, 'id' => $exceptId ?? 0])->fetchColumn();
}
/** Inserts or updates the policy with its assignments; returns its id */
public function save(Policy $policy): int
{
return $this->transaction(function () use ($policy): int {
if ($policy->id === null) {
$policy->id = $this->insertRow('mail_policy', $policy->toRow());
} else {
$this->updateRow('mail_policy', $policy->id, $policy->toRow());
$this->query("DELETE FROM `mail_policy_member` WHERE `policy_id` = :id", ['id' => $policy->id]);
}
foreach ($policy->mailboxes as $username) {
$this->insertRow('mail_policy_member', ['policy_id' => $policy->id, 'username' => $username]);
}
foreach ($policy->groups as $groupId) {
$this->insertRow('mail_policy_member', ['policy_id' => $policy->id, 'group_id' => $groupId]);
}
return $policy->id;
});
}
public function delete(int $id): void
{
$this->query("DELETE FROM `mail_policy` WHERE `id` = :id", ['id' => $id]);
}
/** Address entries of all lists follow a renamed mailbox or alias */
public function renameAddress(string $old, string $new): void
{
$this->transaction(function () use ($old, $new): void {
$columns = ['send_allow', 'send_block', 'receive_allow', 'receive_block'];
foreach ($this->query("SELECT `id`, `" . implode('`, `', $columns) . "` FROM `mail_policy`")->fetchAll() as $row) {
$changed = [];
foreach ($columns as $column) {
$entries = json_decode((string)$row[$column], true);
if (is_array($entries) && in_array($old, $entries, true)) {
$changed[$column] = json_encode(array_values(array_unique(array_map(fn(string $entry): string => ($entry === $old) ? $new : $entry, $entries))));
}
}
if ($changed !== []) {
$this->updateRow('mail_policy', (int)$row['id'], $changed);
}
}
});
}
/** A primary domain (policies never belong to alias domains) */
public function isDomain(string $domain): bool
{
return (bool)$this->query("SELECT 1 FROM `domain` WHERE `domain` = :domain", ['domain' => $domain])->fetchColumn();
}
/** Domain of a regular mailbox, null if there is none */
public function mailboxDomain(string $username): ?string
{
$domain = $this->query("SELECT `domain` FROM `mailbox` WHERE `username` = :username AND `kind` = ''", ['username' => $username])->fetchColumn();
return is_string($domain) ? $domain : null;
}
public function groupDomain(int $id): ?string
{
$domain = $this->query("SELECT `domain` FROM `mailbox_group` WHERE `id` = :id", ['id' => $id])->fetchColumn();
return is_string($domain) ? $domain : null;
}
/** @return array[] id, name, domain, address of the groups in these domains (choices of the forms) */
public function groupsIn(array $domains): array
{
if (empty($domains)) {
return [];
}
$names = [];
$params = [];
foreach (array_values($domains) as $i => $domain) {
$names[] = ':dom' . $i;
$params['dom' . $i] = $domain;
}
return $this->query("SELECT `id`, `name`, `domain`, `address` FROM `mailbox_group` WHERE `domain` IN (" . implode(', ', $names) . ")
ORDER BY `domain`, `name`", $params)->fetchAll();
}
private function withMembers(Policy $policy): Policy
{
foreach ($this->query("SELECT `username`, `group_id` FROM `mail_policy_member` WHERE `policy_id` = :id ORDER BY `username`, `group_id`",
['id' => $policy->id])->fetchAll() as $row) {
if ($row['username'] !== null) {
$policy->mailboxes[] = (string)$row['username'];
} elseif ($row['group_id'] !== null) {
$policy->groups[] = (int)$row['group_id'];
}
}
return $policy;
}
}
@@ -1,327 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\MailPolicy\Service;
use Mailcow\MailPolicy\Model\Decision;
use Mailcow\MailPolicy\Model\Policy;
use Mailcow\MailPolicy\Repository\LookupRepository;
/**
* Policy check of one recipient, asked by Postfix at RCPT TO (data/conf/rspamd/dynmaps/mailpolicy.php).
*
* Send part: the policies of the logged-in mailbox (assigned to it or to its groups) and of the mailbox the
* sender address belongs to. Receive part: what Postfix delivers the recipient to, looked up in the order of its
* virtual alias maps (address, address without +tag, mailbox of an alias domain): the policies of the mailbox, of
* the group address or of the owner of a temporary alias, and the `internal` flag of the alias (a catch-all alias
* with the flag covers its whole domain). Mail sent to an alias or group reaches its members without their own
* policies. Every policy must allow the mail.
*
* Internal: a recipient in the domain of the policy or one of its alias domains; a sender logged in with a
* mailbox of that domain. Entries of a local domain or address count for logged-in senders only, so they cannot
* be forged; block entries also match the envelope sender. External entries are compared with the envelope sender.
* Addresses are compared the way Postfix finds them: lower case, without trailing dot, in their stored spelling
* (MySQL ignores accents). Recipients routed through localhost (user%domain@localhost, domain!user@localhost,
* user@domain@localhost) are refused, Postfix would deliver them to the routed address.
*
* Dovecot submits Sieve redirects, vacation replies and notifications on port 588 without login, SOGo sends
* reminders with a service login: mail with the address of a mailbox counts as sent by that mailbox. Other mail of
* these services (bounces redirected with the null sender, Sieve rejects) has no known sender: it is checked like
* mail from outside and does not leave the mailcow while any policy restricts sending. Quota warnings pass.
*/
class PolicyEngine
{
/** Ports of mailcow's own deliveries: quarantine release (590), BCC and archive copies (591) */
private const INTERNAL_PORTS = [590, 591];
/** Submission of SOGo and Dovecot, without login reachable from mailcow's own network only */
private const SERVICE_PORT = 588;
/** Domain of the Dovecot master login of mailcow's services (SOGo reminders) */
private const SERVICE_LOGIN_DOMAIN = 'mailcow.local';
/** HELO of the quota warning copies of Dovecot (quota_notify.py), configured by admins */
private const QUOTA_HELO = 'quotanotification';
/** Recipient without domain every server accepts (RFC 5321) */
private const POSTMASTER = 'postmaster';
/** Postfix delivers these destinations itself (mydestination), user%domain and domain!user route from there */
private const LOCAL_DESTINATIONS = ['localhost', 'localhost.localdomain'];
/** Lookups of the current check */
private array $cache = [];
public function __construct(private LookupRepository $lookup)
{
}
/** $login: SASL username, '' if not logged in; $port: smtpd port of the request, 0 if unknown; $helo: HELO name */
public function check(string $sender, string $recipient, string $login, int $port = 0, string $helo = ''): Decision
{
$this->cache = [];
$recipient = self::address($recipient);
if (in_array($port, self::INTERNAL_PORTS, true) || $recipient === '' || $recipient === self::POSTMASTER) {
return Decision::allow();
}
if (self::isRouted($recipient)) {
return Decision::routedAddress();
}
$recipient = $this->stored($recipient);
$sender = $this->stored(self::address($sender));
$login = self::address($login);
$serviceLogin = self::domainOf($login) === self::SERVICE_LOGIN_DOMAIN;
$account = null;
if ($login !== '' && !$serviceLogin) {
$account = $this->cached('mailbox', $login);
} elseif ($serviceLogin || $port === self::SERVICE_PORT) {
if ($port === self::SERVICE_PORT && !$serviceLogin && strtolower($helo) === self::QUOTA_HELO) {
return Decision::allow();
}
// without a mailbox sender the sending mailbox is unknown
$account = $this->senderMailbox($sender);
if ($account === null && !$this->cached('isLocalDomain', self::domainOf($recipient)) && $this->lookup->hasSendRules()) {
return Decision::deny('send');
}
}
foreach ($this->sendingPolicies($account, $sender) as $policy) {
if (!$this->allows($policy->sendMode, $policy->sendAllow, $policy->sendBlock, $this->isInternalRecipient($recipient, $policy->domain),
fn(string $entry): bool => $this->recipientMatches($entry, $recipient))) {
return Decision::deny('send', $policy);
}
}
foreach ($this->receivingPolicies($recipient) as $policy) {
if (!$this->allows($policy->receiveMode, $policy->receiveAllow, $policy->receiveBlock,
$account !== null && $account['domain'] === $policy->domain,
fn(string $entry, bool $block): bool => $this->senderMatches($entry, $sender, $account, $block))) {
return Decision::deny('receive', $policy);
}
}
return Decision::allow();
}
/** Blocked entries first; then the mode decides. $matches($entry, $block) */
private function allows(string $mode, array $allow, array $block, bool $internal, callable $matches): bool
{
foreach ($block as $entry) {
if ($matches($entry, true)) {
return false;
}
}
return match ($mode) {
'all' => true,
'internal' => $internal || $this->any($allow, $matches),
'list' => $this->any($allow, $matches),
// an unknown mode lets nothing through
default => false,
};
}
private function any(array $entries, callable $matches): bool
{
foreach ($entries as $entry) {
if ($matches($entry, false)) {
return true;
}
}
return false;
}
/** @return array{username: string, domain: string}|null mailbox of the sender address */
private function senderMailbox(string $sender): ?array
{
return str_contains($sender, '@') ? $this->cached('mailbox', $this->resolve(self::untagged($sender))) : null;
}
/** @return Policy[] of the logged-in mailbox and of the mailbox it sends as */
private function sendingPolicies(?array $account, string $sender): array
{
if ($account === null) {
return [];
}
$policies = $this->lookup->policiesForMailbox($account['username']);
$sentAs = $this->senderMailbox($sender);
if ($sentAs !== null && $sentAs['username'] !== $account['username']) {
$policies = array_merge($policies, $this->lookup->policiesForMailbox($sentAs['username']));
}
return self::unique($policies);
}
/** @return Policy[] of what Postfix delivers the recipient to, see the class comment */
private function receivingPolicies(string $recipient): array
{
// the address, then without +tag; an alias domain maps to the mailbox in its target domain and Postfix
// looks up the result again with the +tag
[$address, $target] = $this->aliasTarget([$recipient, self::untagged($recipient)]);
$resolved = $this->resolve($recipient);
if ($target === null && $resolved !== $recipient) {
[$address, $target] = $this->aliasTarget([$resolved, self::untagged($resolved)]);
}
$address ??= self::untagged($resolved);
if (($target['kind'] ?? '') === 'temporary') {
$address = $target['owner'];
}
$policies = ($this->cached('mailbox', $address) !== null) ? $this->lookup->policiesForMailbox($address) : [];
$group = $this->lookup->groupByAddress($address);
if ($group !== null) {
$policies = array_merge($policies, $this->lookup->policiesForGroup($group));
}
// the `internal` flag of an alias or group: only senders of its domain
if (!empty($target['internal'])) {
$policies[] = $this->internalRule($target['domain']);
}
foreach (array_unique([self::domainOf($recipient), $this->resolveDomain(self::domainOf($recipient))]) as $domain) {
if ($this->lookup->internalCatchAll($domain)) {
$policies[] = $this->internalRule($domain);
}
}
return self::unique($policies);
}
/** @return array{0: ?string, 1: ?array} the first key Postfix' virtual alias maps find, with what they find */
private function aliasTarget(array $keys): array
{
foreach (array_unique($keys) as $key) {
$target = $this->lookup->aliasTarget($key);
if ($target !== null) {
return [$key, $target];
}
}
return [null, null];
}
/** Receive rule of the `internal` flag */
private function internalRule(string $domain): Policy
{
$policy = new Policy();
$policy->domain = $this->resolveDomain($domain);
$policy->receiveMode = 'internal';
return $policy;
}
private function isInternalRecipient(string $recipient, string $domain): bool
{
return $this->resolveDomain(self::domainOf($recipient)) === $domain;
}
private function recipientMatches(string $entry, string $recipient): bool
{
$address = $this->resolve(self::untagged($recipient));
if (str_starts_with($entry, 'group:')) {
$id = (int)substr($entry, 6);
return $this->cached('groupAddress', $id) === $address
|| ($this->cached('mailbox', $address) !== null && $this->cached('isGroupMember', $id, $address));
}
if (str_contains($entry, '@')) {
return $this->resolve($entry) === $address;
}
return $this->resolveDomain(self::domainOf($address)) === $this->resolveDomain($entry);
}
/** $block: a block entry, which also matches the envelope sender (refusing a forged sender is no risk) */
private function senderMatches(string $entry, string $sender, ?array $account, bool $block): bool
{
$address = $this->resolve(self::untagged($sender));
// the sender address of a local entry only counts when logged in: Postfix checked that the login may use it
$useAddress = $account !== null || $block;
if (str_starts_with($entry, 'group:')) {
$id = (int)substr($entry, 6);
return ($account !== null && $this->cached('isGroupMember', $id, $account['username']))
|| ($useAddress && ($this->cached('groupAddress', $id) === $address
|| ($this->cached('mailbox', $address) !== null && $this->cached('isGroupMember', $id, $address))));
}
if (str_contains($entry, '@')) {
return $this->cached('isLocalDomain', self::domainOf($entry))
? ($account !== null && $account['username'] === $this->resolve($entry)) || ($useAddress && $address === $this->resolve($entry))
: self::untagged($sender) === $entry;
}
return $this->cached('isLocalDomain', $entry)
? ($account !== null && $account['domain'] === $this->resolveDomain($entry))
|| ($useAddress && $this->resolveDomain(self::domainOf($address)) === $this->resolveDomain($entry))
: self::domainOf($sender) === $entry;
}
/**
* Stored spelling of an address of this mailcow: Postfix finds chéf@ as chef@ (MySQL ignores accents). Only
* addresses with non-ASCII characters can differ, as they are already in lower case.
*/
private function stored(string $address): string
{
if (!preg_match('/[\x80-\xff]/', $address) || !str_contains($address, '@')) {
return $address;
}
foreach (array_unique([$address, self::untagged($address)]) as $key) {
$stored = $this->cached('storedAddress', $key);
if ($stored !== null) {
return $stored;
}
}
$domain = $this->cached('storedDomain', self::domainOf($address));
return ($domain === null) ? $address : substr($address, 0, strrpos($address, '@') + 1) . $domain;
}
/** Address in an alias domain as the address in its target domain */
private function resolve(string $address): string
{
$domain = self::domainOf($address);
$target = ($domain !== '') ? $this->cached('aliasDomainTarget', $domain) : null;
return ($target === null) ? $address : substr($address, 0, strrpos($address, '@') + 1) . $target;
}
private function resolveDomain(string $domain): string
{
return ($domain === '') ? '' : ($this->cached('aliasDomainTarget', $domain) ?? $domain);
}
/** A lookup, done once per check: long lists ask the same domains and groups again */
private function cached(string $method, int|string ...$args): mixed
{
$key = $method . "\0" . implode("\0", $args);
if (!array_key_exists($key, $this->cache)) {
$this->cache[$key] = $this->lookup->$method(...$args);
}
return $this->cache[$key];
}
/** @param Policy[] $policies @return Policy[] without repeated policies */
private static function unique(array $policies): array
{
$unique = [];
foreach ($policies as $policy) {
$unique[$policy->id ?? 'internal:' . $policy->domain] = $policy;
}
return array_values($unique);
}
/** Lower case, without angle brackets, quotes and trailing dot of the domain, as Postfix resolves it; '' = null sender */
private static function address(string $value): string
{
$value = strtolower(trim($value, " \t\r\n<>"));
$at = strrpos($value, '@');
return ($at === false) ? $value : str_replace('"', '', substr($value, 0, $at)) . '@' . rtrim(substr($value, $at + 1), '.');
}
/**
* Postfix would deliver the recipient elsewhere: no domain, or routing through a local destination
* (localhost, the IP of the server) with %, ! or a second @ in the local part
*/
private static function isRouted(string $address): bool
{
$at = strrpos($address, '@');
if ($at === false || $at === 0 || $at === strlen($address) - 1) {
return true;
}
$domain = substr($address, $at + 1);
return (in_array($domain, self::LOCAL_DESTINATIONS, true) || str_starts_with($domain, '['))
&& strpbrk(substr($address, 0, $at), '@%!') !== false;
}
private static function untagged(string $address): string
{
$at = strrpos($address, '@');
$pos = ($at === false) ? false : strpos(substr($address, 0, $at), '+');
return ($pos === false || $pos === 0) ? $address : substr($address, 0, $pos) . substr($address, $at);
}
private static function domainOf(string $address): string
{
$at = strrpos($address, '@');
return ($at === false) ? '' : substr($address, $at + 1);
}
}
@@ -1,356 +0,0 @@
<?php
declare(strict_types=1);
namespace Mailcow\MailPolicy\Service;
use Mailcow\Core\AccessDenied;
use Mailcow\Core\Context;
use Mailcow\Core\Input;
use Mailcow\Core\UserError;
use Mailcow\MailPolicy\Model\Decision;
use Mailcow\MailPolicy\Model\Policy;
use Mailcow\MailPolicy\Repository\PolicyRepository;
/**
* Use cases of mail policies.
*
* Read: admins all policies (also read-only API keys); domain admins with the `mail_policies` ACL the policies
* of their domains.
* Edit: admins; domain admins with the ACL for policies of their domains. A policy is assigned to mailboxes and
* groups of its own domain; `group:<id>` entries name groups of that domain. Groups need the `mailbox_groups`
* ACL as well: without it a domain admin sees no groups and keeps the group entries and assignments.
*/
class PolicyService
{
private const MAX_ENTRIES = 500;
/** The reject text goes into the SMTP reply: printable ASCII */
private const MAX_REJECT_TEXT = 200;
private const LISTS = ['send_allow' => 'sendAllow', 'send_block' => 'sendBlock', 'receive_allow' => 'receiveAllow', 'receive_block' => 'receiveBlock'];
public function __construct(private PolicyRepository $repo, private PolicyEngine $engine, private Context $context)
{
}
public function canRead(): bool
{
return $this->context->hasAdminRights()
|| ($this->context->isDomainAdmin() && $this->context->hasAcl('mail_policies'));
}
/** @throws AccessDenied unless the caller may edit policies at all */
public function requireEditor(): void
{
if (!$this->canRead() || $this->context->isReadOnlyApi()) {
throw new AccessDenied();
}
}
public function get(int $id): ?Policy
{
$policy = $this->canRead() ? $this->repo->find($id) : null;
return ($policy !== null && $this->visible($policy)) ? $policy : null;
}
/** @return int[] */
public function visibleIds(array $domains = []): array
{
return $this->canRead() ? $this->repo->visibleIds($this->context, $domains) : [];
}
public function canEdit(Policy $policy): bool
{
return $this->canRead() && !$this->context->isReadOnlyApi() && $this->visible($policy);
}
public function toArray(Policy $policy): array
{
return $policy->toArray() + ['can_edit' => (int)$this->canEdit($policy)];
}
/** @throws UserError */
public function create(array $input): Policy
{
$this->requireEditor();
$policy = new Policy();
$policy->domain = $this->domainFor($input['domain'] ?? null);
$this->apply($policy, $input);
$this->repo->save($policy);
return $policy;
}
/** Attributes missing in $input keep their value @throws UserError */
public function update(int $id, array $input): Policy
{
$policy = $this->editable($id);
$this->apply($policy, $input);
$this->repo->save($policy);
return $policy;
}
/** @throws UserError */
public function delete(int $id): Policy
{
$policy = $this->editable($id);
$this->repo->delete($id);
return $policy;
}
/** Address entries of the lists follow a renamed mailbox or alias; called after the rename, without permission check */
public function addressRenamed(string $old, string $new): void
{
$this->repo->renameAddress(strtolower($old), strtolower($new));
}
/** @return array[] id, name, domain, address of the groups in these domains (choices of the forms) */
public function groupChoices(array $domains): array
{
return ($this->canRead() && $this->canUseGroups()) ? $this->repo->groupsIn($domains) : [];
}
private function canUseGroups(): bool
{
return $this->context->hasAdminRights() || $this->context->hasAcl('mailbox_groups');
}
/**
* "Test a rule": would the mail be accepted? A sender that is a mailbox counts as logged in when $loggedIn.
*
* @return array{allowed: int, side: string, policy: ?string, internal_alias: int, text: string}
* @throws UserError
*/
public function test(mixed $sender, mixed $recipient, bool $loggedIn): array
{
if (!$this->canRead()) {
throw new AccessDenied();
}
$sender = strtolower((string)Input::text($sender));
$recipient = strtolower((string)Input::text($recipient));
if (($sender !== '' && !filter_var($sender, FILTER_VALIDATE_EMAIL)) || !filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
throw new UserError('mail_policy_test_invalid');
}
// domain admins test mail of their domains; a sender of another domain counts as not logged in
$ownSender = $this->context->hasAdminRights() || $this->context->canAccessDomain(self::domainOf($sender));
if (!$ownSender && !$this->context->canAccessDomain(self::domainOf($recipient))) {
throw new AccessDenied();
}
$login = ($loggedIn && $ownSender && $this->repo->mailboxDomain($sender) !== null) ? $sender : '';
$decision = $this->engine->check($sender, $recipient, $login);
$policy = $decision->policy;
$hidden = $policy !== null && $policy->id !== null && !$this->visible($policy);
return [
'allowed' => (int)$decision->allowed,
'side' => $decision->side,
'policy' => ($policy !== null && $policy->id !== null && !$hidden) ? $policy->name : null,
'internal_alias' => (int)($policy !== null && $policy->id === null),
'logged_in' => (int)($login !== ''),
// the text of a policy of another domain stays hidden
'text' => $decision->allowed ? '' : ($hidden ? Decision::defaultText($decision->side) : $decision->text()),
];
}
private function visible(Policy $policy): bool
{
return $this->context->hasAdminRights() || $this->context->canAccessDomain($policy->domain);
}
/** @throws UserError */
private function editable(int $id): Policy
{
$this->requireEditor();
$policy = $this->get($id);
if ($policy === null || !$this->canEdit($policy)) {
throw new AccessDenied();
}
return $policy;
}
/** @throws UserError */
private function domainFor(mixed $value): string
{
$domain = Input::text($value);
$domain = ($domain === null || $domain === '') ? '' : (string)idn_to_ascii(strtolower($domain), 0, INTL_IDNA_VARIANT_UTS46);
if ($domain !== '' && !$this->context->hasAdminRights() && !$this->context->canAccessDomain($domain)) {
throw new AccessDenied();
}
if ($domain === '' || !$this->repo->isDomain($domain)) {
throw new UserError('domain_not_found', [htmlspecialchars((string)Input::text($value))]);
}
return $domain;
}
/** @throws UserError */
private function apply(Policy $policy, array $input): void
{
if (array_key_exists('name', $input) || $policy->id === null) {
$name = Input::text($input['name'] ?? null);
if ($name === null || $name === '' || mb_strlen($name) > 100 || preg_match('/[\x00-\x1f\x7f]/', $name)) {
throw new UserError('mail_policy_name_invalid');
}
if ($this->repo->nameExists($policy->domain, $name, $policy->id)) {
throw new UserError('object_exists', [htmlspecialchars($name)]);
}
$policy->name = $name;
}
if (array_key_exists('description', $input)) {
$description = Input::text($input['description']);
if ($description === null || mb_strlen($description) > 255 || preg_match('/[\x00-\x1f\x7f]/', $description)) {
throw new UserError('mail_policy_description_invalid');
}
$policy->description = $description;
}
foreach (['send_mode' => 'sendMode', 'receive_mode' => 'receiveMode'] as $key => $property) {
if (array_key_exists($key, $input)) {
$mode = Input::text($input[$key]);
$policy->$property = in_array($mode, Policy::MODES, true) ? $mode : throw new UserError('mail_policy_mode_invalid');
}
}
$groupAccess = $this->canUseGroups();
foreach (self::LISTS as $key => $property) {
// the UI sends the groups of a list separately (<list>_groups); the API may name them as group:<id>
if (!array_key_exists($key, $input) && !array_key_exists($key . '_groups', $input)) {
continue;
}
$entries = array_key_exists($key, $input) ? self::values($input[$key]) : self::withoutGroups($policy->$property);
$kept = [];
if (!$groupAccess) {
// without access to groups the group entries stay (also when sent back unchanged); the form shows no group choices
$kept = array_values(array_diff($policy->$property, self::withoutGroups($policy->$property)));
$entries = array_values(array_diff(array_map('strtolower', $entries), $kept));
} elseif (array_key_exists($key . '_groups', $input)) {
$entries = array_merge(self::withoutGroups($entries), array_map(fn(string $id): string => 'group:' . $id, self::values($input[$key . '_groups'])));
}
$entries = array_values(array_unique(array_merge($this->entries($entries, $policy->domain, $groupAccess), $kept)));
if (count($entries) > self::MAX_ENTRIES) {
throw new UserError('mail_policy_entries_exceeded', [self::MAX_ENTRIES]);
}
$policy->$property = $entries;
}
if (array_key_exists('reject_text', $input)) {
$text = Input::text($input['reject_text']);
if ($text === null || strlen($text) > self::MAX_REJECT_TEXT || preg_match('/[^\x20-\x7e]/', $text)) {
throw new UserError('mail_policy_reject_text_invalid');
}
$policy->rejectText = $text;
}
if (array_key_exists('active', $input)) {
$policy->active = (int)self::flag($input['active']);
}
if (array_key_exists('mailboxes', $input)) {
$policy->mailboxes = [];
foreach (self::limited($input['mailboxes']) as $username) {
$username = strtolower($username);
if ($this->repo->mailboxDomain($username) !== $policy->domain) {
throw new UserError('mail_policy_member_invalid', [htmlspecialchars($username)]);
}
$policy->mailboxes[$username] = $username;
}
$policy->mailboxes = array_values($policy->mailboxes);
}
// without access to groups the assignments stay; only sending them back unchanged is accepted
if (array_key_exists('groups', $input) && !$groupAccess && self::values($input['groups']) !== []) {
$sent = array_map('intval', self::values($input['groups']));
$stored = $policy->groups;
sort($sent);
sort($stored);
if (array_values(array_unique($sent)) !== $stored) {
throw new AccessDenied();
}
}
if (array_key_exists('groups', $input) && $groupAccess) {
$policy->groups = [];
foreach (self::limited($input['groups']) as $value) {
$id = Input::intInRange($value, 1, PHP_INT_MAX);
if ($id === null || $this->repo->groupDomain($id) !== $policy->domain) {
throw new UserError('mail_policy_member_invalid', [htmlspecialchars($value)]);
}
$policy->groups[$id] = $id;
}
$policy->groups = array_values($policy->groups);
}
}
/**
* Entries of a list: addresses, domains (a leading @ is dropped) and group:<id> of a group of the policy's
* domain, lower case, without repeats.
*
* @param string[] $values
* @return string[]
* @throws UserError
*/
private function entries(array $values, string $domain, bool $groupAccess): array
{
$entries = [];
foreach (self::limited(array_map('strtolower', $values)) as $entry) {
if (str_starts_with($entry, 'group:')) {
$id = Input::intInRange(substr($entry, 6), 1, PHP_INT_MAX);
if (!$groupAccess || $id === null || $this->repo->groupDomain($id) !== $domain) {
throw new UserError('mail_policy_entry_invalid', [htmlspecialchars($entry)]);
}
$entry = 'group:' . $id;
} elseif (str_contains(ltrim($entry, '@'), '@')) {
// in the form the engine compares: domain without the final dot, never a +tag or an IP literal
$at = strrpos($entry, '@');
$local = substr($entry, 0, $at);
$entry = $local . '@' . rtrim(substr($entry, $at + 1), '.');
if (!filter_var($entry, FILTER_VALIDATE_EMAIL) || str_contains(substr($local, 1), '+') || str_contains($entry, '@[')) {
throw new UserError('mail_policy_entry_invalid', [htmlspecialchars($entry)]);
}
} else {
$entry = rtrim(ltrim($entry, '@'), '.');
if (!str_contains($entry, '.') || !filter_var($entry, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
throw new UserError('mail_policy_entry_invalid', [htmlspecialchars($entry)]);
}
}
$entries[$entry] = $entry;
}
return array_values($entries);
}
/**
* Values of a list field without repeats, at most MAX_ENTRIES (checked before anything is looked up)
*
* @return string[]
* @throws UserError
*/
private static function limited(mixed $value): array
{
$values = array_values(array_unique(self::values($value)));
if (count($values) > self::MAX_ENTRIES) {
throw new UserError('mail_policy_entries_exceeded', [self::MAX_ENTRIES]);
}
return $values;
}
/** @return string[] entries without group:<id> */
private static function withoutGroups(array $entries): array
{
return array_values(array_filter($entries, fn(string $entry): bool => !str_starts_with(strtolower($entry), 'group:')));
}
/**
* Values of a list field: an array (multi-select, JSON) or a string separated by line breaks, commas or spaces.
*
* @return string[]
*/
private static function values(mixed $value): array
{
$values = is_array($value) ? $value : preg_split('/[\s,;]+/', (string)Input::text($value));
return array_values(array_filter(array_map(fn(mixed $v): string => is_scalar($v) ? trim((string)$v) : '', $values), 'strlen'));
}
/** Checkbox or API flag; a hidden "0" + checkbox pair arrives as ["0", "1"] when checked */
private static function flag(mixed $value): bool
{
if (is_array($value)) {
$value = end($value);
}
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
private static function domainOf(string $address): string
{
$at = strrpos($address, '@');
return ($at === false) ? '' : substr($address, $at + 1);
}
}
@@ -195,12 +195,12 @@ class GroupService
{ {
$domain = Input::text($value); $domain = Input::text($value);
$domain = ($domain === null || $domain === '') ? '' : (string)idn_to_ascii(strtolower($domain), 0, INTL_IDNA_VARIANT_UTS46); $domain = ($domain === null || $domain === '') ? '' : (string)idn_to_ascii(strtolower($domain), 0, INTL_IDNA_VARIANT_UTS46);
if ($domain !== '' && !$this->context->hasAdminRights() && !$this->context->canAccessDomain($domain)) {
throw new AccessDenied();
}
if ($domain === '' || !$this->lookup->isDomain($domain)) { if ($domain === '' || !$this->lookup->isDomain($domain)) {
throw new UserError('domain_not_found', [htmlspecialchars((string)Input::text($value))]); throw new UserError('domain_not_found', [htmlspecialchars((string)Input::text($value))]);
} }
if (!$this->context->hasAdminRights() && !$this->context->canAccessDomain($domain)) {
throw new AccessDenied();
}
return $domain; return $domain;
} }
@@ -304,9 +304,6 @@ class GroupService
$new = array_values(array_diff(array_unique($add), array_map('strval', array_keys($members)))); $new = array_values(array_diff(array_unique($add), array_map('strval', array_keys($members))));
$regular = $this->lookup->regularMailboxes($new); $regular = $this->lookup->regularMailboxes($new);
foreach ($new as $username) { foreach ($new as $username) {
if ($this->foreignDomain($username)) {
throw new AccessDenied();
}
if (!in_array($username, $regular, true)) { if (!in_array($username, $regular, true)) {
throw new UserError('group_member_invalid', [htmlspecialchars($username)]); throw new UserError('group_member_invalid', [htmlspecialchars($username)]);
} }
@@ -340,12 +337,12 @@ class GroupService
// The group's own address is covered by sender_allowed // The group's own address is covered by sender_allowed
$valid = ($address !== null && $address !== $group->address && $this->lookup->aliasExists($address)); $valid = ($address !== null && $address !== $group->address && $this->lookup->aliasExists($address));
} }
if ($this->foreignDomain($address)) {
throw new AccessDenied();
}
if (!$valid) { if (!$valid) {
throw new UserError('group_send_as_invalid', [htmlspecialchars($value)]); throw new UserError('group_send_as_invalid', [htmlspecialchars($value)]);
} }
if (!$this->context->hasAdminRights() && !$this->context->canAccessDomain(self::domainOf((string)$address))) {
throw new AccessDenied();
}
$sendAs[] = (string)$address; $sendAs[] = (string)$address;
} }
$sendAs = array_values(array_unique($sendAs)); $sendAs = array_values(array_unique($sendAs));
@@ -508,11 +505,4 @@ class GroupService
{ {
return substr($address, strrpos($address, '@') + 1); return substr($address, strrpos($address, '@') + 1);
} }
/** A domain admin names an address or @domain outside their domains: denied before anything is looked up */
private function foreignDomain(?string $address): bool
{
$domain = ($address !== null && str_contains($address, '@')) ? self::domainOf($address) : '';
return $domain !== '' && !$this->context->hasAdminRights() && !$this->context->canAccessDomain($domain);
}
} }
-159
View File
@@ -1,159 +0,0 @@
// Mail policies (admin/domainadmin mailbox page): list, domain choices of the add modal, test of a rule. Loaded via $js_minifier.
var mailcowMailPolicy = (function($) {
'use strict';
// lang.mail_policy of the mailbox page, '' if missing
function L(key) {
var src = (typeof lang !== 'undefined' && lang && lang.mail_policy) ? lang.mail_policy : {};
return (src[key] !== undefined && src[key] !== null) ? src[key] : '';
}
function esc(s) {
return escapeHtml(s === null || s === undefined ? '' : s);
}
// mode of one direction with the number of allowed and blocked entries
function sideCell(mode, allow, block) {
var html = esc(L('summary_' + mode));
var counts = [];
if (mode !== 'all' && allow.length) counts.push('+' + allow.length + ' ' + esc(L('summary_allowed')));
if (block.length) counts.push(block.length + ' ' + esc(L('summary_blocked')));
return html + (counts.length ? '<div class="small text-muted">' + counts.join(', ') + '</div>' : '');
}
function assignedCell(item) {
var list = (item.mailboxes || []).slice(0, 3).map(esc);
if ((item.mailboxes || []).length > 3) list.push('<span class="text-muted">+' + (item.mailboxes.length - 3) + '</span>');
if ((item.groups || []).length) list.push('<i class="bi bi-people"></i> ' + esc(L('groups')) + ': ' + item.groups.length);
return list.length ? list.join('<br>') : '<span class="text-muted">' + esc(L('assigned_none')) + '</span>';
}
function drawTable() {
if ($.fn.DataTable.isDataTable('#mail_policy_table')) {
$('#mail_policy_table').DataTable().columns.adjust().responsive.recalc();
return;
}
$('#mail_policy_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[3, 'asc']],
ajax: {
type: "GET",
url: "/api/v1/get/mail-policy/all",
dataSrc: function(json) {
if (!Array.isArray(json)) return [];
$.each(json, function(i, item) {
item.chkbox = item.can_edit ?
'<input type="checkbox" class="form-check-input" data-id="mail_policy" name="multi_select" value="' + encodeURIComponent(item.id) + '" />' :
'<input type="checkbox" class="form-check-input" disabled />';
item.name_display = esc(item.name) + (item.description ? '<div class="small text-muted">' + esc(item.description) + '</div>' : '');
item.send_display = sideCell(item.send_mode, item.send_allow || [], item.send_block || []);
item.receive_display = sideCell(item.receive_mode, item.receive_allow || [], item.receive_block || []);
item.assigned_display = assignedCell(item);
item.action = '<div class="btn-group">' +
'<a href="/edit/mail-policy/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + esc(item.can_edit ? lang.edit : L('view')) + '</a>' +
(item.can_edit ? '<a href="#" data-action="delete_selected" data-id="single-mail_policy" data-api-url="delete/mail-policy" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + esc(lang.remove) + '</a>' : '') +
'</div>';
});
return json;
},
error: function() {
mailcow_alert_box(L('list_error'), 'danger');
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: '',
responsivePriority: 1
},
{ title: '', data: 'chkbox', searchable: false, orderable: false, defaultContent: '', responsivePriority: 2 },
{ title: 'ID', data: 'id', defaultContent: '', responsivePriority: 6 },
{ title: L('name'), data: 'name_display', defaultContent: '', responsivePriority: 2 },
{ title: L('domain'), data: 'domain', defaultContent: '', responsivePriority: 5 },
{ title: L('send'), data: 'send_display', defaultContent: '', responsivePriority: 3 },
{ title: L('receive'), data: 'receive_display', defaultContent: '', responsivePriority: 3 },
{ title: L('assign'), data: 'assigned_display', defaultContent: '', responsivePriority: 4 },
{
title: lang.active,
data: 'active',
defaultContent: '',
responsivePriority: 6,
render: function(d) { return d == 1 ? '<i class="bi bi-check-lg"></i>' : '<i class="bi bi-x-lg"></i>'; }
},
{
title: lang.action,
data: 'action',
orderable: false,
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
responsivePriority: 3,
defaultContent: ''
}
]
});
}
// add modal: mailboxes and groups of the chosen domain only
function filterDomain($form) {
var domain = $form.find('select.mail-policy-domain-select').val();
$form.find('select.mail-policy-mailboxes, select.mail-policy-groups').each(function() {
$(this).find('option').each(function() {
var other = $(this).data('domain') !== domain;
$(this).prop('hidden', other);
if (other) $(this).prop('selected', false);
});
$(this).selectpicker('refresh');
});
}
function test(e) {
e.preventDefault();
var sender = $.trim($('#mail_policy_test_sender').val());
var recipient = $.trim($('#mail_policy_test_recipient').val());
if (!recipient) return;
// an empty sender (bounces) is sent as "-"
var url = '/api/v1/get/mail-policy/test/' + encodeURIComponent(sender || '-') + '/' + encodeURIComponent(recipient) +
($('#mail_policy_test_login').is(':checked') ? '/login' : '');
$.get(url, function(r) {
var html;
if (!r || r.allowed === undefined) {
html = '<div class="alert alert-danger py-2">' + esc(L('test_invalid')) + '</div>';
} else if (r.allowed == 1) {
html = '<div class="alert alert-success py-2">' + esc(L('test_allowed')) + '</div>';
} else {
var reason = r.internal_alias == 1 ? L('test_denied_internal') : r.side === 'address' ? L('test_denied_address') :
(r.policy ? L('test_denied_' + r.side).replace('%s', r.policy) : L('test_denied_other'));
html = '<div class="alert alert-danger py-2">' + esc(reason) + '<div class="small mt-1"><code>550 5.7.1 ' + esc(r.text) + '</code></div></div>';
}
if (r && r.allowed !== undefined && sender && r.logged_in == 0 && $('#mail_policy_test_login').is(':checked')) {
html += '<div class="small text-muted">' + esc(L('test_not_a_mailbox')) + '</div>';
}
$('#mail_policy_test_result').html(html);
}, 'json').fail(function() {
$('#mail_policy_test_result').html('<div class="alert alert-danger py-2">' + esc(lang_danger.generic_server_error) + '</div>');
});
}
$(document).ready(function() {
$('select.mail-policy-domain-select').on('change', function() {
filterDomain($(this).closest('form'));
});
$('#addMailPolicyModal').on('show.bs.modal', function() {
filterDomain($(this).find('form'));
});
$('#mail_policy_test').on('submit', test);
});
return {
drawTable: drawTable
};
})(jQuery);
+14 -8
View File
@@ -614,7 +614,7 @@ jQuery(function($){
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'RL', title: lang_edit.ratelimit,
data: 'rl', data: 'rl',
searchable: false, searchable: false,
orderable: false, orderable: false,
@@ -715,10 +715,6 @@ jQuery(function($){
$.each(json, function (i, item) { $.each(json, function (i, item) {
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />'; item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
item.attributes.def_quota_for_mbox = humanFileSize(item.attributes.def_quota_for_mbox);
item.attributes.max_quota_for_mbox = humanFileSize(item.attributes.max_quota_for_mbox);
item.attributes.max_quota_for_domain = humanFileSize(item.attributes.max_quota_for_domain);
item.template = escapeHtml(item.template); item.template = escapeHtml(item.template);
if (item.attributes.rl_frame === "s"){ if (item.attributes.rl_frame === "s"){
item.attributes.rl_frame = lang_rl.second; item.attributes.rl_frame = lang_rl.second;
@@ -801,16 +797,25 @@ jQuery(function($){
title: lang.mailbox_defquota, title: lang.mailbox_defquota,
data: 'attributes.def_quota_for_mbox', data: 'attributes.def_quota_for_mbox',
defaultContent: '', defaultContent: '',
render: function (data, type) {
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
}
}, },
{ {
title: lang.max_quota, title: lang.max_quota,
data: 'attributes.max_quota_for_mbox', data: 'attributes.max_quota_for_mbox',
defaultContent: '', defaultContent: '',
render: function (data, type) {
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
}
}, },
{ {
title: lang.domain_quota_total, title: lang.domain_quota_total,
data: 'attributes.max_quota_for_domain', data: 'attributes.max_quota_for_domain',
defaultContent: '', defaultContent: '',
render: function (data, type) {
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
}
}, },
{ {
title: lang.gal, title: lang.gal,
@@ -932,6 +937,7 @@ jQuery(function($){
item.last_mail_login = (item.attributes.imap_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">IMAP @ ' + unix_time_format(Number(item.last_imap_login)) + '</div><br>' : '') + item.last_mail_login = (item.attributes.imap_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">IMAP @ ' + unix_time_format(Number(item.last_imap_login)) + '</div><br>' : '') +
(item.attributes.pop3_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">POP3 @ ' + unix_time_format(Number(item.last_pop3_login)) + '</div><br>' : '') + (item.attributes.pop3_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">POP3 @ ' + unix_time_format(Number(item.last_pop3_login)) + '</div><br>' : '') +
(item.attributes.smtp_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SMTP @ ' + unix_time_format(Number(item.last_smtp_login)) + '</div><br>' : '') + (item.attributes.smtp_access == 1 ? '<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SMTP @ ' + unix_time_format(Number(item.last_smtp_login)) + '</div><br>' : '') +
'<div class="text-start badge bg-info mb-2" style="min-width: 70px;">SOGO @ ' + unix_time_format(Number(item.last_sogo_login)) + '</div><br>' +
'<div class="text-start badge bg-info" style="min-width: 70px;">SSO @ ' + unix_time_format(Number(item.last_sso_login)) + '</div>'; '<div class="text-start badge bg-info" style="min-width: 70px;">SSO @ ' + unix_time_format(Number(item.last_sso_login)) + '</div>';
/* /*
if (!item.rl) { if (!item.rl) {
@@ -1244,8 +1250,6 @@ jQuery(function($){
item.attributes.rl_value = (!item.attributes.rl_value) ? "∞" : escapeHtml(item.attributes.rl_value); item.attributes.rl_value = (!item.attributes.rl_value) ? "∞" : escapeHtml(item.attributes.rl_value);
item.attributes.ratelimit = item.attributes.rl_value + " " + item.attributes.rl_frame; item.attributes.ratelimit = item.attributes.rl_value + " " + item.attributes.rl_frame;
item.attributes.quota = humanFileSize(item.attributes.quota);
item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></i>'; item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></i>';
item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></i>'; item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></i>';
item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></i>'; item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></i>';
@@ -1332,6 +1336,9 @@ jQuery(function($){
title: lang.domain_quota, title: lang.domain_quota,
data: 'attributes.quota', data: 'attributes.quota',
defaultContent: '', defaultContent: '',
render: function (data, type) {
return type === 'sort' || type === 'type' ? data : humanFileSize(data);
}
}, },
{ {
title: lang.tls_enforce_in, title: lang.tls_enforce_in,
@@ -2493,7 +2500,6 @@ jQuery(function($){
onVisible("[id^=sync_job_table]", () => draw_sync_job_table()); onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
onVisible("[id^=imapsync_source_table]", () => mailcowImapsync.drawSourceTable(role === 'admin' || acl_data.syncjobs === 1)); onVisible("[id^=imapsync_source_table]", () => mailcowImapsync.drawSourceTable(role === 'admin' || acl_data.syncjobs === 1));
onVisible("[id^=mailbox_group_table]", () => mailcowGroups.drawTable()); onVisible("[id^=mailbox_group_table]", () => mailcowGroups.drawTable());
onVisible("[id^=mail_policy_table]", () => mailcowMailPolicy.drawTable());
onVisible("[id^=filter_table]", () => draw_filter_table()); onVisible("[id^=filter_table]", () => draw_filter_table());
onVisible("[id^=bcc_table]", () => draw_bcc_table()); onVisible("[id^=bcc_table]", () => draw_bcc_table());
onVisible("[id^=recipient_map_table]", () => draw_recipient_map_table()); onVisible("[id^=recipient_map_table]", () => draw_recipient_map_table());
+6 -28
View File
@@ -280,9 +280,6 @@ if (isset($_GET['query'])) {
case "mailbox-group": case "mailbox-group":
process_add_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->addGroup($attr)); process_add_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->addGroup($attr));
break; break;
case "mail-policy":
process_add_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->addPolicy($attr));
break;
case "domain": case "domain":
switch ($object) { switch ($object) {
case "template": case "template":
@@ -861,23 +858,6 @@ if (isset($_GET['query'])) {
} }
break; break;
case "mail-policy":
switch ($object) {
case "all":
process_get_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->getPolicies($extra));
break;
// test/<sender>/<recipient>[/login]: would the mail be accepted?
case "test":
process_get_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->test((string)$extra, (string)($query[4] ?? ''), ($query[5] ?? '') === 'login'));
break;
default:
process_get_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->getPolicy($object));
break;
}
break;
case "logs": case "logs":
switch ($object) { switch ($object) {
case "dovecot": case "dovecot":
@@ -1107,10 +1087,14 @@ if (isset($_GET['query'])) {
$data['mailboxes'][$mailbox][] = $mailbox; $data['mailboxes'][$mailbox][] = $mailbox;
$user_alias_details = user_get_alias_details($mailbox); $user_alias_details = user_get_alias_details($mailbox);
foreach ($user_alias_details['direct_aliases'] as $k => $v) { foreach ($user_alias_details['direct_aliases'] as $k => $v) {
$data['mailboxes'][$mailbox][] = $k; if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr(strrchr($k, '@'), 1))) {
$data['mailboxes'][$mailbox][] = $k;
}
} }
foreach ($user_alias_details['shared_aliases'] as $k => $v) { foreach ($user_alias_details['shared_aliases'] as $k => $v) {
$data['mailboxes'][$mailbox][] = $k; if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], substr(strrchr($k, '@'), 1))) {
$data['mailboxes'][$mailbox][] = $k;
}
} }
} }
} }
@@ -1799,9 +1783,6 @@ if (isset($_GET['query'])) {
case "mailbox-group": case "mailbox-group":
process_delete_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->deleteGroups($items)); process_delete_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->deleteGroups($items));
break; break;
case "mail-policy":
process_delete_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->deletePolicies($items));
break;
case "app-passwd": case "app-passwd":
process_delete_return(app_passwd('delete', array('id' => $items))); process_delete_return(app_passwd('delete', array('id' => $items)));
break; break;
@@ -1976,9 +1957,6 @@ if (isset($_GET['query'])) {
case "mailbox-group": case "mailbox-group":
process_edit_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->editGroups($items, $attr)); process_edit_return(\Mailcow\MailboxGroup\MailboxGroupModule::fromGlobals()->api->editGroups($items, $attr));
break; break;
case "mail-policy":
process_edit_return(\Mailcow\MailPolicy\MailPolicyModule::fromGlobals()->api->editPolicies($items, $attr));
break;
case "recipient_map": case "recipient_map":
process_edit_return(recipient_map('edit', array_merge(array('id' => $items), $attr))); process_edit_return(recipient_map('edit', array_merge(array('id' => $items), $attr)));
break; break;
+3 -73
View File
@@ -11,7 +11,6 @@
"extend_sender_acl": "Eingabe externer Absenderadressen erlauben", "extend_sender_acl": "Eingabe externer Absenderadressen erlauben",
"filters": "Filter", "filters": "Filter",
"login_as": "Einloggen als Mailbox-Benutzer", "login_as": "Einloggen als Mailbox-Benutzer",
"mail_policies": "Mail-Richtlinien",
"mailbox_groups": "Gruppen", "mailbox_groups": "Gruppen",
"mailbox_relayhost": "Relayhost für eine Mailbox setzen", "mailbox_relayhost": "Relayhost für eine Mailbox setzen",
"prohibited": "Untersagt durch Richtlinie", "prohibited": "Untersagt durch Richtlinie",
@@ -67,7 +66,7 @@
"hostname": "Host", "hostname": "Host",
"inactive": "Inaktiv", "inactive": "Inaktiv",
"internal": "Intern", "internal": "Intern",
"internal_info": "Interne Aliasse nehmen nur E-Mails von angemeldeten Absendern ihrer Domain an (auch über Alias-Domains).", "internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.",
"sender_allowed": "Als dieser Alias senden erlauben", "sender_allowed": "Als dieser Alias senden erlauben",
"kind": "Art", "kind": "Art",
"mailbox_quota_def": "Standard-Quota einer Mailbox", "mailbox_quota_def": "Standard-Quota einer Mailbox",
@@ -583,14 +582,6 @@
"is_spam_alias": "%s lautet bereits eine temporäre Alias-Adresse (Spam-Alias-Adresse)", "is_spam_alias": "%s lautet bereits eine temporäre Alias-Adresse (Spam-Alias-Adresse)",
"last_key": "Letzter Key kann nicht gelöscht werden, bitte stattdessen die 2FA deaktivieren.", "last_key": "Letzter Key kann nicht gelöscht werden, bitte stattdessen die 2FA deaktivieren.",
"login_failed": "Anmeldung fehlgeschlagen", "login_failed": "Anmeldung fehlgeschlagen",
"mail_policy_description_invalid": "Ungültige Beschreibung",
"mail_policy_entries_exceeded": "Höchstens %s Einträge pro Liste oder Auswahl möglich",
"mail_policy_entry_invalid": "Ungültige Adresse, Domain oder Gruppe in der Liste: %s",
"mail_policy_member_invalid": "Mailbox oder Gruppe %s gehört nicht zur Domain der Richtlinie",
"mail_policy_mode_invalid": "Ungültiger Modus",
"mail_policy_name_invalid": "Ungültiger Name (1 bis 100 Zeichen)",
"mail_policy_reject_text_invalid": "Ungültiger Ablehnungstext: druckbare ASCII-Zeichen, höchstens 200",
"mail_policy_test_invalid": "Ungültiger Absender oder Empfänger",
"mailbox_defquota_exceeds_mailbox_maxquota": "Standard-Quota überschreitet das Limit der maximal erlaubten Größe einer Mailbox", "mailbox_defquota_exceeds_mailbox_maxquota": "Standard-Quota überschreitet das Limit der maximal erlaubten Größe einer Mailbox",
"mailbox_invalid": "Mailboxname ist ungültig", "mailbox_invalid": "Mailboxname ist ungültig",
"mailbox_quota_exceeded": "Speicherplatz überschreitet das Limit (max. %d MiB)", "mailbox_quota_exceeded": "Speicherplatz überschreitet das Limit (max. %d MiB)",
@@ -838,7 +829,7 @@
"iam_unlink": "Verknüpfung lösen", "iam_unlink": "Verknüpfung lösen",
"inactive": "Inaktiv", "inactive": "Inaktiv",
"internal": "Intern", "internal": "Intern",
"internal_info": "Interne Aliasse nehmen nur E-Mails von angemeldeten Absendern ihrer Domain an (auch über Alias-Domains).", "internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.",
"sender_allowed": "Als dieser Alias senden erlauben", "sender_allowed": "Als dieser Alias senden erlauben",
"sender_allowed_info": "Wenn deaktiviert, kann dieser Alias nur E-Mails empfangen. Verwenden Sie Sender-ACL, um bestimmten Postfächern die Berechtigung zum Senden zu erteilen.", "sender_allowed_info": "Wenn deaktiviert, kann dieser Alias nur E-Mails empfangen. Verwenden Sie Sender-ACL, um bestimmten Postfächern die Berechtigung zum Senden zu erteilen.",
"kind": "Art", "kind": "Art",
@@ -978,7 +969,7 @@
"imap_grants": "Postfachzugriff", "imap_grants": "Postfachzugriff",
"imap_grants_hint": "Mitglieder öffnen diese Mailboxen in ihrem Mailprogramm (Ordner Shared/<mailbox>). Lesen: lesen und als gelesen markieren. Schreiben: zusätzlich ablegen, verschieben und löschen. Vollzugriff: zusätzlich Ordner löschen. Team-Postfächer ohne eigenen Login als geteiltes Postfach markieren.", "imap_grants_hint": "Mitglieder öffnen diese Mailboxen in ihrem Mailprogramm (Ordner Shared/<mailbox>). Lesen: lesen und als gelesen markieren. Schreiben: zusätzlich ablegen, verschieben und löschen. Vollzugriff: zusätzlich Ordner löschen. Team-Postfächer ohne eigenen Login als geteiltes Postfach markieren.",
"info": "Gruppen bündeln Postfächer. Eine Gruppe kann eine eigene Adresse haben: ein Alias, der automatisch an alle Mitglieder zustellt. Mitglieder können als andere Adressen senden, auf freigegebene Postfächer, Kalender und Adressbücher zugreifen und ein Postfach-Template erhalten. Vom Identity Provider synchronisierte Mitglieder sind mit IdP markiert.", "info": "Gruppen bündeln Postfächer. Eine Gruppe kann eine eigene Adresse haben: ein Alias, der automatisch an alle Mitglieder zustellt. Mitglieder können als andere Adressen senden, auf freigegebene Postfächer, Kalender und Adressbücher zugreifen und ein Postfach-Template erhalten. Vom Identity Provider synchronisierte Mitglieder sind mit IdP markiert.",
"internal": "Nur E-Mails von angemeldeten Absendern der Domain annehmen", "internal": "Nur Mails von Absendern der Domain annehmen",
"list_error": "Gruppen konnten nicht geladen werden", "list_error": "Gruppen konnten nicht geladen werden",
"managed_alias": "Wird von Gruppe %s verwaltet", "managed_alias": "Wird von Gruppe %s verwaltet",
"members": "Mitglieder", "members": "Mitglieder",
@@ -1045,61 +1036,6 @@
"username": "Benutzername", "username": "Benutzername",
"email": "E-Mail-Adresse" "email": "E-Mail-Adresse"
}, },
"mail_policy": {
"add": "Mail-Richtlinie hinzufügen",
"allow_receive": "Erlaubte Absender",
"allow_send": "Erlaubte Empfänger",
"assign": "Zugewiesen an",
"assign_info": "Die Richtlinie gilt für diese Mailboxen und für die Mitglieder dieser Gruppen aus der Domain der Richtlinie, bei einer Gruppe auch für E-Mails an die Gruppenadresse. E-Mails an einen Alias oder eine andere Gruppe erreichen die Mitglieder ohne deren Richtlinien.",
"assigned_none": "Nicht zugewiesen",
"block_receive": "Gesperrte Absender",
"block_send": "Gesperrte Empfänger",
"description": "Beschreibung",
"domain": "Domain",
"entries_info_receive": "Adressen oder Domains, eine pro Zeile, und Gruppen der Domain. Erlaubte Adressen und Domains dieser mailcow gelten nur für angemeldete Absender, gesperrte auch für die Absenderadresse im Umschlag. Externe Absender werden an der Absenderadresse im Umschlag erkannt.",
"entries_info_send": "Adressen oder Domains, eine pro Zeile, und Gruppen der Domain.",
"groups": "Gruppen",
"info": "Mail-Richtlinien legen fest, an wen Mailboxen und Gruppen senden und von wem sie empfangen dürfen, z. B. nur intern oder nur mit bestimmten Domains. Postfix prüft sie für jeden Empfänger und lehnt nicht erlaubte Empfänger mit einem 550-Fehler ab. Eingehende E-Mails erreichen die übrigen Empfänger trotzdem, SOGo sendet eine E-Mail mit einem abgelehnten Empfänger dagegen gar nicht.",
"internal_info": "Intern: Empfänger in der Domain der Richtlinie und ihren Alias-Domains; Absender, die mit einer Mailbox dieser Domain angemeldet sind.",
"list_error": "Die Mail-Richtlinien konnten nicht geladen werden",
"mail_policies": "Mail-Richtlinien",
"mailboxes": "Mailboxen",
"mode": "Erlaubt",
"mode_receive_all": "Alle Absender",
"mode_receive_internal": "Interne Absender und die erlaubten Absender",
"mode_receive_list": "Nur die erlaubten Absender",
"mode_send_all": "Alle Empfänger",
"mode_send_internal": "Interne Empfänger und die erlaubten Empfänger",
"mode_send_list": "Nur die erlaubten Empfänger",
"name": "Name",
"policy": "Mail-Richtlinie",
"receive": "Empfangen",
"receive_info": "Für E-Mails an eine zugewiesene Mailbox oder an die Adresse einer zugewiesenen Gruppe.",
"reject_text": "Ablehnungstext",
"reject_text_info": "Text der SMTP-Ablehnung (550 5.7.1 …), nur ASCII. Leer: ein Standardtext.",
"send": "Senden",
"send_info": "Für E-Mails, die eine zugewiesene Mailbox angemeldet sendet oder die unter ihrer Adresse gesendet werden, auch Weiterleitungen per Filter und Abwesenheitsnotizen.",
"summary_all": "Alle",
"summary_allowed": "erlaubt",
"summary_blocked": "gesperrt",
"summary_internal": "Intern",
"summary_list": "Nur erlaubte",
"test": "Regel testen",
"test_allowed": "Angenommen: Keine Richtlinie lehnt diese E-Mail ab.",
"test_denied_address": "Abgelehnt: Diese Schreibweise leitet über eine andere Adresse um und ist nicht erlaubt.",
"test_denied_internal": "Abgelehnt: Die Adresse nimmt nur E-Mails von angemeldeten Absendern ihrer Domain an (interner Alias oder interne Gruppe).",
"test_denied_other": "Abgelehnt durch eine Richtlinie einer anderen Domain.",
"test_denied_receive": "Abgelehnt durch den Empfangen-Teil der Richtlinie „%s“.",
"test_denied_send": "Abgelehnt durch den Senden-Teil der Richtlinie „%s“.",
"test_info": "Prüft, ob Postfix die E-Mail für diesen Empfänger annehmen würde. Für Bounces den Absender leer lassen.",
"test_invalid": "Geben Sie einen gültigen Empfänger (und Absender) Ihrer Domains ein.",
"test_logged_in": "Absender ist angemeldet",
"test_not_a_mailbox": "Der Absender ist keine Mailbox Ihrer Domains und wurde als nicht angemeldet geprüft.",
"test_recipient": "Empfänger",
"test_run": "Testen",
"test_sender": "Absender",
"view": "Ansehen"
},
"mailbox": { "mailbox": {
"action": "Aktion", "action": "Aktion",
"activate": "Aktivieren", "activate": "Aktivieren",
@@ -1198,9 +1134,6 @@
"no_record": "Kein Eintrag für Objekt %s", "no_record": "Kein Eintrag für Objekt %s",
"no_record_single": "Kein Eintrag", "no_record_single": "Kein Eintrag",
"owner": "Besitzer", "owner": "Besitzer",
"policies": "Richtlinien",
"policies_mail": "Mail",
"policies_tls": "TLS",
"private_comment": "Privater Kommentar", "private_comment": "Privater Kommentar",
"public_comment": "Öffentlicher Kommentar", "public_comment": "Öffentlicher Kommentar",
"q_add_header": "bei Mail in Junk-Ordner", "q_add_header": "bei Mail in Junk-Ordner",
@@ -1415,9 +1348,6 @@
"learned_ham": "ID %s wurde erfolgreich als Ham gelernt", "learned_ham": "ID %s wurde erfolgreich als Ham gelernt",
"license_modified": "Änderungen an Lizenz wurden gespeichert", "license_modified": "Änderungen an Lizenz wurden gespeichert",
"logged_in_as": "Eingeloggt als %s", "logged_in_as": "Eingeloggt als %s",
"mail_policy_added": "Mail-Richtlinie %s hinzugefügt",
"mail_policy_modified": "Mail-Richtlinie %s gespeichert",
"mail_policy_removed": "Mail-Richtlinie %s entfernt",
"mailbox_added": "Mailbox %s wurde angelegt", "mailbox_added": "Mailbox %s wurde angelegt",
"mailbox_modified": "Änderungen an Mailbox %s wurden gespeichert", "mailbox_modified": "Änderungen an Mailbox %s wurden gespeichert",
"mailbox_removed": "Mailbox %s wurde entfernt", "mailbox_removed": "Mailbox %s wurde entfernt",
+5 -74
View File
@@ -11,7 +11,6 @@
"extend_sender_acl": "Allow to extend sender ACL by external addresses", "extend_sender_acl": "Allow to extend sender ACL by external addresses",
"filters": "Filters", "filters": "Filters",
"login_as": "Login as mailbox user", "login_as": "Login as mailbox user",
"mail_policies": "Mail policies",
"mailbox_groups": "Groups", "mailbox_groups": "Groups",
"mailbox_relayhost": "Change relayhost for a mailbox", "mailbox_relayhost": "Change relayhost for a mailbox",
"prohibited": "Prohibited by ACL", "prohibited": "Prohibited by ACL",
@@ -67,7 +66,7 @@
"hostname": "Host", "hostname": "Host",
"inactive": "Inactive", "inactive": "Inactive",
"internal": "Internal", "internal": "Internal",
"internal_info": "Internal aliases only accept mail from logged-in senders of their domain (also through alias domains).", "internal_info": "Internal aliases are only accessible from the own domain or alias domains.",
"sender_allowed": "Allow to send as this alias", "sender_allowed": "Allow to send as this alias",
"kind": "Kind", "kind": "Kind",
"mailbox_quota_def": "Default mailbox quota", "mailbox_quota_def": "Default mailbox quota",
@@ -519,14 +518,6 @@
"group_send_as_invalid": "Send-as address %s is invalid or unknown", "group_send_as_invalid": "Send-as address %s is invalid or unknown",
"iam_sync_mode_invalid": "Invalid sync mode", "iam_sync_mode_invalid": "Invalid sync mode",
"iam_sync_unavailable": "The identity provider cannot be synced (Keycloak and LDAP only)", "iam_sync_unavailable": "The identity provider cannot be synced (Keycloak and LDAP only)",
"mail_policy_description_invalid": "Invalid description",
"mail_policy_entries_exceeded": "At most %s entries per list or selection are possible",
"mail_policy_entry_invalid": "Invalid address, domain or group in the list: %s",
"mail_policy_member_invalid": "Mailbox or group %s does not belong to the domain of the policy",
"mail_policy_mode_invalid": "Invalid mode",
"mail_policy_name_invalid": "Invalid name (1 to 100 characters)",
"mail_policy_reject_text_invalid": "Invalid rejection text: printable ASCII, at most 200 characters",
"mail_policy_test_invalid": "Invalid sender or recipient",
"tfa_login_locked": "Too many failed two-factor logins, please try again later", "tfa_login_locked": "Too many failed two-factor logins, please try again later",
"tfa_removal_blocked": "Two-factor authentication cannot be removed because it is required for your account.", "tfa_removal_blocked": "Two-factor authentication cannot be removed because it is required for your account.",
"alias_domain_invalid": "Alias domain %s is invalid", "alias_domain_invalid": "Alias domain %s is invalid",
@@ -838,7 +829,7 @@
"iam_unlink": "Unlink", "iam_unlink": "Unlink",
"inactive": "Inactive", "inactive": "Inactive",
"internal": "Internal", "internal": "Internal",
"internal_info": "Internal aliases only accept mail from logged-in senders of their domain (also through alias domains).", "internal_info": "Internal aliases are only accessible from the own domain or alias domains.",
"sender_allowed": "Allow to send as this alias", "sender_allowed": "Allow to send as this alias",
"sender_allowed_info": "If disabled, this alias can only receive mail. Use sender ACL to override and grant specific mailboxes permission to send.", "sender_allowed_info": "If disabled, this alias can only receive mail. Use sender ACL to override and grant specific mailboxes permission to send.",
"kind": "Kind", "kind": "Kind",
@@ -978,7 +969,7 @@
"imap_grants": "Mailbox access", "imap_grants": "Mailbox access",
"imap_grants_hint": "Members open these mailboxes in their mail client (folder Shared/<mailbox>). Read: read and mark as read. Write: also file, move and delete mails. Full access: also delete folders. For team mailboxes without a login of their own, mark the mailbox as shared mailbox.", "imap_grants_hint": "Members open these mailboxes in their mail client (folder Shared/<mailbox>). Read: read and mark as read. Write: also file, move and delete mails. Full access: also delete folders. For team mailboxes without a login of their own, mark the mailbox as shared mailbox.",
"info": "Groups bundle mailboxes. A group can have its own address: an alias that delivers to all members automatically. Members can send as other addresses, access shared mailboxes, calendars and address books and get a mailbox template. Members synced from the identity provider are marked IdP.", "info": "Groups bundle mailboxes. A group can have its own address: an alias that delivers to all members automatically. Members can send as other addresses, access shared mailboxes, calendars and address books and get a mailbox template. Members synced from the identity provider are marked IdP.",
"internal": "Only accept mail from logged-in senders of the domain", "internal": "Only accept mail from senders of the domain",
"list_error": "Groups could not be loaded", "list_error": "Groups could not be loaded",
"managed_alias": "Managed by group %s", "managed_alias": "Managed by group %s",
"members": "Members", "members": "Members",
@@ -1045,61 +1036,6 @@
"username": "Username", "username": "Username",
"email": "Email address" "email": "Email address"
}, },
"mail_policy": {
"add": "Add mail policy",
"allow_receive": "Allowed senders",
"allow_send": "Allowed recipients",
"assign": "Assigned to",
"assign_info": "The policy applies to these mailboxes and to the members of these groups in the policy's domain; for a group also to mail sent to the group address. Mail sent to an alias or another group reaches the members without their policies.",
"assigned_none": "Not assigned",
"block_receive": "Blocked senders",
"block_send": "Blocked recipients",
"description": "Description",
"domain": "Domain",
"entries_info_receive": "Addresses or domains, one per line, and groups of the domain. Allowed addresses and domains of this mailcow count for logged-in senders only, blocked ones also for the envelope sender address. External senders are recognised by the envelope sender address.",
"entries_info_send": "Addresses or domains, one per line, and groups of the domain.",
"groups": "Groups",
"info": "Mail policies define whom mailboxes and groups may send to and receive from, e.g. only internally or only with certain domains. Postfix checks them for every recipient and refuses recipients that are not allowed with a 550 error. Incoming mail still reaches the other recipients, whereas SOGo does not send a mail with a refused recipient at all.",
"internal_info": "Internal: recipients in the domain of the policy and its alias domains; senders logged in with a mailbox of this domain.",
"list_error": "The mail policies could not be loaded",
"mail_policies": "Mail policies",
"mailboxes": "Mailboxes",
"mode": "Allowed",
"mode_receive_all": "All senders",
"mode_receive_internal": "Internal senders and the allowed senders",
"mode_receive_list": "Only the allowed senders",
"mode_send_all": "All recipients",
"mode_send_internal": "Internal recipients and the allowed recipients",
"mode_send_list": "Only the allowed recipients",
"name": "Name",
"policy": "Mail policy",
"receive": "Receiving",
"receive_info": "For mail to an assigned mailbox or to the address of an assigned group.",
"reject_text": "Rejection text",
"reject_text_info": "Text of the SMTP rejection (550 5.7.1 ...), ASCII only. Empty: a default text.",
"send": "Sending",
"send_info": "For mail an assigned mailbox sends while logged in, or that is sent with its address, including redirects by filters and vacation replies.",
"summary_all": "All",
"summary_allowed": "allowed",
"summary_blocked": "blocked",
"summary_internal": "Internal",
"summary_list": "Allowed only",
"test": "Test a rule",
"test_allowed": "Accepted: no policy refuses this mail.",
"test_denied_address": "Refused: this form of the address routes through another address and is not allowed.",
"test_denied_internal": "Refused: the address only accepts mail from logged-in senders of its domain (internal alias or group).",
"test_denied_other": "Refused by a policy of another domain.",
"test_denied_receive": "Refused by the receiving part of the policy \"%s\".",
"test_denied_send": "Refused by the sending part of the policy \"%s\".",
"test_info": "Checks whether Postfix would accept the mail for this recipient. Leave the sender empty for bounces.",
"test_invalid": "Enter a valid recipient (and sender) of your domains.",
"test_logged_in": "Sender is logged in",
"test_not_a_mailbox": "The sender is no mailbox of your domains and was tested as not logged in.",
"test_recipient": "Recipient",
"test_run": "Test",
"test_sender": "Sender",
"view": "View"
},
"mailbox": { "mailbox": {
"action": "Action", "action": "Action",
"activate": "Activate", "activate": "Activate",
@@ -1191,7 +1127,7 @@
"mailbox_defaults_info": "Define default settings for new mailboxes.", "mailbox_defaults_info": "Define default settings for new mailboxes.",
"mailbox_defquota": "Default mailbox size", "mailbox_defquota": "Default mailbox size",
"mailbox_templates": "Mailbox Templates", "mailbox_templates": "Mailbox Templates",
"mailbox_quota": "Max. size of a mailbox", "mailbox_quota": "Max. mailbox size",
"mailboxes": "Mailboxes", "mailboxes": "Mailboxes",
"max_aliases": "Max. aliases", "max_aliases": "Max. aliases",
"max_mailboxes": "Max. possible mailboxes", "max_mailboxes": "Max. possible mailboxes",
@@ -1205,9 +1141,6 @@
"no_record_single": "No record", "no_record_single": "No record",
"open_logs": "Open logs", "open_logs": "Open logs",
"owner": "Owner", "owner": "Owner",
"policies": "Policies",
"policies_mail": "Mail",
"policies_tls": "TLS",
"private_comment": "Private comment", "private_comment": "Private comment",
"public_comment": "Public comment", "public_comment": "Public comment",
"q_add_header": "when moved to Junk folder", "q_add_header": "when moved to Junk folder",
@@ -1422,9 +1355,6 @@
"learned_ham": "Successfully learned ID %s as ham", "learned_ham": "Successfully learned ID %s as ham",
"license_modified": "Changes to license have been saved", "license_modified": "Changes to license have been saved",
"logged_in_as": "Logged in as %s", "logged_in_as": "Logged in as %s",
"mail_policy_added": "Mail policy %s added",
"mail_policy_modified": "Mail policy %s saved",
"mail_policy_removed": "Mail policy %s removed",
"mailbox_added": "Mailbox %s has been added", "mailbox_added": "Mailbox %s has been added",
"mailbox_modified": "Changes to mailbox %s have been saved", "mailbox_modified": "Changes to mailbox %s have been saved",
"mailbox_removed": "Mailbox %s has been removed", "mailbox_removed": "Mailbox %s has been removed",
@@ -1499,6 +1429,7 @@
"u2f_deprecated": "It seems that your Key was registered using the deprecated U2F method. We will deactivate Two-Factor-Authenticaiton for you and delete your Key.", "u2f_deprecated": "It seems that your Key was registered using the deprecated U2F method. We will deactivate Two-Factor-Authenticaiton for you and delete your Key.",
"u2f_deprecated_important": "Please register your Key in the admin panel with the new WebAuthn method.", "u2f_deprecated_important": "Please register your Key in the admin panel with the new WebAuthn method.",
"webauthn": "WebAuthn authentication", "webauthn": "WebAuthn authentication",
"webauthn_registration_failed": "WebAuthn registration failed. The credential could not be verified, please try again.",
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.", "waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.",
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.", "waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
"yubi_otp": "Yubico OTP authentication" "yubi_otp": "Yubico OTP authentication"
-5
View File
@@ -1074,7 +1074,6 @@
"verified_webauthn_login": "Inicio de sesión WebAuthn verificado", "verified_webauthn_login": "Inicio de sesión WebAuthn verificado",
"verified_yotp_login": "Inicio de sesión Yubico OTP verificado", "verified_yotp_login": "Inicio de sesión Yubico OTP verificado",
"admin_added": "Se agregó el administrador %s", "admin_added": "Se agregó el administrador %s",
"admin_api_modified": "Se guardaron los cambios en la API",
"admin_removed": "Se eliminó el administrador %s", "admin_removed": "Se eliminó el administrador %s",
"app_links": "Se guardaron los cambios en los enlaces de aplicaciones", "app_links": "Se guardaron los cambios en los enlaces de aplicaciones",
"app_passwd_added": "Se agregó una nueva contraseña de aplicación", "app_passwd_added": "Se agregó una nueva contraseña de aplicación",
@@ -1333,10 +1332,6 @@
"sogo_profile_reset_help": "Esto destruirá el perfil de SOGo del usuario y <b>eliminará de forma irrecuperable todos sus contactos y datos de calendario</b>.", "sogo_profile_reset_help": "Esto destruirá el perfil de SOGo del usuario y <b>eliminará de forma irrecuperable todos sus contactos y datos de calendario</b>.",
"spam_score_reset": "Restablecer al valor predeterminado del servidor", "spam_score_reset": "Restablecer al valor predeterminado del servidor",
"spamfilter_table_domain_policy": "n/d (política del dominio)", "spamfilter_table_domain_policy": "n/d (política del dominio)",
"syncjob_check_log": "Revisar registro",
"syncjob_EX_OK": "Correcto",
"syncjob_EXIT_OVERQUOTA": "El buzón de destino superó su cuota",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Usuario o contraseña incorrectos",
"tfa_info": "La autenticación de dos factores ayuda a proteger su cuenta. Si la activa, necesitará contraseñas de aplicación para iniciar sesión en aplicaciones o servicios que no admiten la autenticación de dos factores (p. ej., clientes de correo)." "tfa_info": "La autenticación de dos factores ayuda a proteger su cuenta. Si la activa, necesitará contraseñas de aplicación para iniciar sesión en aplicaciones o servicios que no admiten la autenticación de dos factores (p. ej., clientes de correo)."
}, },
"warning": { "warning": {
-1
View File
@@ -95,7 +95,6 @@
"bcc_dest_format": "Il destinatario in copia nascosta deve essere un singolo indirizzo email.<br>Se si vuole spedire una copia del messaggio a più destinatari, bisogna creare un alias ed utilizzarlo per questa opzione.", "bcc_dest_format": "Il destinatario in copia nascosta deve essere un singolo indirizzo email.<br>Se si vuole spedire una copia del messaggio a più destinatari, bisogna creare un alias ed utilizzarlo per questa opzione.",
"app_passwd_protocols": "Protocolli consentiti per la password dell'app", "app_passwd_protocols": "Protocolli consentiti per la password dell'app",
"tags": "Tag", "tags": "Tag",
"dry": "Simula sincronizzazione",
"internal": "Interno", "internal": "Interno",
"internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.", "internal_info": "Gli alias interni sono accessibili solo dal proprio dominio o dai domini alias.",
"sender_allowed": "Consenti l'invio con questo alias" "sender_allowed": "Consenti l'invio con questo alias"
+17 -11
View File
@@ -416,18 +416,24 @@ function recursiveBase64StrToArrayBuffer(obj) {
return navigator.credentials.create(json); return navigator.credentials.create(json);
}).then(function(cred) { }).then(function(cred) {
return { return {
id: cred.id, clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON),
rawId: arrayBufferToBase64(cred.rawId), attestationObject: arrayBufferToBase64(cred.response.attestationObject),
response: { key_id: keyId,
attestationObject: arrayBufferToBase64(cred.response.attestationObject), tfa_method: 'webauthn',
clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON) confirm_password: ''
},
type: cred.type
}; };
}).then(function(credData) { }).then(JSON.stringify).then(function(credentialData) {
$('#setup_webauthn_register_data').val(JSON.stringify(credData)); // send the attestation to the JSON API, like the regular WebAuthn modal does,
$('#setup_webauthn_reg_form input[name=set_tfa]').val('1'); // so that the credential is parsed and stored correctly
$('#setup_webauthn_reg_form').submit(); return window.fetch('/api/v1/add/webauthn-tfa-registration', {method: 'POST', body: credentialData, cache: 'no-cache'});
}).then(function(response) {
return response.json();
}).then(function(json) {
if (json.success) {
window.location = window.location.href.split('#')[0];
} else {
throw new Error(json.msg);
}
}).catch(function(err) { }).catch(function(err) {
$('#setup_webauthn_return_code').show().text(err.message || 'Registration failed'); $('#setup_webauthn_return_code').show().text(err.message || 'Registration failed');
}); });
@@ -1,84 +0,0 @@
{# Fields of a mail policy (add modal, edit page): policy, id (prefix of the element ids), groups (choices: id, name,
domain, address), mailboxes (choices), ro, label_class. Groups of a list are sent apart as <list>_groups; without
group choices (none in the domain, no access to groups) the group fields are left out and stay unchanged. #}
{% set entries = {} %}
{% set listed = {} %}
{% for key in ['send_allow', 'send_block', 'receive_allow', 'receive_block'] %}
{% set plain = [] %}
{% set group_ids = [] %}
{% for entry in attribute(policy, key)|default([]) %}
{% if entry starts with 'group:' %}
{% set group_ids = group_ids|merge([entry|slice(6)]) %}
{% else %}
{% set plain = plain|merge([entry]) %}
{% endif %}
{% endfor %}
{% set entries = entries|merge({(key): plain|join("\n")}) %}
{% set listed = listed|merge({(key): group_ids}) %}
{% endfor %}
{% for side in ['send', 'receive'] %}
<legend class="fs-5 mt-4"><i class="bi {{ side == 'send' ? 'bi-send' : 'bi-inbox' }}"></i> {{ attribute(lang.mail_policy, side) }}</legend><hr>
<p class="text-muted small">{{ attribute(lang.mail_policy, side ~ '_info') }}</p>
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_{{ side }}_mode">{{ lang.mail_policy.mode }}</label>
<div class="col-sm-10">
<select class="form-control" name="{{ side }}_mode" id="{{ id }}_{{ side }}_mode"{% if ro %} disabled{% endif %}>
{% for mode in ['all', 'internal', 'list'] %}
<option value="{{ mode }}"{% if attribute(policy, side ~ '_mode')|default('all') == mode %} selected{% endif %}>{{ attribute(lang.mail_policy, 'mode_' ~ side ~ '_' ~ mode) }}</option>
{% endfor %}
</select>
<small class="text-muted d-block">{{ lang.mail_policy.internal_info }}</small>
</div>
</div>
{% for list in ['allow', 'block'] %}
{% set key = side ~ '_' ~ list %}
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_{{ key }}">{{ attribute(lang.mail_policy, list ~ '_' ~ side) }}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="{{ key }}" id="{{ id }}_{{ key }}" placeholder="partner.example, user@example.org"{% if ro %} disabled{% endif %}>{{ attribute(entries, key) }}</textarea>
<small class="text-muted d-block mb-1">{{ attribute(lang.mail_policy, 'entries_info_' ~ side) }}</small>
{% if groups %}
{% if not ro %}<input type="hidden" name="{{ key }}_groups" value="">{% endif %}
<select data-live-search="true" data-width="100%" name="{{ key }}_groups" class="form-control mail-policy-groups" multiple title="{{ lang.mail_policy.groups }}"{% if ro %} disabled{% endif %}>
{% for group in groups %}
<option value="{{ group.id }}" data-domain="{{ group.domain }}"{% if group.id ~ '' in attribute(listed, key) %} selected{% endif %}>{{ group.name }}{% if group.address %} ({{ group.address }}){% endif %}</option>
{% endfor %}
</select>
{% endif %}
</div>
</div>
{% endfor %}
{% endfor %}
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_reject_text">{{ lang.mail_policy.reject_text }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="reject_text" id="{{ id }}_reject_text" maxlength="200" value="{{ policy.reject_text|default('') }}"{% if ro %} disabled{% endif %}>
<small class="text-muted d-block">{{ lang.mail_policy.reject_text_info }}</small>
</div>
</div>
<legend class="fs-5 mt-4"><i class="bi bi-people"></i> {{ lang.mail_policy.assign }}</legend><hr>
<p class="text-muted small">{{ lang.mail_policy.assign_info }}</p>
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_mailboxes">{{ lang.mail_policy.mailboxes }}</label>
<div class="col-sm-10">
{% if not ro %}<input type="hidden" name="mailboxes" value="">{% endif %}
<select data-live-search="true" data-width="100%" name="mailboxes" id="{{ id }}_mailboxes" class="form-control mail-policy-mailboxes" multiple title="{{ lang.mail_policy.mailboxes }}"{% if ro %} disabled{% endif %}>
{% for mailbox in mailboxes %}
<option value="{{ mailbox }}" data-domain="{{ mailbox|split('@')|last }}"{% if mailbox in policy.mailboxes|default([]) %} selected{% endif %}>{{ mailbox }}</option>
{% endfor %}
</select>
</div>
</div>
{% if groups %}
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_groups">{{ lang.mail_policy.groups }}</label>
<div class="col-sm-10">
{% if not ro %}<input type="hidden" name="groups" value="">{% endif %}
<select data-live-search="true" data-width="100%" name="groups" id="{{ id }}_groups" class="form-control mail-policy-groups" multiple title="{{ lang.mail_policy.groups }}"{% if ro %} disabled{% endif %}>
{% for group in groups %}
<option value="{{ group.id }}" data-domain="{{ group.domain }}"{% if group.id in policy.groups|default([]) %} selected{% endif %}>{{ group.name }}{% if group.address %} ({{ group.address }}){% endif %}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
-46
View File
@@ -1,46 +0,0 @@
{% extends 'edit.twig' %}
{% block inner_content %}
{% if result %}
{% set ro = not result.can_edit %}
<h4>{{ lang.mail_policy.policy }}: {{ result.name }}</h4>
<form data-id="edit_mail_policy" class="form-horizontal" role="form" method="post" autocapitalize="none" autocorrect="off">
<input type="hidden" value="0" name="active">
<div class="row mb-2">
<label class="control-label col-sm-2" for="mail_policy_domain">{{ lang.mail_policy.domain }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="mail_policy_domain" value="{{ result.domain }}" disabled>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="mail_policy_name">{{ lang.mail_policy.name }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="mail_policy_name" value="{{ result.name }}" maxlength="100" required{% if ro %} disabled{% endif %}>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="mail_policy_description">{{ lang.mail_policy.description }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" id="mail_policy_description" value="{{ result.description }}" maxlength="255"{% if ro %} disabled{% endif %}>
</div>
</div>
{% include 'edit/mail-policy-fields.twig' with {'policy': result, 'id': 'mail_policy', 'groups': mail_policy_groups, 'mailboxes': mailboxes, 'ro': ro, 'label_class': ''} %}
<div class="row mb-4">
<div class="offset-sm-2 col-sm-10">
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="active"{% if result.active == 1 %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.mailbox.active }}</label>
</div>
</div>
</div>
{% if not ro %}
<div class="row">
<div class="offset-sm-2 col-sm-10">
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="edit_mail_policy" data-item="{{ result.id }}" data-api-url='edit/mail-policy' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div>
</div>
{% endif %}
</form>
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
+5 -3
View File
@@ -97,9 +97,11 @@
<div class="col-sm-10"> <div class="col-sm-10">
<select data-live-search="true" data-width="100%" style="width:100%" id="editSelectSenderACL" name="sender_acl" size="10" multiple> <select data-live-search="true" data-width="100%" style="width:100%" id="editSelectSenderACL" name="sender_acl" size="10" multiple>
{% for domain in sender_acl_handles.sender_acl_domains.ro %} {% for domain in sender_acl_handles.sender_acl_domains.ro %}
<option data-subtext="Admin" value="{{ domain }}" disabled selected> {% if domain == '*' or mailcow_cc_role == 'admin' %}
{{ lang.edit.dont_check_sender_acl|format(domain) }} <option data-subtext="Admin" value="{{ domain }}" disabled selected>
</option> {{ lang.edit.dont_check_sender_acl|format(domain) }}
</option>
{% endif %}
{% endfor %} {% endfor %}
{% for domain in sender_acl_handles.sender_acl_domains.rw %} {% for domain in sender_acl_handles.sender_acl_domains.rw %}
<option value="{{ domain }}" selected> <option value="{{ domain }}" selected>
+1 -15
View File
@@ -36,20 +36,10 @@
{% if acl.mailbox_groups == 1 %} {% if acl.mailbox_groups == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-groups" role="tab" data-bs-toggle="tab" data-bs-target="#tab-groups">{{ lang.groups.groups }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-groups" role="tab" data-bs-toggle="tab" data-bs-target="#tab-groups">{{ lang.groups.groups }}</button></li>
{% endif %} {% endif %}
{% if mailcow_cc_role == 'admin' %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.policies }}</a>
<ul class="dropdown-menu">
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.policies_tls }}</button></li>
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-mail-policies" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mail-policies">{{ lang.mailbox.policies_mail }}</button></li>
</ul>
</li>
{% elseif acl.mail_policies == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-mail-policies" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mail-policies">{{ lang.mail_policy.mail_policies }}</button></li>
{% endif %}
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#tab-syncjobs">{{ lang.syncjobs.sync_jobs }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#tab-syncjobs">{{ lang.syncjobs.sync_jobs }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-filters" role="tab" data-bs-toggle="tab" data-bs-target="#tab-filters">{{ lang.mailbox.filters }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-filters" role="tab" data-bs-toggle="tab" data-bs-target="#tab-filters">{{ lang.mailbox.filters }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-bcc" role="tab" data-bs-toggle="tab" data-bs-target="#tab-bcc">{{ lang.mailbox.address_rewriting }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-bcc" role="tab" data-bs-toggle="tab" data-bs-target="#tab-bcc">{{ lang.mailbox.address_rewriting }}</button></li>
<li class="nav-item" role="presentation"{% if mailcow_cc_role != 'admin' %} class="d-none"{% endif %}><button class="nav-link" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.tls_policy_maps }}</button></li>
</ul> </ul>
<div class="row"> <div class="row">
@@ -69,9 +59,6 @@
{% if acl.mailbox_groups == 1 %} {% if acl.mailbox_groups == 1 %}
{% include 'mailbox/tab-groups.twig' %} {% include 'mailbox/tab-groups.twig' %}
{% endif %} {% endif %}
{% if acl.mail_policies == 1 %}
{% include 'mailbox/tab-mail-policies.twig' %}
{% endif %}
{% include 'mailbox/tab-syncjobs.twig' %} {% include 'mailbox/tab-syncjobs.twig' %}
{% include 'mailbox/tab-filters.twig' %} {% include 'mailbox/tab-filters.twig' %}
{% include 'mailbox/tab-bcc.twig' %} {% include 'mailbox/tab-bcc.twig' %}
@@ -88,7 +75,6 @@
var lang = {{ lang_mailbox|raw }}; var lang = {{ lang_mailbox|raw }};
lang.syncjobs = {{ lang_syncjobs|raw }}; lang.syncjobs = {{ lang_syncjobs|raw }};
lang.groups = {{ lang_groups|raw }}; lang.groups = {{ lang_groups|raw }};
lang.mail_policy = {{ lang_mail_policy|raw }};
var lang_rl = {{ lang_rl|raw }}; var lang_rl = {{ lang_rl|raw }};
var lang_edit = {{ lang_edit|raw }}; var lang_edit = {{ lang_edit|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
@@ -1,54 +0,0 @@
<div class="tab-pane fade" id="tab-mail-policies" role="tabpanel" aria-labelledby="tab-mail-policies">
<div class="card mb-4">
<div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-mail-policies" data-bs-toggle="collapse" aria-controls="collapse-tab-mail-policies">
{{ lang.mail_policy.mail_policies }} <span class="badge bg-info table-lines"></span>
</button>
<span class="d-none d-md-block">{{ lang.mail_policy.mail_policies }} <span class="badge bg-info table-lines"></span></span>
<div class="btn-group ms-auto d-flex">
<button class="btn btn-xs btn-secondary refresh_table" data-table="mail_policy_table">{{ lang.admin.refresh }}</button>
</div>
</div>
<div id="collapse-tab-mail-policies" class="card-body collapse" data-bs-parent="#mail-content">
<p class="text-muted">{{ lang.mail_policy.info }}</p>
<table id="mail_policy_table" class="table table-striped dt-responsive w-100"></table>
<div class="mass-actions-mailbox mt-4">
<div class="btn-group">
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="mail_policy" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-action="edit_selected" data-id="mail_policy" data-api-url='edit/mail-policy' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mail_policy" data-api-url='edit/mail-policy' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="mail_policy" data-api-url='delete/mail-policy' href="#">{{ lang.mailbox.remove }}</a></li>
</ul>
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addMailPolicyModal"><i class="bi bi-plus-lg"></i> {{ lang.mail_policy.add }}</a>
</div>
</div>
{# Test a rule: would the mail be accepted? Filled by js/site/mail-policy.js #}
<div class="card mt-4">
<div class="card-header">{{ lang.mail_policy.test }}</div>
<div class="card-body">
<p class="text-muted small">{{ lang.mail_policy.test_info }}</p>
<form class="row g-2 align-items-center" id="mail_policy_test" autocomplete="off">
<div class="col-12 col-md-4">
<input type="email" class="form-control" id="mail_policy_test_sender" placeholder="{{ lang.mail_policy.test_sender }}" aria-label="{{ lang.mail_policy.test_sender }}">
</div>
<div class="col-12 col-md-4">
<input type="email" class="form-control" id="mail_policy_test_recipient" placeholder="{{ lang.mail_policy.test_recipient }}" aria-label="{{ lang.mail_policy.test_recipient }}" required>
</div>
<div class="col-12 col-md-4 d-flex align-items-center">
<div class="form-check me-3">
<input class="form-check-input" type="checkbox" id="mail_policy_test_login" checked>
<label class="form-check-label" for="mail_policy_test_login">{{ lang.mail_policy.test_logged_in }}</label>
</div>
<button type="submit" class="btn btn-sm btn-secondary ms-auto"><i class="bi bi-play"></i> {{ lang.mail_policy.test_run }}</button>
</div>
</form>
<div id="mail_policy_test_result" class="mt-3"></div>
</div>
</div>
</div>
</div>
</div>
@@ -1,50 +0,0 @@
<div class="modal fade" id="addMailPolicyModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">{{ lang.mail_policy.add }}</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form" data-id="add_mail_policy" autocapitalize="none" autocorrect="off">
<input type="hidden" value="0" name="active">
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_domain">{{ lang.mail_policy.domain }}</label>
<div class="col-sm-10">
<select data-live-search="true" name="domain" id="add_mail_policy_domain" class="form-control mail-policy-domain-select" required>
{% for domain in domains %}
<option>{{ domain }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_name">{{ lang.mail_policy.name }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="add_mail_policy_name" maxlength="100" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_description">{{ lang.mail_policy.description }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" id="add_mail_policy_description" maxlength="255">
</div>
</div>
{% include 'edit/mail-policy-fields.twig' with {'policy': {}, 'id': 'add_mail_policy', 'groups': mail_policy_groups, 'mailboxes': mailboxes, 'ro': false, 'label_class': ' text-sm-end'} %}
<div class="row mb-2">
<div class="offset-sm-2 col-sm-10">
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="active" checked> {{ lang.mailbox.active }}</label>
</div>
</div>
</div>
<div class="row">
<div class="offset-sm-2 col-sm-10">
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="add_item" data-id="add_mail_policy" data-api-url='add/mail-policy' data-api-attr='{}' href="#">{{ lang.admin.add }}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
-3
View File
@@ -1362,6 +1362,3 @@
{% if acl.mailbox_groups == 1 %} {% if acl.mailbox_groups == 1 %}
{% include "modals/mailbox_group.twig" %} {% include "modals/mailbox_group.twig" %}
{% endif %} {% endif %}
{% if acl.mail_policies == 1 %}
{% include "modals/mail_policy.twig" %}
{% endif %}
+6 -7
View File
@@ -1,5 +1,5 @@
# Version of the database schema # Version of the database schema
x-db-version: &db_version DB_VERSION=07112026_1800 x-db-version: &db_version DB_VERSION=02112026_1600
services: services:
@@ -68,7 +68,7 @@ services:
- redis - redis
clamd-mailcow: clamd-mailcow:
image: ghcr.io/mailcow/clamd:1.4.6-1 image: ghcr.io/mailcow/clamd:1.4.6-2
restart: always restart: always
depends_on: depends_on:
unbound-mailcow: unbound-mailcow:
@@ -348,7 +348,7 @@ services:
- dovecot - dovecot
postfix-mailcow: postfix-mailcow:
image: ghcr.io/mailcow/postfix:3.10.12-2 image: ghcr.io/mailcow/postfix:3.10.13-1
depends_on: depends_on:
mysql-mailcow: mysql-mailcow:
condition: service_started condition: service_started
@@ -375,7 +375,6 @@ services:
- REDISPASS=${REDISPASS} - REDISPASS=${REDISPASS}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- SPAMHAUS_DQS_KEY=${SPAMHAUS_DQS_KEY:-} - SPAMHAUS_DQS_KEY=${SPAMHAUS_DQS_KEY:-}
- *db_version
cap_add: cap_add:
- NET_BIND_SERVICE - NET_BIND_SERVICE
ports: ports:
@@ -392,7 +391,7 @@ services:
- postfix - postfix
postfix-tlspol-mailcow: postfix-tlspol-mailcow:
image: ghcr.io/mailcow/postfix-tlspol:1.14.0 image: ghcr.io/mailcow/postfix-tlspol:1.14.5
depends_on: depends_on:
unbound-mailcow: unbound-mailcow:
condition: service_healthy condition: service_healthy
@@ -429,7 +428,7 @@ services:
- php-fpm-mailcow - php-fpm-mailcow
- sogo-mailcow - sogo-mailcow
- rspamd-mailcow - rspamd-mailcow
image: ghcr.io/mailcow/nginx:1.30.6-2 image: ghcr.io/mailcow/nginx:1.30.6-3
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment:
@@ -613,7 +612,7 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: ghcr.io/mailcow/dockerapi:2.15 image: ghcr.io/mailcow/dockerapi:2.16
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always
+82
View File
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Admin;
use Mailcow\Tests\Api\ApiTestCase;
/** Routes admin: administrator accounts */
class AdminRoutesTest extends ApiTestCase
{
private const DETAILS = ['username' => 'admin', 'tfa_active' => 0, 'tfa_active_int' => 0, 'active' => 1, 'active_int' => 1,
'created' => '2026-09-21 10:23:08', 'attributes' => ['force_tfa' => '0', 'force_pw_update' => '0']];
public function testGetAllCollectsTheDetailsOfEveryAdmin(): void
{
$result = $this->get('get/admin/all', ['script' => [
self::answer('admin', ['get'], ['admin', 'gone']),
self::answer('admin', ['details', 'admin'], self::DETAILS),
self::answer('admin', ['details', 'gone'], false),
]]);
$this->assertSame([self::DETAILS], $result->json());
$this->assertSame([['get'], ['details', 'admin'], ['details', 'gone']], $result->callsTo('admin'));
}
public function testGetAllWithoutAdmins(): void
{
$result = $this->get('get/admin/all', ['script' => [self::answer('admin', ['get'], [])]]);
$this->assertSame('{}', $result->body);
$this->assertSame([['get']], $result->callsTo('admin'));
}
public function testGetOne(): void
{
$found = $this->get('get/admin/admin', ['script' => [self::answer('admin', ['details', 'admin'], self::DETAILS)]]);
$this->assertSame(self::DETAILS, $found->json());
$missing = $this->get('get/admin/nobody', ['script' => [self::answer('admin', ['details'], false)]]);
$this->assertSame('{}', $missing->body);
$this->assertCalled($missing, 'admin', ['details', 'nobody']);
}
public function testAdd(): void
{
$attr = ['username' => 'admin2', 'password' => 'Rand0m-Passw0rd!', 'password2' => 'Rand0m-Passw0rd!', 'active' => 1];
$messages = [self::message('success', ['admin_added', 'admin2'], ['admin', 'add', ['username' => 'admin2', 'password' => '*']])];
$result = $this->post('add/admin', $attr, ['script' => [self::answer('admin', ['add'], null, $messages)]]);
$this->assertSame([['add', $attr]], $result->callsTo('admin'));
$this->assertSame($messages, $result->json());
}
public function testEdit(): void
{
$result = $this->post('edit/admin', ['items' => ['admin2', 'admin3'], 'attr' => ['active' => '0', 'force_tfa' => 1]]);
$this->assertSame([['edit', ['username' => ['admin2', 'admin3'], 'active' => '0', 'force_tfa' => 1]]], $result->callsTo('admin'));
}
public function testEditTakesASingleUsername(): void
{
$result = $this->post('edit/admin', ['items' => 'admin2', 'attr' => ['password' => 'x', 'password2' => 'x']]);
$this->assertCalled($result, 'admin', ['edit', ['username' => ['admin2'], 'password' => 'x', 'password2' => 'x']]);
}
public function testDelete(): void
{
$messages = [
self::message('success', ['admin_removed', 'admin2']),
self::message('danger', ['username_invalid', 'nobody']),
];
$result = $this->post('delete/admin', ['admin2', 'nobody'], ['script' => [self::answer('admin', ['delete'], null, $messages)]]);
$this->assertSame([['delete', ['username' => ['admin2', 'nobody']]]], $result->callsTo('admin'));
$this->assertSame($messages, $result->json());
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Admin;
use Mailcow\Tests\Api\ApiTestCase;
/** Routes api-key: the ApiKey module */
class ApiKeyRoutesTest extends ApiTestCase
{
private const API = 'Mailcow\ApiKey\ApiKeyModule->api->';
private const KEY = ['id' => 3, 'description' => 'provisioning', 'access' => 'rw', 'api_key' => '1B7E2C-94F0A3-6D5B18-C2E7F4-03A9D6',
'allow_from' => ['192.0.2.10'], 'skip_ip_check' => 0, 'service' => 0, 'active' => 1, 'created' => '2026-11-02 09:12:44', 'modified' => null];
public function testGetAll(): void
{
$result = $this->get('get/api-key/all', ['script' => [self::answer(self::API . 'getKeys', [], [self::KEY])]]);
$this->assertSame([self::KEY], $result->json());
$this->assertSame([[]], $result->callsTo(self::API . 'getKeys'));
}
public function testGetAllAnswersAnEmptyObjectWithoutKeys(): void
{
// also the answer for read-only API keys: the module returns no keys for them
$result = $this->get('get/api-key/all', ['session' => self::API_RO, 'script' => [self::answer(self::API . 'getKeys', [], [])]]);
$this->assertSame('{}', $result->body);
}
public function testGetOne(): void
{
$found = $this->get('get/api-key/3', ['script' => [self::answer(self::API . 'getKey', ['3'], self::KEY)]]);
$this->assertSame(self::KEY, $found->json());
$missing = $this->get('get/api-key/99', ['script' => [self::answer(self::API . 'getKey', [], [])]]);
$this->assertSame('{}', $missing->body);
$this->assertSame([['99']], $missing->callsTo(self::API . 'getKey'));
}
public function testAdd(): void
{
$attr = ['description' => 'provisioning', 'access' => 'rw', 'allow_from' => '192.0.2.10, 2001:db8::/64', 'skip_ip_check' => 0, 'active' => 1];
$messages = [self::message('success', ['api_key_added', 'provisioning'], ['api', 'add', 'key', $attr])];
$result = $this->post('add/api-key', $attr, ['script' => [self::answer(self::API . 'addKey', [], true, $messages)]]);
$this->assertSame([[$attr]], $result->callsTo(self::API . 'addKey'));
$this->assertSame($messages, $result->json());
}
public function testEdit(): void
{
$result = $this->post('edit/api-key', ['items' => [3, 4], 'attr' => ['regen_key' => 1]]);
$this->assertSame([[[3, 4], ['regen_key' => 1]]], $result->callsTo(self::API . 'editKeys'));
}
public function testEditTakesASingleId(): void
{
$result = $this->post('edit/api-key', ['items' => 3, 'attr' => ['active' => 0]]);
$this->assertSame([[[3], ['active' => 0]]], $result->callsTo(self::API . 'editKeys'));
}
public function testDelete(): void
{
$messages = [self::message('danger', ['api_key_service_locked', 'API_KEY'])];
$result = $this->post('delete/api-key', [1], ['script' => [self::answer(self::API . 'deleteKeys', [], true, $messages)]]);
$this->assertSame([[[1]]], $result->callsTo(self::API . 'deleteKeys'));
$this->assertSame($messages, $result->json());
}
}
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Admin;
use Mailcow\Tests\Api\ApiTestCase;
/** Routes domain-admin, da-acl and sso/domain-admin */
class DomainAdminRoutesTest extends ApiTestCase
{
private const DETAILS = ['username' => 'domainadmin1', 'tfa_active' => 0, 'tfa_active_int' => 0, 'active' => 1, 'active_int' => 1,
'created' => '2026-09-21 10:24:48', 'attributes' => ['force_tfa' => '0', 'force_pw_update' => '0'],
'selected_domains' => ['example.org'], 'unselected_domains' => ['example.com']];
public function testGetAllCollectsTheDetailsOfEveryDomainAdmin(): void
{
$result = $this->get('get/domain-admin/all', ['script' => [
self::answer('domain_admin', ['get'], ['domainadmin1', 'gone']),
self::answer('domain_admin', ['details', 'domainadmin1'], self::DETAILS),
self::answer('domain_admin', ['details', 'gone'], false),
]]);
$this->assertSame([self::DETAILS], $result->json());
$this->assertSame([['get'], ['details', 'domainadmin1'], ['details', 'gone']], $result->callsTo('domain_admin'));
}
public function testGetAllWithoutDomainAdmins(): void
{
$result = $this->get('get/domain-admin/all', ['script' => [self::answer('domain_admin', ['get'], [])]]);
$this->assertSame('{}', $result->body);
}
public function testGetOne(): void
{
$found = $this->get('get/domain-admin/domainadmin1', ['script' => [self::answer('domain_admin', ['details', 'domainadmin1'], self::DETAILS)]]);
$this->assertSame(self::DETAILS, $found->json());
$missing = $this->get('get/domain-admin/nobody', ['script' => [self::answer('domain_admin', ['details'], false)]]);
$this->assertSame('{}', $missing->body);
$this->assertSame([['details', 'nobody']], $missing->callsTo('domain_admin'));
}
public function testAdd(): void
{
$attr = ['username' => 'domainadmin1', 'password' => 'Rand0m-Passw0rd!', 'password2' => 'Rand0m-Passw0rd!',
'domains' => ['example.org', 'example.com'], 'active' => 1];
$result = $this->post('add/domain-admin', $attr);
$this->assertSame([['add', $attr]], $result->callsTo('domain_admin'));
}
public function testEdit(): void
{
$result = $this->post('edit/domain-admin', ['items' => ['domainadmin1'], 'attr' => ['domains' => ['example.org'], 'disable_tfa' => 1]]);
$this->assertSame([['edit', ['username' => ['domainadmin1'], 'domains' => ['example.org'], 'disable_tfa' => 1]]], $result->callsTo('domain_admin'));
}
public function testDelete(): void
{
$result = $this->post('delete/domain-admin', ['domainadmin1', 'domainadmin2']);
$this->assertSame([['delete', ['username' => ['domainadmin1', 'domainadmin2']]]], $result->callsTo('domain_admin'));
}
public function testEditAcl(): void
{
$messages = [self::message('success', ['acl_saved', 'domainadmin1'])];
$result = $this->post('edit/da-acl', ['items' => 'domainadmin1', 'attr' => ['da_acl' => ['syncjobs', 'quarantine']]],
['script' => [self::answer('acl', ['edit', 'domainadmin'], null, $messages)]]);
$this->assertSame([['edit', 'domainadmin', ['username' => ['domainadmin1'], 'da_acl' => ['syncjobs', 'quarantine']]]], $result->callsTo('acl'));
$this->assertSame($messages, $result->json());
}
public function testSsoTokenIsTheAnswer(): void
{
$token = ['token' => '591F6D-5C3DD2-7455CD-DAF1C1-AA4FCC'];
$result = $this->post('add/sso/domain-admin', ['username' => 'domainadmin1'],
['script' => [self::answer('domain_admin_sso', ['issue'], $token)]]);
$this->assertSame([['issue', ['username' => 'domainadmin1']]], $result->callsTo('domain_admin_sso'));
$this->assertSame($token, $result->json());
}
public function testSsoTokenFailureAnswersTheMessages(): void
{
$messages = [self::message('danger', ['object_doesnt_exist', 'nobody'])];
$failed = $this->post('add/sso/domain-admin', ['username' => 'nobody'],
['script' => [self::answer('domain_admin_sso', ['issue'], false, $messages)]]);
$this->assertSame($messages, $failed->json());
$silent = $this->post('add/sso/domain-admin', ['username' => 'nobody'],
['script' => [self::answer('domain_admin_sso', ['issue'], false)]]);
$this->assertSame(['type' => 'error', 'msg' => 'Cannot add item'], $silent->json());
}
public function testSsoForOtherObjectsAnswersNothing(): void
{
$result = $this->post('add/sso/mailbox', ['username' => 'user@example.org']);
$this->assertSame([200, ''], [$result->status, $result->body]);
$this->assertNotCalled($result, 'domain_admin_sso');
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Admin;
use Mailcow\Tests\Api\ApiTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
/** Routes logs/<type>[/<count>] */
class LogRoutesTest extends ApiTestCase
{
/** @return array<string, array{0: string, 1: string}> route type => log name of get_logs() */
public static function logs(): array
{
return [
'dovecot' => ['dovecot', 'dovecot-mailcow'],
'ratelimited' => ['ratelimited', 'ratelimited'],
'netfilter' => ['netfilter', 'netfilter-mailcow'],
'cron' => ['cron', 'cron-mailcow'],
'postfix' => ['postfix', 'postfix-mailcow'],
'autodiscover' => ['autodiscover', 'autodiscover-mailcow'],
'sogo' => ['sogo', 'sogo-mailcow'],
'ui' => ['ui', 'mailcow-ui'],
'sasl' => ['sasl', 'sasl'],
'watchdog' => ['watchdog', 'watchdog-mailcow'],
'acme' => ['acme', 'acme-mailcow'],
'api' => ['api', 'api-mailcow'],
'rspamd-history' => ['rspamd-history', 'rspamd-history'],
];
}
#[DataProvider('logs')]
public function testWithoutCount(string $type, string $log): void
{
$entries = [['time' => '1794308452', 'message' => 'first'], ['time' => '1794308451', 'message' => 'second']];
$result = $this->get("get/logs/$type", ['script' => [self::answer('get_logs', [$log], $entries)]]);
$this->assertSame([[$log]], $result->callsTo('get_logs'));
$this->assertSame($entries, $result->json());
}
#[DataProvider('logs')]
public function testCountOrRange(string $type, string $log): void
{
$this->assertSame([[$log, '50']], $this->get("get/logs/$type/50")->callsTo('get_logs'));
$this->assertSame([[$log, '2-3']], $this->get("get/logs/$type/2-3")->callsTo('get_logs'));
}
#[DataProvider('logs')]
public function testEmptyLogs(string $type, string $log): void
{
$this->assertSame('{}', $this->get("get/logs/$type/5", ['script' => [self::answer('get_logs', [$log], false)]])->body);
$this->assertSame('{}', $this->get("get/logs/$type", ['script' => [self::answer('get_logs', [$log], [])]])->body);
}
public function testCountKeepsOnlyDigitsAndDashes(): void
{
$this->assertSame([['postfix-mailcow', '12-30']], $this->get('get/logs/postfix/a12-3x0')->callsTo('get_logs'));
}
public function testTrailingSlashPassesAnEmptyCount(): void
{
$this->assertSame([['postfix-mailcow', '']], $this->get('get/logs/postfix/')->callsTo('get_logs'));
}
public function testRspamdStatsIgnoresTheCount(): void
{
$stats = ['version' => '3.12.1', 'scanned' => 300, 'actions' => ['reject' => 38]];
$result = $this->get('get/logs/rspamd-stats/10', ['script' => [self::answer('get_logs', ['rspamd-stats'], $stats)]]);
$this->assertSame([['rspamd-stats']], $result->callsTo('get_logs'));
$this->assertSame($stats, $result->json());
}
public function testUnknownLog(): void
{
$this->assertApiError($this->get('get/logs/clamd'), 404, 'route not found');
}
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Admin;
use Mailcow\Tests\Api\ApiTestCase;
/** Routes oauth2-client */
class OAuth2ClientRoutesTest extends ApiTestCase
{
private const CLIENT = ['id' => 1, 'client_id' => '17c76aaa88c0', 'client_secret' => '73fc668a88147e32a31ff80c',
'redirect_uri' => 'https://app.example.org/oauth/callback', 'grant_types' => null, 'scope' => 'profile', 'user_id' => null];
public function testGetAllCollectsTheDetailsOfEveryClient(): void
{
$result = $this->get('get/oauth2-client/all', ['script' => [
self::answer('oauth2', ['get', 'clients'], [1, 2]),
self::answer('oauth2', ['details', 'client', 1], self::CLIENT),
self::answer('oauth2', ['details', 'client', 2], false),
]]);
$this->assertSame([self::CLIENT], $result->json());
$this->assertSame([['get', 'clients'], ['details', 'client', 1], ['details', 'client', 2]], $result->callsTo('oauth2'));
}
public function testGetAllWithoutClients(): void
{
$this->assertSame('{}', $this->get('get/oauth2-client/all', ['script' => [self::answer('oauth2', ['get', 'clients'], null)]])->body);
}
public function testGetOne(): void
{
$found = $this->get('get/oauth2-client/1', ['script' => [self::answer('oauth2', ['details', 'client', '1'], self::CLIENT)]]);
$this->assertSame(self::CLIENT, $found->json());
$missing = $this->get('get/oauth2-client/9', ['script' => [self::answer('oauth2', ['details'], false)]]);
$this->assertSame('{}', $missing->body);
$this->assertSame([['details', 'client', '9']], $missing->callsTo('oauth2'));
}
public function testAdd(): void
{
$result = $this->post('add/oauth2-client', ['redirect_uri' => 'https://app.example.org/oauth/callback']);
$this->assertSame([['add', 'client', ['redirect_uri' => 'https://app.example.org/oauth/callback']]], $result->callsTo('oauth2'));
}
public function testEdit(): void
{
$result = $this->post('edit/oauth2-client', ['items' => [1, 2], 'attr' => ['renew_secret' => 1]]);
$this->assertSame([['edit', 'client', ['id' => [1, 2], 'renew_secret' => 1]]], $result->callsTo('oauth2'));
}
public function testDelete(): void
{
$result = $this->post('delete/oauth2-client', [1, 2]);
$this->assertSame([['delete', 'client', ['id' => [1, 2]]]], $result->callsTo('oauth2'));
}
}
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Admin;
use Mailcow\Tests\Api\ApiTestCase;
/** Routes self and fido2-fn act on the account of the session; the pending password update of a login */
class OwnAccountRoutesTest extends ApiTestCase
{
private const PASSWORDS = ['user_old_pass' => 'old', 'user_new_pass' => 'N3w-Passw0rd!', 'user_new_pass2' => 'N3w-Passw0rd!'];
public function testSelfDoesNothingForAnApiKey(): void
{
$result = $this->post('edit/self', ['attr' => self::PASSWORDS]);
$this->assertSame([200, ''], [$result->status, $result->body]);
$this->assertNotCalled($result, 'domain_admin');
$this->assertNotCalled($result, 'edit_user_account');
}
public function testSelfOfADomainAdmin(): void
{
$result = $this->post('edit/self', ['attr' => self::PASSWORDS], ['session' => self::DOMAIN_ADMIN]);
$this->assertSame([['edit', self::PASSWORDS]], $result->callsTo('domain_admin'));
}
public function testSelfOfAMailboxUser(): void
{
$result = $this->post('edit/self', ['attr' => self::PASSWORDS], ['session' => self::USER]);
$this->assertSame([[self::PASSWORDS]], $result->callsTo('edit_user_account'));
}
public function testFido2FriendlyName(): void
{
$attr = ['fido2_cid' => hash('sha256', 'credential'), 'fido2_fn' => 'YubiKey'];
$result = $this->post('edit/fido2-fn', ['attr' => $attr]);
$this->assertSame([[['action' => 'edit_fn', 'fido2_attrs' => $attr]]], $result->callsTo('fido2'));
}
public function testPendingPasswordUpdateAllowsOnlyThePasswordRoutes(): void
{
$session = self::ADMIN + ['pending_pw_update' => true];
$this->assertCalled($this->post('edit/admin', ['items' => 'admin', 'attr' => ['password' => 'x', 'password2' => 'x']], ['session' => $session]),
'admin', ['edit', ['username' => ['admin'], 'password' => 'x', 'password2' => 'x']]);
$this->assertCalled($this->get('get/passwordpolicy', ['session' => $session]), 'password_complexity', ['get']);
$denied = $this->post('edit/domain-admin', ['items' => 'domainadmin1', 'attr' => ['active' => 0]], ['session' => $session]);
$this->assertApiError($denied, 403, 'API access denied until the pending account setup is finished');
$this->assertNotCalled($denied, 'domain_admin');
}
}
@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Admin;
use Mailcow\Tests\Api\ApiTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
/** Routes passwordpolicy, reset-password-notification, the UI customization routes and cors */
class SettingsRoutesTest extends ApiTestCase
{
private const POLICY = ['length' => '8', 'chars' => '1', 'special_chars' => '0', 'lowerupper' => '1', 'numbers' => '1'];
public function testGetPasswordPolicy(): void
{
foreach (['get/passwordpolicy', 'get/passwordpolicy/all'] as $route) {
$result = $this->get($route, ['script' => [self::answer('password_complexity', ['get'], self::POLICY)]]);
$this->assertSame([['get']], $result->callsTo('password_complexity'), $route);
$this->assertSame(self::POLICY, $result->json(), $route);
}
}
public function testGetPasswordPolicyAsHtml(): void
{
$html = '<p class="help-block small">- Minimum password length is 8</p>';
$result = $this->get('get/passwordpolicy/html', ['script' => [self::answer('password_complexity', ['html'], $html)]]);
$this->assertSame([['html']], $result->callsTo('password_complexity'));
$this->assertSame($html, $result->json());
}
public function testGetPasswordPolicyFailure(): void
{
$this->assertSame('{}', $this->get('get/passwordpolicy', ['script' => [self::answer('password_complexity', ['get'], false)]])->body);
$this->assertSame('{}', $this->get('get/passwordpolicy/html', ['script' => [self::answer('password_complexity', ['html'], false)]])->body);
}
public function testEditPasswordPolicyIgnoresItems(): void
{
$result = $this->post('edit/passwordpolicy', ['items' => ['x'], 'attr' => ['length' => 12, 'numbers' => 1]]);
$this->assertSame([['edit', ['length' => 12, 'numbers' => 1]]], $result->callsTo('password_complexity'));
}
public function testEditPasswordResetNotification(): void
{
$attr = ['from' => 'noreply@example.org', 'subject' => 'Reset your password', 'text_tmpl' => '{{link}}', 'html_tmpl' => ''];
$result = $this->post('edit/reset-password-notification', ['attr' => $attr]);
$this->assertSame([['edit_notification', $attr]], $result->callsTo('reset_password'));
}
/** @return array<string, array{0: string, 1: array}> */
public static function customizations(): array
{
return [
'ui_texts' => ['ui_texts', ['title_name' => 'Example Mail', 'ui_announcement_type' => 'warning', 'ui_announcement_active' => 1]],
'ip_check' => ['ip_check', ['ip_check_opt_in' => '1']],
'custom_login' => ['custom_login', ['hide_admin_quicklink' => 1, 'force_sso' => 0]],
'ui_theme' => ['ui_theme', ['ui_theme' => 'servercow']],
'app_links' => ['app_links', ['app' => ['Wiki'], 'href' => ['https://wiki.example.org'], 'user_href' => [''], 'hide' => ['0']]],
];
}
#[DataProvider('customizations')]
public function testCustomizationsPassAttrOnly(string $item, array $attr): void
{
$messages = [self::message('success', $item === 'ip_check' ? 'ip_check_opt_in_modified' : $item)];
$result = $this->post("edit/$item", ['items' => ['ignored'], 'attr' => $attr], ['script' => [self::answer('customize', ['edit', $item], null, $messages)]]);
$this->assertSame([['edit', $item, $attr]], $result->callsTo('customize'));
$this->assertSame($messages, $result->json());
}
public function testCustomizationWithoutAttrPassesNothing(): void
{
$result = $this->post('edit/app_links', ['items' => []]);
$this->assertSame([['edit', 'app_links', []]], $result->callsTo('customize'));
}
public function testEditCors(): void
{
$attr = ['allowed_origins' => ['https://app.example.org'], 'allowed_methods' => ['GET', 'POST']];
$result = $this->post('edit/cors', ['attr' => $attr]);
$this->assertSame([['set_headers'], ['edit', $attr]], $result->callsTo('cors'));
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Admin;
use Mailcow\Tests\Api\ApiTestCase;
/** Routes status/<object> */
class StatusRoutesTest extends ApiTestCase
{
public function testContainersAreReducedToStateStartAndImage(): void
{
$info = [
'dovecot-mailcow' => ['State' => ['Status' => 'running', 'StartedAt' => '2026-11-10T11:00:51.541059812Z', 'Pid' => 42],
'Config' => ['Image' => 'ghcr.io/mailcow/dovecot:2.3.21.1-4', 'Hostname' => 'dovecot'], 'Id' => '5d0f3cbb2a6e'],
'ofelia-mailcow' => ['State' => ['Status' => 'exited', 'StartedAt' => '2026-11-09T08:00:00Z'],
'Config' => ['Image' => 'mcuadros/ofelia:latest'], 'Id' => '77aa01'],
];
$result = $this->get('get/status/containers', ['script' => [self::answer('docker', ['info'], $info)]]);
$this->assertSame([['info']], $result->callsTo('docker'));
$this->assertSame([
'dovecot-mailcow' => ['type' => 'info', 'container' => 'dovecot-mailcow', 'state' => 'running',
'started_at' => '2026-11-10T11:00:51.541059812Z', 'image' => 'ghcr.io/mailcow/dovecot:2.3.21.1-4'],
'ofelia-mailcow' => ['type' => 'info', 'container' => 'ofelia-mailcow', 'state' => 'exited',
'started_at' => '2026-11-09T08:00:00Z', 'image' => 'mcuadros/ofelia:latest'],
], $result->json());
$this->assertStringContainsString('"ghcr.io/mailcow/dovecot', $result->body);
}
public function testContainersWithoutDockerapi(): void
{
$result = $this->get('get/status/containers', ['script' => [self::answer('docker', ['info'], 'Could not resolve host: dockerapi')]]);
$this->assertSame('null', $result->body);
}
public function testContainerStatsByContainerId(): void
{
$stats = [['read' => '2026-11-10T19:20:01Z', 'pids_stats' => ['current' => 38]], ['read' => '2026-11-10T19:20:06Z', 'pids_stats' => ['current' => 39]]];
$result = $this->get('get/status/container/5d0f3cbb2a6e', ['script' => [self::answer('docker', ['container_stats'], $stats)]]);
$this->assertSame([['container_stats', '5d0f3cbb2a6e']], $result->callsTo('docker'));
$this->assertSame($stats, $result->json());
}
public function testContainerStatsWithoutId(): void
{
$result = $this->get('get/status/container', ['script' => [self::answer('docker', ['container_stats'], false)]]);
$this->assertSame([['container_stats', null]], $result->callsTo('docker'));
$this->assertSame('false', $result->body);
}
public function testVmailSplitsTheDfLine(): void
{
$df = json_encode('/dev/sda1,105G,65G,40G,66%,/var/vmail');
$result = $this->get('get/status/vmail', ['script' => [self::answer('docker', ['post', 'dovecot-mailcow', 'exec'], $df)]]);
$this->assertSame([['post', 'dovecot-mailcow', 'exec', ['cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail']]], $result->callsTo('docker'));
$this->assertSame(['type' => 'info', 'disk' => '/dev/sda1', 'used' => '65G', 'total' => '105G', 'used_percent' => '66%'], $result->json());
}
public function testHost(): void
{
$stats = ['cpu' => ['cores' => 4, 'usage' => 6], 'memory' => ['total' => 12541669376, 'usage' => 62.5], 'uptime' => 703800.5,
'system_time' => '10.11.2026 19:13:07', 'architecture' => 'x86_64'];
$result = $this->get('get/status/host', ['script' => [self::answer('docker', ['host_stats'], $stats)]]);
$this->assertSame([['host_stats']], $result->callsTo('docker'));
$this->assertSame($stats, $result->json());
}
public function testPublicAddressesAskMailcowEmail(): void
{
$result = $this->get('get/status/host/ip', ['script' => [
self::answer('curl_exec', [], '198.51.100.7', once: true),
self::answer('curl_exec', [], '2001:db8::7', once: true),
]]);
$this->assertSame([[null, CURLOPT_URL, 'http://ipv4.mailcow.email'], [null, CURLOPT_URL, 'http://ipv6.mailcow.email']],
array_values(array_filter($result->callsTo('curl_setopt'), fn(array $args): bool => $args[1] === CURLOPT_URL)));
$this->assertSame(['ipv4' => '198.51.100.7', 'ipv6' => '2001:db8::7'], $result->json());
}
public function testHostWithAnotherSegmentAnswersNothing(): void
{
$result = $this->get('get/status/host/cpu');
$this->assertSame([200, ''], [$result->status, $result->body]);
$this->assertNotCalled($result, 'docker');
}
public function testVersion(): void
{
// $MAILCOW_GIT_VERSION comes from app_info.inc.php, which the harness does not load
$this->assertSame(['version' => null], $this->get('get/status/version')->json());
}
public function testUnknownStatusAnswersNothing(): void
{
$result = $this->get('get/status/disks');
$this->assertSame([200, ''], [$result->status, $result->body]);
}
public function testOnlyForAdmins(): void
{
foreach (['containers', 'vmail', 'host', 'version'] as $object) {
$result = $this->get("get/status/$object", ['session' => self::DOMAIN_ADMIN]);
$this->assertSame([200, ''], [$result->status, $result->body], $object);
$this->assertNotCalled($result, 'docker');
}
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api;
/** What json_api.php did for one request of ApiTestCase: status, output and the calls it made */
final class ApiResult
{
/**
* @param list<array{0: string, 1: array}> $calls function or method name and arguments, in call order
*/
public function __construct(
public readonly int $status,
public readonly string $body,
public readonly array $calls,
public readonly ?array $sessionReturn,
public readonly ?string $error
) {
}
/** The decoded output, null if it is no JSON */
public function json(): mixed
{
return json_decode($this->body, true);
}
/**
* The arguments of every call of $name, e.g. 'mailbox' or 'Mailcow\ApiKey\ApiKeyModule->api->addKey'
*
* @return list<array>
*/
public function callsTo(string $name): array
{
return array_values(array_map(fn(array $call): array => $call[1], array_filter($this->calls, fn(array $call): bool => $call[0] === $name)));
}
/** Names of the calls, without the API log of every request (redis->lPush) */
public function callNames(): array
{
return array_values(array_filter(array_column($this->calls, 0), fn(string $name): bool => $name !== 'redis->lPush'));
}
}
+150
View File
@@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api;
use PHPUnit\Framework\TestCase;
/**
* Runs requests through the real data/web/json_api.php in a process of its own (Harness/runner.php). The legacy
* functions (mailbox(), bcc(), ...) and the module classes it calls are recorders: a test checks the HTTP behaviour of
* a route and which backend call it makes with which arguments, not the backend itself.
*
* $result = $this->post('add/alias', ['address' => 'a@example.org', 'goto' => 'b@example.org']);
* $this->assertCalled($result, 'mailbox', ['add', 'alias', ['address' => 'a@example.org', 'goto' => 'b@example.org']]);
*
* Backend answers are scripted with self::answer(); a call without one returns null (module chains like
* Module::fromGlobals()->api->x() resolve to recorders).
*/
abstract class ApiTestCase extends TestCase
{
/** Session of a read/write API key (sessions.inc.php logs it in as the admin "API") */
protected const API_RW = ['mailcow_cc_username' => 'API', 'mailcow_cc_role' => 'admin', 'mailcow_cc_api' => true, 'mailcow_cc_api_access' => 'rw'];
/** Session of a read-only API key */
protected const API_RO = ['mailcow_cc_username' => 'API', 'mailcow_cc_role' => 'admin', 'mailcow_cc_api' => true, 'mailcow_cc_api_access' => 'ro'];
/** UI sessions */
protected const ADMIN = ['mailcow_cc_username' => 'admin', 'mailcow_cc_role' => 'admin'];
protected const DOMAIN_ADMIN = ['mailcow_cc_username' => 'domainadmin', 'mailcow_cc_role' => 'domainadmin'];
protected const USER = ['mailcow_cc_username' => 'user@example.org', 'mailcow_cc_role' => 'user'];
protected const NO_SESSION = [];
private const JSON_API = __DIR__ . '/../../../data/web/json_api.php';
/** PHP functions with outside effects that json_api.php calls directly: disabled, so the harness records them */
private const DISABLED = ['curl_init', 'curl_setopt', 'curl_setopt_array', 'curl_exec', 'curl_close', 'curl_error', 'curl_errno', 'curl_getinfo'];
/**
* One scripted backend answer: the first call of $call whose arguments start with $args returns $return and
* appends $messages to $_SESSION['return'] (what the legacy functions do), $once makes it answer one call only.
*/
protected static function answer(string $call, array $args = [], mixed $return = null, array $messages = [], bool $once = false): array
{
return ['call' => $call, 'args' => $args, 'return' => $return, 'messages' => $messages, 'once' => $once];
}
/** A mailcow message as the legacy functions put it into $_SESSION['return'] */
protected static function message(string $type, string|array $msg, array $log = []): array
{
return ['type' => $type, 'log' => $log, 'msg' => $msg];
}
/**
* @param array{session?: array, script?: list<array>, server?: array, query?: array} $options
*/
protected function get(string $route, array $options = []): ApiResult
{
return $this->request('GET', $route, $options);
}
/**
* POST with a JSON body (any value; a string is sent as it is), as API clients send it
*
* @param array{session?: array, script?: list<array>, server?: array, query?: array, content_type?: string} $options
*/
protected function post(string $route, mixed $json = null, array $options = []): ApiResult
{
if ($json !== null) {
$options['body'] = is_string($json) ? $json : json_encode($json);
$options['content_type'] ??= 'application/json';
}
return $this->request('POST', $route, $options);
}
/**
* POST with form fields (attr, items as JSON strings), as the mailcow UI sends it
*
* @param array<string, string> $fields
*/
protected function postForm(string $route, array $fields, array $options = []): ApiResult
{
$options['post'] = $fields;
$options['content_type'] ??= 'application/x-www-form-urlencoded';
return $this->request('POST', $route, $options);
}
/**
* @param string $route the part after /api/v1/, e.g. "get/domain/all"
* @param array{session?: array, script?: list<array>, server?: array, query?: array, post?: array, body?: string,
* content_type?: string, globals?: array, allow_error?: bool} $options globals: variables prerequisites.inc.php
* would set (e.g. iam_settings)
*/
protected function request(string $method, string $route, array $options = []): ApiResult
{
$dir = sys_get_temp_dir();
$requestFile = tempnam($dir, 'api-req-');
$resultFile = tempnam($dir, 'api-res-');
try {
file_put_contents($requestFile, json_encode([
'json_api' => realpath(self::JSON_API),
'method' => $method,
'query' => ltrim(preg_replace('#^/?api/v1/#', '', $route), '/'),
'get' => $options['query'] ?? [],
'post' => $options['post'] ?? [],
'body' => $options['body'] ?? null,
'content_type' => $options['content_type'] ?? null,
'server' => $options['server'] ?? [],
'session' => $options['session'] ?? self::API_RW,
'script' => $options['script'] ?? [],
'globals' => $options['globals'] ?? [],
'result' => $resultFile,
], JSON_THROW_ON_ERROR));
$process = proc_open([PHP_BINARY, '-d', 'display_errors=stderr', '-d', 'disable_functions=' . implode(',', self::DISABLED),
__DIR__ . '/Harness/runner.php', $requestFile],
[0 => ['file', '/dev/null', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
$data = json_decode((string)file_get_contents($resultFile), true);
$this->assertIsArray($data, "the harness wrote no result for $method $route:\n$stdout$stderr");
} finally {
@unlink($requestFile);
@unlink($resultFile);
}
$result = new ApiResult((int)$data['status'], (string)$data['body'], $data['calls'], $data['session_return'], $data['error']);
if (empty($options['allow_error'])) {
$this->assertNull($result->error, "json_api.php failed for $method $route");
}
return $result;
}
/** At least one call of $name had exactly $args */
protected function assertCalled(ApiResult $result, string $name, array $args): void
{
$this->assertContains($args, $result->callsTo($name),
"no call $name(" . json_encode($args) . "), calls were:\n" . json_encode($result->calls, JSON_PRETTY_PRINT));
}
/** $name was not called at all */
protected function assertNotCalled(ApiResult $result, string $name): void
{
$this->assertSame([], $result->callsTo($name), "unexpected call of $name");
}
/** The JSON answer json_api.php gives without calling a backend, e.g. 404 route not found */
protected function assertApiError(ApiResult $result, int $status, string $msg): void
{
$this->assertSame([$status, ['type' => 'error', 'msg' => $msg]], [$result->status, $result->json()], $result->body);
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Domains;
use Mailcow\Tests\Api\ApiTestCase;
/** get/add/edit/delete alias-domain */
class AliasDomainTest extends ApiTestCase
{
public function testAllAliasDomainsAreReadOneByOne(): void
{
$result = $this->get('get/alias-domain/all', ['script' => [
self::answer('mailbox', ['get', 'alias_domains'], ['example.net', 'example.com']),
self::answer('mailbox', ['get', 'alias_domain_details', 'example.net'], ['alias_domain' => 'example.net', 'target_domain' => 'example.org']),
self::answer('mailbox', ['get', 'alias_domain_details', 'example.com'], false),
]]);
$this->assertCalled($result, 'mailbox', ['get', 'alias_domains']);
$this->assertSame([['alias_domain' => 'example.net', 'target_domain' => 'example.org']], $result->json());
}
public function testAllAliasDomainsWithoutAliasDomains(): void
{
$result = $this->get('get/alias-domain/all', ['script' => [self::answer('mailbox', ['get', 'alias_domains'], [])]]);
$this->assertSame('{}', $result->body);
$this->assertSame([['get', 'alias_domains']], $result->callsTo('mailbox'));
}
public function testOneAliasDomain(): void
{
$result = $this->get('get/alias-domain/example.net', ['script' => [
self::answer('mailbox', ['get', 'alias_domain_details', 'example.net'], ['alias_domain' => 'example.net', 'rl' => false]),
]]);
$this->assertSame(['alias_domain' => 'example.net', 'rl' => false], $result->json());
}
public function testUnknownAliasDomain(): void
{
$result = $this->get('get/alias-domain/example.com', ['script' => [self::answer('mailbox', ['get', 'alias_domain_details'], false)]]);
$this->assertCalled($result, 'mailbox', ['get', 'alias_domain_details', 'example.com']);
$this->assertSame('{}', $result->body);
}
public function testAddAliasDomains(): void
{
$attr = ['alias_domain' => "example.net\nexample.com", 'target_domain' => 'example.org', 'active' => '1', 'key_size' => '2048', 'dkim_selector' => 'dkim'];
$result = $this->post('add/alias-domain', $attr);
$this->assertCalled($result, 'mailbox', ['add', 'alias_domain', $attr]);
}
public function testEditAliasDomains(): void
{
$result = $this->post('edit/alias-domain', ['items' => ['example.net', 'example.com'], 'attr' => ['active' => '0']]);
$this->assertCalled($result, 'mailbox', ['edit', 'alias_domain', ['alias_domain' => ['example.net', 'example.com'], 'active' => '0']]);
}
public function testDeleteAliasDomains(): void
{
$result = $this->post('delete/alias-domain', ['example.net']);
$this->assertCalled($result, 'mailbox', ['delete', 'alias_domain', ['alias_domain' => ['example.net']]]);
}
}
+62
View File
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Domains;
use Mailcow\Tests\Api\ApiTestCase;
/** get/dkim, add/dkim, add/dkim_duplicate, add/dkim_import, delete/dkim */
class DkimTest extends ApiTestCase
{
public function testDkimKeyOfADomain(): void
{
$key = ['pubkey' => 'MIIBIjAN', 'length' => '2048', 'dkim_txt' => 'v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjAN', 'dkim_selector' => 'dkim', 'privkey' => ''];
$result = $this->get('get/dkim/example.org', ['script' => [self::answer('dkim', ['details', 'example.org'], $key)]]);
$this->assertSame([['details', 'example.org']], $result->callsTo('dkim'));
$this->assertSame($key, $result->json());
}
public function testDomainWithoutKey(): void
{
$result = $this->get('get/dkim/example.com', ['script' => [self::answer('dkim', ['details'], [])]]);
$this->assertSame('{}', $result->body);
}
public function testGenerateKeys(): void
{
$attr = ['domains' => 'example.org, example.net', 'dkim_selector' => 'dkim', 'key_size' => 2048];
$result = $this->post('add/dkim', $attr);
$this->assertSame([['add', $attr]], $result->callsTo('dkim'));
}
public function testDuplicateKey(): void
{
$attr = ['from_domain' => 'example.org', 'to_domain' => ['example.net', 'example.com']];
$result = $this->post('add/dkim_duplicate', $attr);
$this->assertSame([['duplicate', $attr]], $result->callsTo('dkim'));
}
public function testImportKey(): void
{
$attr = ['domain' => 'example.org', 'dkim_selector' => 'dkim2026', 'private_key_file' => "-----BEGIN PRIVATE KEY-----\nMIIE\n-----END PRIVATE KEY-----", 'overwrite_existing' => '1'];
$result = $this->post('add/dkim_import', $attr);
$this->assertSame([['import', $attr]], $result->callsTo('dkim'));
}
public function testDeleteKeys(): void
{
$result = $this->post('delete/dkim', ['example.org', 'example.net']);
$this->assertSame([['delete', ['domains' => ['example.org', 'example.net']]]], $result->callsTo('dkim'));
}
}
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Domains;
use Mailcow\Tests\Api\ApiTestCase;
/** get/policy_wl_domain, get/policy_bl_domain, add/domain-policy, delete/domain-policy */
class DomainPolicyTest extends ApiTestCase
{
private const LISTS = [
'whitelist' => [['object' => 'example.org', 'value' => '*@partner.example.com', 'prefid' => 12]],
'blacklist' => [['object' => 'anna@example.org', 'value' => '*@spam.example.com', 'prefid' => 13]],
];
public function testAllowListOfADomain(): void
{
$result = $this->get('get/policy_wl_domain/example.org', ['script' => [self::answer('policy', ['get', 'domain', 'example.org'], self::LISTS)]]);
$this->assertSame([['get', 'domain', 'example.org']], $result->callsTo('policy'));
$this->assertSame(self::LISTS['whitelist'], $result->json());
}
public function testDenyListOfADomain(): void
{
$result = $this->get('get/policy_bl_domain/example.org', ['script' => [self::answer('policy', ['get', 'domain', 'example.org'], self::LISTS)]]);
$this->assertSame([['get', 'domain', 'example.org']], $result->callsTo('policy'));
$this->assertSame(self::LISTS['blacklist'], $result->json());
}
public function testEmptyListsAndUnknownDomains(): void
{
$empty = $this->get('get/policy_wl_domain/example.org', ['script' => [self::answer('policy', ['get', 'domain'], ['whitelist' => [], 'blacklist' => []])]]);
$this->assertSame('{}', $empty->body);
$unknown = $this->get('get/policy_bl_domain/example.com', ['script' => [self::answer('policy', ['get', 'domain'], false)]]);
$this->assertSame('{}', $unknown->body);
}
public function testAddEntry(): void
{
$attr = ['domain' => 'example.org', 'object_list' => 'wl', 'object_from' => '*@partner.example.com'];
$result = $this->post('add/domain-policy', $attr);
$this->assertSame([['add', 'domain', $attr]], $result->callsTo('policy'));
}
public function testDeleteEntries(): void
{
$result = $this->post('delete/domain-policy', [12, 13]);
$this->assertSame([['delete', 'domain', ['prefid' => [12, 13]]]], $result->callsTo('policy'));
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Domains;
use Mailcow\Tests\Api\ApiTestCase;
/** get/rl-domain, edit/rl-domain */
class DomainRatelimitTest extends ApiTestCase
{
public function testRatelimitOfADomain(): void
{
$result = $this->get('get/rl-domain/example.org', ['script' => [self::answer('ratelimit', ['get', 'domain', 'example.org'], ['value' => '100', 'frame' => 'h'])]]);
$this->assertSame([['get', 'domain', 'example.org']], $result->callsTo('ratelimit'));
$this->assertSame(['value' => '100', 'frame' => 'h'], $result->json());
}
public function testDomainWithoutRatelimit(): void
{
$result = $this->get('get/rl-domain/example.org', ['script' => [self::answer('ratelimit', ['get', 'domain'], false)]]);
$this->assertSame('{}', $result->body);
}
public function testAllRatelimitsOfDomainsAndAliasDomains(): void
{
$result = $this->get('get/rl-domain/all', ['script' => [
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com']),
self::answer('mailbox', ['get', 'alias_domains'], ['example.net']),
self::answer('ratelimit', ['get', 'domain', 'example.org'], ['value' => '100', 'frame' => 'h']),
self::answer('ratelimit', ['get', 'domain', 'example.com'], false),
self::answer('ratelimit', ['get', 'domain', 'example.net'], ['value' => '10', 'frame' => 's']),
]]);
$this->assertSame([['get', 'domains'], ['get', 'alias_domains']], $result->callsTo('mailbox'));
$this->assertSame([['get', 'domain', 'example.org'], ['get', 'domain', 'example.com'], ['get', 'domain', 'example.net']], $result->callsTo('ratelimit'));
$this->assertSame([
['value' => '100', 'frame' => 'h', 'domain' => 'example.org'],
['value' => '10', 'frame' => 's', 'domain' => 'example.net'],
], $result->json());
}
public function testAllWithoutRatelimits(): void
{
$none = $this->get('get/rl-domain/all', ['script' => [
self::answer('mailbox', ['get', 'domains'], ['example.org']),
self::answer('mailbox', ['get', 'alias_domains'], []),
self::answer('ratelimit', ['get', 'domain'], false),
]]);
$this->assertSame('{}', $none->body);
$noDomains = $this->get('get/rl-domain/all', ['script' => [
self::answer('mailbox', ['get', 'domains'], []),
self::answer('mailbox', ['get', 'alias_domains'], []),
]]);
$this->assertSame('{}', $noDomains->body);
$this->assertNotCalled($noDomains, 'ratelimit');
}
public function testSetRatelimits(): void
{
$result = $this->post('edit/rl-domain', ['items' => ['example.org', 'example.net'], 'attr' => ['rl_value' => 100, 'rl_frame' => 'h']]);
$this->assertSame([['edit', 'domain', ['object' => ['example.org', 'example.net'], 'rl_value' => 100, 'rl_frame' => 'h']]], $result->callsTo('ratelimit'));
}
}
+115
View File
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Domains;
use Mailcow\Tests\Api\ApiResult;
use Mailcow\Tests\Api\ApiTestCase;
/** search/domain: DataTables server-side processing (SSP) of the domain table */
class DomainSearchTest extends ApiTestCase
{
private const REQUEST = [
'draw' => 3,
'start' => 0,
'length' => 10,
'search' => ['value' => 'example'],
'order' => [['column' => 2, 'dir' => 'asc']],
'columns' => [
['data' => '', 'searchable' => false, 'orderable' => false],
['data' => 'chkbox', 'searchable' => false, 'orderable' => false],
['data' => 'domain_h_name', 'searchable' => true, 'orderable' => true, 'search' => ['value' => '']],
],
];
private const PAGE = ['draw' => 3, 'recordsTotal' => 12, 'recordsFiltered' => 2, 'data' => [[2 => 'example.org'], [2 => 'example.com']]];
public function testAdminsSearchAllDomains(): void
{
$result = $this->post('search/domain', self::REQUEST, ['script' => [self::answer('SSP::simple', [], self::PAGE)]]);
$call = $this->sspCall($result, 'SSP::simple');
$this->assertSame([self::REQUEST, '@pdo', ['domain', 'd'], 'domain'], array_slice($call, 0, 4));
$this->assertSame([
2 => 'domain', 3 => 'aliases', 4 => 'mailboxes', 5 => 'quota', 6 => 'stats', 7 => 'defquota', 8 => 'maxquota',
10 => 'backupmx', 14 => 'tags', 15 => 'active',
], array_column($call[4], 'db', 'dt'));
$this->assertNotCalled($result, 'SSP::complex');
}
public function testRowsAreReplacedByTheDomainDetails(): void
{
$result = $this->post('search/domain', self::REQUEST, ['script' => [
self::answer('SSP::simple', [], self::PAGE),
self::answer('mailbox', ['get', 'domain_details', 'example.org'], ['domain_name' => 'example.org']),
self::answer('mailbox', ['get', 'domain_details', 'example.com'], false),
]]);
$this->assertSame([['get', 'domain_details', 'example.org'], ['get', 'domain_details', 'example.com']], $result->callsTo('mailbox'));
$this->assertSame(['draw' => 3, 'recordsTotal' => 12, 'recordsFiltered' => 2, 'data' => [['domain_name' => 'example.org']]], $result->json());
}
public function testAnEmptyPageIsAnsweredAsItIs(): void
{
$page = ['draw' => 1, 'recordsTotal' => 12, 'recordsFiltered' => 0, 'data' => []];
$result = $this->post('search/domain', ['draw' => 1], ['script' => [self::answer('SSP::simple', [], $page)]]);
$this->assertSame($page, $result->json());
$this->assertNotCalled($result, 'mailbox');
}
public function testErrorsOfTheSearchArePassedOn(): void
{
$result = $this->post('search/domain', self::REQUEST, ['script' => [self::answer('SSP::simple', [], ['error' => 'An SQL error occurred: unknown column'])]]);
$this->assertSame(['error' => 'An SQL error occurred: unknown column'], $result->json());
}
public function testReadOnlyKeysCanSearch(): void
{
$result = $this->post('search/domain', self::REQUEST, ['session' => self::API_RO, 'script' => [self::answer('SSP::simple', [], self::PAGE)]]);
$this->assertSame(200, $result->status);
$this->assertSame(self::REQUEST, $this->sspCall($result, 'SSP::simple')[0]);
}
public function testDomainAdminsSearchTheirDomains(): void
{
$result = $this->post('search/domain', self::REQUEST, ['session' => self::DOMAIN_ADMIN, 'script' => [self::answer('SSP::complex', [], self::PAGE)]]);
$call = $this->sspCall($result, 'SSP::complex');
$this->assertSame([self::REQUEST, '@pdo', ['domain', 'd'], 'domain'], array_slice($call, 0, 4));
$this->assertSame(['INNER JOIN domain_admins as da ON da.domain = d.domain', [
'condition' => 'da.active = 1 and da.username = :username',
'bindings' => ['username' => 'domainadmin'],
]], array_slice($call, 5));
$this->assertNotCalled($result, 'SSP::simple');
}
public function testOtherLoginsAreRefused(): void
{
foreach ([self::USER, self::NO_SESSION] as $session) {
$result = $this->post('search/domain', self::REQUEST, ['session' => $session]);
$this->assertApiError($result, 403, 'Insufficient permissions');
$this->assertNotCalled($result, 'SSP::simple');
$this->assertNotCalled($result, 'SSP::complex');
}
}
public function testWithoutJsonBodyTheSearchGetsNoParameters(): void
{
$result = $this->request('POST', 'search/domain', ['script' => [self::answer('SSP::simple', [], self::PAGE)]]);
$this->assertNull($this->sspCall($result, 'SSP::simple')[0]);
}
private function sspCall(ApiResult $result, string $name): array
{
$calls = $result->callsTo($name);
$this->assertCount(1, $calls, "one call of $name");
return $calls[0];
}
}
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Domains;
use Mailcow\Tests\Api\ApiTestCase;
/** get/add/edit/delete domain/template */
class DomainTemplateTest extends ApiTestCase
{
private const TEMPLATE = ['id' => 1, 'template' => 'Default', 'type' => 'domain', 'attributes' => ['max_num_aliases_for_domain' => 400]];
public function testAllTemplates(): void
{
$result = $this->get('get/domain/template/all', ['script' => [self::answer('mailbox', ['get', 'domain_templates'], [self::TEMPLATE])]]);
$this->assertSame([['get', 'domain_templates']], $result->callsTo('mailbox'));
$this->assertSame([self::TEMPLATE], $result->json());
}
public function testOneTemplate(): void
{
$result = $this->get('get/domain/template/1', ['script' => [self::answer('mailbox', ['get', 'domain_templates', '1'], self::TEMPLATE)]]);
$this->assertSame(self::TEMPLATE, $result->json());
}
public function testUnknownTemplate(): void
{
$result = $this->get('get/domain/template/9', ['script' => [self::answer('mailbox', ['get', 'domain_templates'], false)]]);
$this->assertCalled($result, 'mailbox', ['get', 'domain_templates', '9']);
$this->assertSame('{}', $result->body);
}
public function testTemplateWithoutIdAsksForAll(): void
{
$result = $this->get('get/domain/template');
$this->assertSame([['get', 'domain_templates', null]], $result->callsTo('mailbox'));
}
public function testAddTemplate(): void
{
$attr = ['template' => 'Small business', 'max_num_mboxes_for_domain' => 25, 'def_quota_for_mbox' => 2048, 'tags' => ['small']];
$result = $this->post('add/domain/template', $attr);
$this->assertSame([['add', 'domain_templates', $attr]], $result->callsTo('mailbox'));
}
public function testEditTemplates(): void
{
$result = $this->post('edit/domain/template', ['items' => [2, 3], 'attr' => ['template' => 'Small business', 'active' => 1]]);
$this->assertCalled($result, 'mailbox', ['edit', 'domain_templates', ['ids' => [2, 3], 'template' => 'Small business', 'active' => 1]]);
}
public function testDeleteTemplates(): void
{
$result = $this->post('delete/domain/template', [2, 3]);
$this->assertCalled($result, 'mailbox', ['delete', 'domain_templates', ['ids' => [2, 3]]]);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Domains;
use Mailcow\Tests\Api\ApiTestCase;
/** get/domain, add/domain, edit/domain, delete/domain, delete/domain/tag, edit/domain/footer */
class DomainTest extends ApiTestCase
{
public function testAllDomainsAreReadOneByOne(): void
{
$result = $this->get('get/domain/all', ['script' => [
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com', 'example.net']),
self::answer('mailbox', ['get', 'domain_details', 'example.org'], ['domain_name' => 'example.org']),
self::answer('mailbox', ['get', 'domain_details', 'example.com'], false),
self::answer('mailbox', ['get', 'domain_details', 'example.net'], ['domain_name' => 'example.net']),
]]);
$this->assertCalled($result, 'mailbox', ['get', 'domains', null, null]);
$this->assertSame([['domain_name' => 'example.org'], ['domain_name' => 'example.net']], $result->json());
}
public function testAllDomainsFilteredByTags(): void
{
$result = $this->get('get/domain/all', ['query' => ['tags' => 'customer,premium'], 'script' => [
self::answer('mailbox', ['get', 'domains'], ['example.org']),
self::answer('mailbox', ['get', 'domain_details', 'example.org'], ['domain_name' => 'example.org', 'tags' => ['customer']]),
]]);
$this->assertCalled($result, 'mailbox', ['get', 'domains', null, ['customer', 'premium']]);
$this->assertSame([['domain_name' => 'example.org', 'tags' => ['customer']]], $result->json());
}
public function testAnEmptyTagsParameterDoesNotFilter(): void
{
$result = $this->get('get/domain/all', ['query' => ['tags' => ''], 'script' => [self::answer('mailbox', ['get', 'domains'], [])]]);
$this->assertCalled($result, 'mailbox', ['get', 'domains', null, null]);
}
public function testAllDomainsWithoutDomains(): void
{
$result = $this->get('get/domain/all', ['script' => [self::answer('mailbox', ['get', 'domains'], [])]]);
$this->assertSame('{}', $result->body);
$this->assertSame([['get', 'domains', null, null]], $result->callsTo('mailbox'));
}
public function testAllDomainsWithoutReadableDetails(): void
{
$result = $this->get('get/domain/all', ['script' => [
self::answer('mailbox', ['get', 'domains'], ['example.org']),
self::answer('mailbox', ['get', 'domain_details'], false),
]]);
$this->assertSame('{}', $result->body);
}
public function testOneDomain(): void
{
$result = $this->get('get/domain/example.org', ['script' => [
self::answer('mailbox', ['get', 'domain_details', 'example.org'], ['domain_name' => 'example.org', 'rl' => false]),
]]);
$this->assertSame(['domain_name' => 'example.org', 'rl' => false], $result->json());
$this->assertSame([['get', 'domain_details', 'example.org']], $result->callsTo('mailbox'));
}
public function testUnknownDomain(): void
{
$result = $this->get('get/domain/example.com', ['script' => [self::answer('mailbox', ['get', 'domain_details'], false)]]);
$this->assertSame('{}', $result->body);
}
public function testAddDomain(): void
{
$attr = ['domain' => 'example.org', 'description' => 'Example Org', 'defquota' => 3072, 'tags' => ['customer'], 'restart_sogo' => '1'];
$result = $this->post('add/domain', $attr);
$this->assertCalled($result, 'mailbox', ['add', 'domain', $attr]);
}
public function testAddDomainIgnoresFurtherSegments(): void
{
$result = $this->post('add/domain/example.org', ['domain' => 'example.org']);
$this->assertCalled($result, 'mailbox', ['add', 'domain', ['domain' => 'example.org']]);
}
public function testEditDomains(): void
{
$result = $this->post('edit/domain', ['items' => ['example.org', 'example.com'], 'attr' => ['quota' => 51200, 'tags' => ['premium']]]);
$this->assertCalled($result, 'mailbox', ['edit', 'domain', ['domain' => ['example.org', 'example.com'], 'quota' => 51200, 'tags' => ['premium']]]);
}
public function testEditDomainWithASingleItem(): void
{
$result = $this->post('edit/domain', ['items' => 'example.org', 'attr' => ['active' => '0']]);
$this->assertCalled($result, 'mailbox', ['edit', 'domain', ['domain' => ['example.org'], 'active' => '0']]);
}
public function testAnAttributeNamedLikeTheItemsWins(): void
{
$result = $this->post('edit/domain', ['items' => ['example.org'], 'attr' => ['domain' => 'example.com']]);
$this->assertCalled($result, 'mailbox', ['edit', 'domain', ['domain' => 'example.com']]);
}
public function testDeleteDomains(): void
{
$result = $this->post('delete/domain', ['example.org', 'example.com']);
$this->assertCalled($result, 'mailbox', ['delete', 'domain', ['domain' => ['example.org', 'example.com']]]);
}
public function testDeleteDomainTags(): void
{
$result = $this->post('delete/domain/tag/example.org', ['customer', 'premium']);
$this->assertCalled($result, 'mailbox', ['delete', 'tags_domain', ['tags' => ['customer', 'premium'], 'domain' => 'example.org']]);
}
public function testDeleteDomainTagsWithoutTags(): void
{
$result = $this->post('delete/domain/tag/example.org', [], ['script' => [self::answer('mailbox', ['delete', 'tags_domain'], false)]]);
$this->assertCalled($result, 'mailbox', ['delete', 'tags_domain', ['tags' => [], 'domain' => 'example.org']]);
$this->assertSame(['type' => 'error', 'msg' => 'Cannot delete item'], $result->json());
}
public function testDeleteDomainTagsWithoutDomain(): void
{
$result = $this->post('delete/domain/tag', ['customer']);
$this->assertCalled($result, 'mailbox', ['delete', 'tags_domain', ['tags' => ['customer'], 'domain' => null]]);
}
public function testEditDomainFooter(): void
{
$attr = ['html' => '<p>{= from_name =}</p>', 'plain' => '{= from_name =}', 'skip_replies' => 1, 'exclude' => ['noreply@example.org', 'example.net']];
$result = $this->post('edit/domain/footer', ['items' => ['example.org'], 'attr' => $attr]);
$this->assertCalled($result, 'mailbox', ['edit', 'domain_wide_footer', ['domains' => ['example.org']] + $attr]);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Domains;
use Mailcow\Tests\Api\ApiTestCase;
/** add/mta-sts, edit/mta-sts (there is no read route) */
class MtaStsTest extends ApiTestCase
{
public function testAddPolicy(): void
{
$attr = ['domain' => 'example.org', 'version' => 'stsv1', 'mode' => 'enforce', 'mx' => 'mail.example.org,mx2.example.org', 'max_age' => 86400, 'active' => 1];
$result = $this->post('add/mta-sts', $attr);
$this->assertSame([['add', 'mta_sts', $attr]], $result->callsTo('mailbox'));
// the backend reports no success message of its own
$this->assertSame(['type' => 'success', 'msg' => 'Task completed'], $result->json());
}
public function testEditPolicies(): void
{
$result = $this->post('edit/mta-sts', ['items' => ['example.org'], 'attr' => ['version' => 'stsv1', 'mode' => 'testing']]);
$this->assertSame([['edit', 'mta_sts', ['domains' => ['example.org'], 'version' => 'stsv1', 'mode' => 'testing']]], $result->callsTo('mailbox'));
}
public function testThereIsNoReadRoute(): void
{
$this->assertApiError($this->get('get/mta-sts/example.org'), 404, 'route not found');
}
}
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff. Show more