Merge pull request #6391 from mailcow/staging

Update 2025-03
This commit is contained in:
FreddleSpl0it 2025-03-25 08:10:50 +01:00 committed by GitHub
commit c3c68360dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
804 changed files with 49082 additions and 9617 deletions

View File

@ -30,7 +30,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64

2
.gitignore vendored
View File

@ -45,6 +45,7 @@ data/conf/rspamd/local.d/*
data/conf/rspamd/override.d/* data/conf/rspamd/override.d/*
data/conf/sogo/custom-theme.js data/conf/sogo/custom-theme.js
data/conf/sogo/plist_ldap data/conf/sogo/plist_ldap
data/conf/sogo/plist_ldap.sh
data/conf/sogo/sieve.creds data/conf/sogo/sieve.creds
data/conf/sogo/cron.creds data/conf/sogo/cron.creds
data/conf/sogo/custom-fulllogo.svg data/conf/sogo/custom-fulllogo.svg
@ -73,3 +74,4 @@ rebuild-images.sh
refresh_images.sh refresh_images.sh
update_diffs/ update_diffs/
create_cold_standby.sh create_cold_standby.sh
!data/conf/nginx/mailcow_auth.conf

View File

@ -1,8 +1,7 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk upgrade --no-cache \ RUN apk upgrade --no-cache \
&& apk add --update --no-cache \ && apk add --update --no-cache \
bash \ bash \
@ -15,7 +14,7 @@ RUN apk upgrade --no-cache \
tini \ tini \
tzdata \ tzdata \
python3 \ python3 \
acme-tiny --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ acme-tiny
COPY acme.sh /srv/acme.sh COPY acme.sh /srv/acme.sh
COPY functions.sh /srv/functions.sh COPY functions.sh /srv/functions.sh

View File

@ -138,7 +138,7 @@ log_f "Resolver OK"
log_f "Waiting for domain table..." log_f "Waiting for domain table..."
while [[ -z ${DOMAIN_TABLE} ]]; do while [[ -z ${DOMAIN_TABLE} ]]; do
curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1 curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1
DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs) DOMAIN_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
[[ -z ${DOMAIN_TABLE} ]] && sleep 10 [[ -z ${DOMAIN_TABLE} ]] && sleep 10
done done
log_f "OK" no_date log_f "OK" no_date
@ -231,7 +231,7 @@ while true; do
######################################### #########################################
# IP and webroot challenge verification # # IP and webroot challenge verification #
SQL_DOMAINS=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs) SQL_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs)
if [[ ! $? -eq 0 ]]; then if [[ ! $? -eq 0 ]]; then
log_f "Failed to read SQL domains, retrying in 1 minute..." log_f "Failed to read SQL domains, retrying in 1 minute..."
sleep 1m sleep 1m

View File

@ -11,4 +11,4 @@ if [ "${CLAMAV_NO_CLAMD:-}" != "false" ]; then
echo "Clamd is up" echo "Clamd is up"
fi fi
exit 0 exit 0

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
@ -34,9 +34,13 @@ RUN addgroup -g 5000 vmail \
lua5.3-sql-mysql \ lua5.3-sql-mysql \
icu-data-full \ icu-data-full \
mariadb-connector-c \ mariadb-connector-c \
lua-sec \
mariadb-dev \
glib-dev \
gcompat \ gcompat \
mariadb-client \ mariadb-client \
perl \ perl \
perl-dev \
perl-ntlm \ perl-ntlm \
perl-cgi \ perl-cgi \
perl-crypt-openssl-rsa \ perl-crypt-openssl-rsa \
@ -65,7 +69,7 @@ RUN addgroup -g 5000 vmail \
perl-par-packer \ perl-par-packer \
perl-parse-recdescent \ perl-parse-recdescent \
perl-lockfile-simple \ perl-lockfile-simple \
libproc \ libproc2 \
perl-readonly \ perl-readonly \
perl-regexp-common \ perl-regexp-common \
perl-sys-meminfo \ perl-sys-meminfo \

View File

@ -15,6 +15,6 @@ if ! [[ ${MAX_AGE} =~ ${NUM_REGEXP} ]] ; then
exit 1 exit 1
fi fi
TO_DELETE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN) TO_DELETE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN)
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY"
echo "Deleted ${TO_DELETE} items from quarantine table (max age is ${MAX_AGE//[!0-9]/} days)" echo "Deleted ${TO_DELETE} items from quarantine table (max age is ${MAX_AGE//[!0-9]/} days)"

View File

@ -28,7 +28,7 @@ ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
# Create missing directories # Create missing directories
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/ [[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
[[ ! -d /etc/dovecot/lua/ ]] && mkdir -p /etc/dovecot/lua/ [[ ! -d /etc/dovecot/auth/ ]] && mkdir -p /etc/dovecot/auth/
[[ ! -d /etc/dovecot/conf.d/ ]] && mkdir -p /etc/dovecot/conf.d/ [[ ! -d /etc/dovecot/conf.d/ ]] && mkdir -p /etc/dovecot/conf.d/
[[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage [[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve [[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
@ -131,123 +131,6 @@ user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format
iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2'; iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2';
EOF EOF
cat <<EOF > /etc/dovecot/lua/passwd-verify.lua
function auth_password_verify(req, pass)
if req.domain == nil then
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
end
if cur == nil then
script_init()
end
if req.user == nil then
req.user = ''
end
respbody = {}
-- check against mailbox passwds
local cur,errorString = con:execute(string.format([[SELECT password FROM mailbox
WHERE username = '%s'
AND active = '1'
AND domain IN (SELECT domain FROM domain WHERE domain='%s' AND active='1')
AND IFNULL(JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.force_pw_update')), 0) != '1'
AND IFNULL(JSON_UNQUOTE(JSON_VALUE(attributes, '$.%s_access')), 1) = '1']], con:escape(req.user), con:escape(req.domain), con:escape(req.service)))
local row = cur:fetch ({}, "a")
while row do
if req.password_verify(req, row.password, pass) == 1 then
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
VALUES ("%s", 0, "%s", "%s")]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip)))
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
end
row = cur:fetch (row, "a")
end
-- check against app passwds for imap and smtp
-- app passwords are only available for imap, smtp, sieve and pop3 when using sasl
if req.service == "smtp" or req.service == "imap" or req.service == "sieve" or req.service == "pop3" then
local cur,errorString = con:execute(string.format([[SELECT app_passwd.id, %s_access AS has_prot_access, app_passwd.password FROM app_passwd
INNER JOIN mailbox ON mailbox.username = app_passwd.mailbox
WHERE mailbox = '%s'
AND app_passwd.active = '1'
AND mailbox.active = '1'
AND app_passwd.domain IN (SELECT domain FROM domain WHERE domain='%s' AND active='1')]], con:escape(req.service), con:escape(req.user), con:escape(req.domain)))
local row = cur:fetch ({}, "a")
while row do
if req.password_verify(req, row.password, pass) == 1 then
-- if password is valid and protocol access is 1 OR real_rip matches SOGo, proceed
if tostring(req.real_rip) == "__IPV4_SOGO__" then
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
elseif row.has_prot_access == "1" then
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
VALUES ("%s", %d, "%s", "%s")]], con:escape(req.service), row.id, con:escape(req.user), con:escape(req.real_rip)))
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
end
end
row = cur:fetch (row, "a")
end
end
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
-- PoC
-- local reqbody = string.format([[{
-- "success":0,
-- "service":"%s",
-- "app_password":false,
-- "username":"%s",
-- "real_rip":"%s"
-- }]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip))
-- http.request {
-- method = "POST",
-- url = "http://nginx:8081/sasl_log.php",
-- source = ltn12.source.string(reqbody),
-- headers = {
-- ["content-type"] = "application/json",
-- ["content-length"] = tostring(#reqbody)
-- },
-- sink = ltn12.sink.table(respbody)
-- }
end
function auth_passdb_lookup(req)
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
end
function script_init()
mysql = require "luasql.mysql"
http = require "socket.http"
http.TIMEOUT = 5
ltn12 = require "ltn12"
env = mysql.mysql()
con = env:connect("__DBNAME__","__DBUSER__","__DBPASS__","localhost")
return 0
end
function script_deinit()
con:close()
env:close()
end
EOF
# Replace patterns in app-passdb.lua
sed -i "s/__DBUSER__/${DBUSER}/g" /etc/dovecot/lua/passwd-verify.lua
sed -i "s/__DBPASS__/${DBPASS}/g" /etc/dovecot/lua/passwd-verify.lua
sed -i "s/__DBNAME__/${DBNAME}/g" /etc/dovecot/lua/passwd-verify.lua
sed -i "s/__IPV4_SOGO__/${IPV4_NETWORK}.248/g" /etc/dovecot/lua/passwd-verify.lua
# Migrate old sieve_after file # Migrate old sieve_after file
[[ -f /etc/dovecot/sieve_after ]] && mv /etc/dovecot/sieve_after /etc/dovecot/global_sieve_after [[ -f /etc/dovecot/sieve_after ]] && mv /etc/dovecot/sieve_after /etc/dovecot/global_sieve_after
@ -385,8 +268,8 @@ sievec /usr/lib/dovecot/sieve/report-ham.sieve
# Fix permissions # Fix permissions
chown root:root /etc/dovecot/sql/*.conf chown root:root /etc/dovecot/sql/*.conf
chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/lua/passwd-verify.lua chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/auth/passwd-verify.lua
chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/lua/passwd-verify.lua chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/auth/passwd-verify.lua
chown -R vmail:vmail /var/vmail/sieve chown -R vmail:vmail /var/vmail/sieve
chown -R vmail:vmail /var/volatile chown -R vmail:vmail /var/volatile
chown -R vmail:vmail /var/vmail_index chown -R vmail:vmail /var/vmail_index
@ -414,15 +297,15 @@ printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh
# Clean stopped imapsync jobs # Clean stopped imapsync jobs
rm -f /tmp/imapsync_busy.lock rm -f /tmp/imapsync_busy.lock
IMAPSYNC_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs) IMAPSYNC_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'" [[ ! -z ${IMAPSYNC_TABLE} ]] && mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
# Envsubst maildir_gc # Envsubst maildir_gc
echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh
# GUID generation # GUID generation
while [[ ${VERSIONS_OK} != 'OK' ]]; do while [[ ${VERSIONS_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then
VERSIONS_OK=OK VERSIONS_OK=OK
else else
echo "Waiting for versions table to be created..." echo "Waiting for versions table to be created..."
@ -433,11 +316,11 @@ PUBKEY_MCRYPT=$(doveconf -P 2> /dev/null | grep -i mail_crypt_global_public_key
if [ -f ${PUBKEY_MCRYPT} ]; then if [ -f ${PUBKEY_MCRYPT} ]; then
GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ") GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ")
if [ ${#GUID} -eq 64 ]; then if [ ${#GUID} -eq 64 ]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}"); REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}");
EOF EOF
else else
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID"); REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID");
EOF EOF
fi fi
@ -456,7 +339,7 @@ done
# For some strange, unknown and stupid reason, Dovecot may run into a race condition, when this file is not touched before it is read by dovecot/auth # For some strange, unknown and stupid reason, Dovecot may run into a race condition, when this file is not touched before it is read by dovecot/auth
# May be related to something inside Docker, I seriously don't know # May be related to something inside Docker, I seriously don't know
touch /etc/dovecot/lua/passwd-verify.lua touch /etc/dovecot/auth/passwd-verify.lua
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf

View File

@ -23,3 +23,4 @@ catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM CRON_LOG 0 ${LOG_LINES}"

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM php:8.2-fpm-alpine3.20 FROM php:8.2-fpm-alpine3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
@ -13,7 +13,7 @@ ARG MEMCACHED_PECL_VERSION=3.2.0
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
ARG REDIS_PECL_VERSION=6.1.0 ARG REDIS_PECL_VERSION=6.1.0
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
ARG COMPOSER_VERSION=2.6.6 ARG COMPOSER_VERSION=2.8.6
RUN apk add -U --no-cache autoconf \ RUN apk add -U --no-cache autoconf \
aspell-dev \ aspell-dev \

View File

@ -81,7 +81,7 @@ if [ ${SQL_CHANGED} -eq 1 ]; then
fi fi
# Check mysql tz import (master and slave) # Check mysql tz import (master and slave)
TZ_CHECK=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null) TZ_CHECK=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null)
if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then
SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json') SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json')
echo "MySQL mysql_tzinfo_to_sql - debug output:" echo "MySQL mysql_tzinfo_to_sql - debug output:"
@ -120,11 +120,11 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
while read line while read line
do do
DOMAIN_ARR+=("$line") DOMAIN_ARR+=("$line")
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs) done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
while read line while read line
do do
DOMAIN_ARR+=("$line") DOMAIN_ARR+=("$line")
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
if [[ ! -z ${DOMAIN_ARR} ]]; then if [[ ! -z ${DOMAIN_ARR} ]]; then
for domain in "${DOMAIN_ARR[@]}"; do for domain in "${DOMAIN_ARR[@]}"; do
@ -146,13 +146,13 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]}) VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]})
if [[ ! -z ${VALIDATED_IPS} ]]; then if [[ ! -z ${VALIDATED_IPS} ]]; then
if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELETE FROM api WHERE access = 'rw'; DELETE FROM api WHERE access = 'rw';
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw"); INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw");
EOF EOF
fi fi
if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELETE FROM api WHERE access = 'ro'; DELETE FROM api WHERE access = 'ro';
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro"); INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro");
EOF EOF
@ -161,7 +161,7 @@ EOF
fi fi
# Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED) # Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED)
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DROP EVENT IF EXISTS clean_spamalias; DROP EVENT IF EXISTS clean_spamalias;
DELIMITER // DELIMITER //
CREATE EVENT clean_spamalias CREATE EVENT clean_spamalias

View File

@ -2,11 +2,11 @@ FROM debian:bookworm-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG RSPAMD_VER=rspamd_3.11.0-2~90a175b45 ARG RSPAMD_VER=rspamd_3.11.1-1~ab0b44951
ARG CODENAME=bookworm ARG CODENAME=bookworm
ENV LC_ALL=C ENV LC_ALL=C
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y --no-install-recommends \
tzdata \ tzdata \
ca-certificates \ ca-certificates \
gnupg2 \ gnupg2 \

View File

@ -47,6 +47,7 @@ COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
COPY supervisord.conf /etc/supervisor/supervisord.conf COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY acl.diff /acl.diff COPY acl.diff /acl.diff
COPY navMailcowBtns.diff /navMailcowBtns.diff
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
COPY docker-entrypoint.sh / COPY docker-entrypoint.sh /

View File

@ -14,120 +14,16 @@ do
done done
# Wait for updated schema # Wait for updated schema
DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN) DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
echo "Waiting for schema update..." echo "Waiting for schema update..."
DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN) DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
sleep 5 sleep 5
done done
echo "DB schema is ${DBV_NOW}" echo "DB schema is ${DBV_NOW}"
# Recreate view
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing sogo_view..."
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
while [[ ${VIEW_OK} != 'OK' ]]; do
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) AS
SELECT
mailbox.username,
mailbox.domain,
mailbox.username,
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.force_pw_update')) = '0', IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.sogo_access')) = 1, password, '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'), '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
mailbox.name,
mailbox.username,
IFNULL(GROUP_CONCAT(ga.aliases ORDER BY ga.aliases SEPARATOR ' '), ''),
IFNULL(gda.ad_alias, ''),
IFNULL(external_acl.send_as_acl, ''),
mailbox.kind,
mailbox.multiple_bookings
FROM
mailbox
LEFT OUTER JOIN
grouped_mail_aliases ga
ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
LEFT OUTER JOIN
grouped_domain_alias_address gda
ON gda.username = mailbox.username
LEFT OUTER JOIN
grouped_sender_acl_external external_acl
ON external_acl.username = mailbox.username
WHERE
mailbox.active = '1'
GROUP BY
mailbox.username;
EOF
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Will retry to setup SOGo view in 3s..."
sleep 3
fi
done
else
while [[ ${VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Waiting for SOGo view to be created by master..."
sleep 3
fi
done
fi
# Wait for static view table if missing after update and update content
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing _sogo_static_view..."
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
echo "Updating _sogo_static_view content..."
# If changed, also update init_db.inc.php
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) SELECT c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings from sogo_view;"
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')"
else
echo "Waiting for database initialization..."
sleep 3
fi
done
else
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
else
echo "Waiting for database initialization by master..."
sleep 3
fi
done
fi
# Recreate password update trigger
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing update trigger..."
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
while [[ ${TRIGGER_OK} != 'OK' ]]; do
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELIMITER -
CREATE TRIGGER sogo_update_password AFTER UPDATE ON _sogo_static_view
FOR EACH ROW
BEGIN
UPDATE mailbox SET password = NEW.c_password WHERE NEW.c_uid = username;
END;
-
DELIMITER ;
EOF
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then
TRIGGER_OK=OK
else
echo "Will retry to setup SOGo password update trigger in 3s"
sleep 3
fi
done
fi
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl # cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9) RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
@ -213,10 +109,10 @@ while read -r line gal
</dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist </dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
# Generate alternative LDAP authentication dict, when SQL authentication fails # Generate alternative LDAP authentication dict, when SQL authentication fails
# This will nevertheless read attributes from LDAP # This will nevertheless read attributes from LDAP
line=${line} envsubst < /etc/sogo/plist_ldap >> /var/lib/sogo/GNUstep/Defaults/sogod.plist /etc/sogo/plist_ldap.sh ${line} ${gal} >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
echo " </array> echo " </array>
</dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist </dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N) done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N)
# Generate footer # Generate footer
echo ' </dict> echo ' </dict>
@ -240,6 +136,10 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
# fi # fi
#fi #fi
if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then
patch -R /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff;
fi
# Rename custom logo, if any # Rename custom logo, if any
[[ -f /etc/sogo/sogo-full.svg ]] && mv /etc/sogo/sogo-full.svg /etc/sogo/custom-fulllogo.svg [[ -f /etc/sogo/sogo-full.svg ]] && mv /etc/sogo/sogo-full.svg /etc/sogo/custom-fulllogo.svg

View File

@ -0,0 +1,20 @@
59,65d58
< ng-show="::!activeUser.isSuperUser"
< var:ng-click="navButtonClick"
< ng-href="/user">
< <md-icon>build</md-icon>
< <md-tooltip><var:string label:value="mailcow"/></md-tooltip>
< </md-button>
< <md-button class="md-icon-button"
83c76
< onclick="document.getElementById('mc_logout').setAttribute('action', '/'); document.getElementById('mc_logout').submit();"
---
> ng-show="::activeUser.path.logoff.length"
85c78
< ng-href="#">
---
> ng-href="{{::activeUser.path.logoff}}">
89,91d81
< <form method="POST" id="mc_logout" action="user">
< <input type="hidden" name="logout" value="1">
< </form>

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -132,9 +132,9 @@ fi
# Connect to the DB server and store output in vars # Connect to the DB server and store output in vars
if [[ -n $socket ]]; then if [[ -n $socket ]]; then
ConnectionResult=$(mysql ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1) ConnectionResult=$(mariadb --skip-ssl ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1)
else else
ConnectionResult=$(mysql ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1) ConnectionResult=$(mariadb --skip-ssl ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1)
fi fi
if [ -z "`echo "${ConnectionResult}" |grep Slave_IO_State`" ]; then if [ -z "`echo "${ConnectionResult}" |grep Slave_IO_State`" ]; then

View File

@ -234,7 +234,7 @@ external_checks() {
diff_c=0 diff_c=0
THRESHOLD=${EXTERNAL_CHECKS_THRESHOLD} THRESHOLD=${EXTERNAL_CHECKS_THRESHOLD}
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
GUID=$(mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN) GUID=$(mariadb --skip-ssl -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN)
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
err_c_cur=${err_count} err_c_cur=${err_count}

View File

@ -0,0 +1,108 @@
<?php
ini_set('error_reporting', 0);
header('Content-Type: application/json');
$post = trim(file_get_contents('php://input'));
if ($post) {
$post = json_decode($post, true);
}
$return = array("success" => false);
if(!isset($post['username']) || !isset($post['password']) || !isset($post['real_rip'])){
error_log("MAILCOWAUTH: Bad Request");
http_response_code(400); // Bad Request
echo json_encode($return);
exit();
}
require_once('../../../web/inc/vars.inc.php');
if (file_exists('../../../web/inc/vars.local.inc.php')) {
include_once('../../../web/inc/vars.local.inc.php');
}
require_once '../../../web/inc/lib/vendor/autoload.php';
// Init Redis
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
}
else {
$redis->connect('redis-mailcow', 6379);
}
$redis->auth(getenv("REDISPASS"));
}
catch (Exception $e) {
error_log("MAILCOWAUTH: " . $e . PHP_EOL);
http_response_code(500); // Internal Server Error
echo json_encode($return);
exit;
}
// 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("MAILCOWAUTH: " . $e . PHP_EOL);
http_response_code(500); // Internal Server Error
echo json_encode($return);
exit;
}
// Load core functions first
require_once 'functions.inc.php';
require_once 'functions.auth.inc.php';
require_once 'sessions.inc.php';
require_once 'functions.mailbox.inc.php';
require_once 'functions.ratelimit.inc.php';
require_once 'functions.acl.inc.php';
$isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248';
$result = false;
$protocol = $post['protocol'];
if ($isSOGoRequest) {
$protocol = null;
// This is a SOGo Auth request. First check for SSO password.
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
if ($sogo_sso_pass === $post['password']){
error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']);
$result = true;
}
}
if ($result === false){
$result = apppass_login($post['username'], $post['password'], $protocol, array(
'is_internal' => true,
'remote_addr' => $post['real_rip']
));
if ($result) error_log('MAILCOWAUTH: App auth for user ' . $post['username']);
}
if ($result === false){
// Init Identity Provider
$iam_provider = identity_provider('init');
$iam_settings = identity_provider('get');
$result = user_login($post['username'], $post['password'], array('is_internal' => true));
if ($result) error_log('MAILCOWAUTH: User auth for user ' . $post['username']);
}
if ($result) {
http_response_code(200); // OK
$return['success'] = true;
} else {
error_log("MAILCOWAUTH: Login failed for user " . $post['username']);
http_response_code(401); // Unauthorized
}
echo json_encode($return);
session_destroy();
exit;

View File

@ -0,0 +1,42 @@
function auth_password_verify(request, password)
if request.domain == nil then
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
end
json = require "cjson"
ltn12 = require "ltn12"
https = require "ssl.https"
https.TIMEOUT = 5
local req = {
username = request.user,
password = password,
real_rip = request.real_rip,
protocol = {}
}
req.protocol[request.service] = true
local req_json = json.encode(req)
local res = {}
local b, c = https.request {
method = "POST",
url = "https://nginx:9082",
source = ltn12.source.string(req_json),
headers = {
["content-type"] = "application/json",
["content-length"] = tostring(#req_json)
},
sink = ltn12.sink.table(res),
insecure = true
}
local api_response = json.decode(table.concat(res))
if api_response.success == true then
return dovecot.auth.PASSDB_RESULT_OK, ""
end
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
end
function auth_passdb_lookup(req)
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
end

View File

@ -53,7 +53,7 @@ mail_shared_explicit_inbox = yes
mail_prefetch_count = 30 mail_prefetch_count = 30
passdb { passdb {
driver = lua driver = lua
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%u:%w
result_success = return-ok result_success = return-ok
result_failure = continue result_failure = continue
result_internalfail = continue result_internalfail = continue
@ -69,7 +69,7 @@ passdb {
# a return of the following passdb is mandatory # a return of the following passdb is mandatory
passdb { passdb {
driver = lua driver = lua
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes
} }
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing) # Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
service doveadm { service doveadm {
@ -125,6 +125,7 @@ service managesieve-login {
} }
service imap-login { service imap-login {
service_count = 1 service_count = 1
process_min_avail = 2
process_limit = 10000 process_limit = 10000
vsz_limit = 1G vsz_limit = 1G
user = dovenull user = dovenull
@ -140,6 +141,7 @@ service imap-login {
} }
service pop3-login { service pop3-login {
service_count = 1 service_count = 1
process_min_avail = 1
vsz_limit = 1G vsz_limit = 1G
inet_listener pop3_haproxy { inet_listener pop3_haproxy {
port = 10110 port = 10110
@ -239,7 +241,7 @@ plugin {
mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
mail_crypt_save_version = 2 mail_crypt_save_version = 2
# Enable compression while saving, lz4 Dovecot v2.2.11+ # Enable compression while saving, lz4 Dovecot v2.3.17+
zlib_save = lz4 zlib_save = lz4
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
@ -274,10 +276,10 @@ service stats {
} }
} }
imap_max_line_length = 2 M imap_max_line_length = 2 M
#auth_cache_verify_password_with_worker = yes auth_cache_verify_password_with_worker = yes
#auth_cache_negative_ttl = 0 auth_cache_negative_ttl = 60s
#auth_cache_ttl = 30 s auth_cache_ttl = 300s
#auth_cache_size = 2 M auth_cache_size = 10M
auth_verbose_passwords = sha1:6 auth_verbose_passwords = sha1:6
service replicator { service replicator {
process_min_avail = 1 process_min_avail = 1
@ -302,7 +304,6 @@ replication_dsync_parameters = -d -l 30 -U -n INBOX
!include_try /etc/dovecot/sni.conf !include_try /etc/dovecot/sni.conf
!include_try /etc/dovecot/sogo_trusted_ip.conf !include_try /etc/dovecot/sogo_trusted_ip.conf
!include_try /etc/dovecot/extra.conf !include_try /etc/dovecot/extra.conf
!include_try /etc/dovecot/sogo-sso.conf
!include_try /etc/dovecot/shared_namespace.conf !include_try /etc/dovecot/shared_namespace.conf
!include_try /etc/dovecot/conf.d/fts.conf !include_try /etc/dovecot/conf.d/fts.conf
# </Includes> # </Includes>

View File

@ -159,6 +159,29 @@ http {
} }
} }
server {
listen 9082 ssl http2;
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
index mailcowauth.php;
server_name _;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /mailcowauth;
client_max_body_size 10M;
location ~ \.php$ {
client_max_body_size 10M;
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9001;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
{% for cert in valid_cert_dirs %} {% for cert in valid_cert_dirs %}
server { server {
{% if not HTTP_REDIRECT %} {% if not HTTP_REDIRECT %}

View File

@ -0,0 +1,231 @@
<?php
require_once(__DIR__ . '/../web/inc/vars.inc.php');
if (file_exists(__DIR__ . '/../web/inc/vars.local.inc.php')) {
include_once(__DIR__ . '/../web/inc/vars.local.inc.php');
}
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
// Init database
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
$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) {
logMsg("err", $e->getMessage());
session_destroy();
exit;
}
// Init Redis
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
}
else {
$redis->connect('redis-mailcow', 6379);
}
$redis->auth(getenv("REDISPASS"));
}
catch (Exception $e) {
echo "Exiting: " . $e->getMessage();
session_destroy();
exit;
}
function logMsg($priority, $message, $task = "Keycloak Sync") {
global $redis;
$finalMsg = array(
"time" => time(),
"priority" => $priority,
"task" => $task,
"message" => $message
);
$redis->lPush('CRON_LOG', json_encode($finalMsg));
}
// Load core functions first
require_once __DIR__ . '/../web/inc/functions.inc.php';
require_once __DIR__ . '/../web/inc/functions.auth.inc.php';
require_once __DIR__ . '/../web/inc/sessions.inc.php';
require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php';
require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php';
require_once __DIR__ . '/../web/inc/functions.acl.inc.php';
$_SESSION['mailcow_cc_username'] = "admin";
$_SESSION['mailcow_cc_role'] = "admin";
$_SESSION['acl']['tls_policy'] = "1";
$_SESSION['acl']['quarantine_notification'] = "1";
$_SESSION['acl']['quarantine_category'] = "1";
$_SESSION['acl']['ratelimit'] = "1";
$_SESSION['acl']['sogo_access'] = "1";
$_SESSION['acl']['protocol_access'] = "1";
$_SESSION['acl']['mailbox_relayhost'] = "1";
$_SESSION['acl']['unlimited_quota'] = "1";
$iam_settings = identity_provider('get');
if ($iam_settings['authsource'] != "keycloak" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) {
session_destroy();
exit;
}
// Set pagination variables
$start = 0;
$max = 100;
// lock sync if already running
$lock_file = '/tmp/iam-sync.lock';
if (file_exists($lock_file)) {
$lock_file_parts = explode("\n", file_get_contents($lock_file));
$pid = $lock_file_parts[0];
if (count($lock_file_parts) > 1){
$last_execution = $lock_file_parts[1];
$elapsed_time = (time() - $last_execution) / 60;
if ($elapsed_time < intval($iam_settings['sync_interval'])) {
logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)");
session_destroy();
exit;
}
}
if (posix_kill($pid, 0)) {
logMsg("warning", "Sync is already running");
session_destroy();
exit;
} else {
unlink($lock_file);
}
}
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid());
fclose($lock_file_handle);
// Init Keycloak Provider
$iam_provider = identity_provider('init');
// Loop until all users have been retrieved
while (true) {
// Get admin access token
$admin_token = identity_provider("get-keycloak-admin-token");
// Make the API request to retrieve the users
$url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users?first=$start&max=$max";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer " . $admin_token
]);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code != 200){
logMsg("err", "Recieved HTTP {$code}");
session_destroy();
exit;
}
try {
$response = json_decode($response, true);
} catch (Exception $e) {
logMsg("err", $e->getMessage());
break;
}
if (!is_array($response)){
logMsg("err", "Recieved malformed response from keycloak api");
break;
}
if (count($response) == 0) {
break;
}
// Process the batch of users
foreach ($response as $user) {
if (empty($user['email'])){
logMsg("warning", "No email address in keycloak found for user " . $user['name']);
continue;
}
// try get mailbox user
$stmt = $pdo->prepare("SELECT
mailbox.*,
domain.active AS d_active
FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $user['email']));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// check if matching attribute mapping exists
$user_template = $user['attributes']['mailcow_template'][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
$_SESSION['access_all_exception'] = '1';
if (!$row && intval($iam_settings['import_users']) == 1){
if ($mapper_key === false){
if (!empty($iam_settings['default_template'])) {
$mbox_template = $iam_settings['default_template'];
logMsg("warning", "Using default template for user " . $user['email']);
} else {
logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
continue;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// mailbox user does not exist, create...
logMsg("info", "Creating user " . $user['email']);
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user['email'])[1],
'local_part' => explode('@', $user['email'])[0],
'name' => $user['firstName'] . " " . $user['lastName'],
'authsource' => 'keycloak',
'template' => $mbox_template
));
if (!$create_res){
logMsg("err", "Could not create user " . $user['email']);
continue;
}
} else if ($row && intval($iam_settings['periodic_sync']) == 1) {
if ($mapper_key === false){
logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
continue;
}
$mbox_template = $iam_settings['templates'][$mapper_key];
// mailbox user does exist, sync attribtues...
logMsg("info", "Syncing attributes for user " . $user['email']);
mailbox('edit', 'mailbox_from_template', array(
'username' => $user['email'],
'name' => $user['firstName'] . " " . $user['lastName'],
'template' => $mbox_template
));
} else {
// skip mailbox user
logMsg("info", "Skipping user " . $user['email']);
}
$_SESSION['access_all_exception'] = '0';
sleep(0.025);
}
// Update the pagination variables for the next batch
$start += $max;
sleep(1);
}
logMsg("info", "DONE!");
// add last execution time to lock file
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid() . "\n" . time());
fclose($lock_file_handle);
session_destroy();

View File

@ -0,0 +1,198 @@
<?php
require_once(__DIR__ . '/../web/inc/vars.inc.php');
if (file_exists(__DIR__ . '/../web/inc/vars.local.inc.php')) {
include_once(__DIR__ . '/../web/inc/vars.local.inc.php');
}
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
// Init database
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
$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) {
logMsg("err", $e->getMessage());
session_destroy();
exit;
}
// Init Redis
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
}
else {
$redis->connect('redis-mailcow', 6379);
}
$redis->auth(getenv("REDISPASS"));
}
catch (Exception $e) {
echo "Exiting: " . $e->getMessage();
session_destroy();
exit;
}
function logMsg($priority, $message, $task = "LDAP Sync") {
global $redis;
$finalMsg = array(
"time" => time(),
"priority" => $priority,
"task" => $task,
"message" => $message
);
$redis->lPush('CRON_LOG', json_encode($finalMsg));
}
// Load core functions first
require_once __DIR__ . '/../web/inc/functions.inc.php';
require_once __DIR__ . '/../web/inc/functions.auth.inc.php';
require_once __DIR__ . '/../web/inc/sessions.inc.php';
require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php';
require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php';
require_once __DIR__ . '/../web/inc/functions.acl.inc.php';
$_SESSION['mailcow_cc_username'] = "admin";
$_SESSION['mailcow_cc_role'] = "admin";
$_SESSION['acl']['tls_policy'] = "1";
$_SESSION['acl']['quarantine_notification'] = "1";
$_SESSION['acl']['quarantine_category'] = "1";
$_SESSION['acl']['ratelimit'] = "1";
$_SESSION['acl']['sogo_access'] = "1";
$_SESSION['acl']['protocol_access'] = "1";
$_SESSION['acl']['mailbox_relayhost'] = "1";
$_SESSION['acl']['unlimited_quota'] = "1";
$iam_settings = identity_provider('get');
if ($iam_settings['authsource'] != "ldap" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) {
session_destroy();
exit;
}
// Set pagination variables
$start = 0;
$max = 100;
// lock sync if already running
$lock_file = '/tmp/iam-sync.lock';
if (file_exists($lock_file)) {
$lock_file_parts = explode("\n", file_get_contents($lock_file));
$pid = $lock_file_parts[0];
if (count($lock_file_parts) > 1){
$last_execution = $lock_file_parts[1];
$elapsed_time = (time() - $last_execution) / 60;
if ($elapsed_time < intval($iam_settings['sync_interval'])) {
logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)");
session_destroy();
exit;
}
}
if (posix_kill($pid, 0)) {
logMsg("warning", "Sync is already running");
session_destroy();
exit;
} else {
unlink($lock_file);
}
}
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid());
fclose($lock_file_handle);
// Init Provider
$iam_provider = identity_provider('init');
// Get ldap users
$ldap_query = $iam_provider->query();
if (!empty($iam_settings['filter'])) {
$ldap_query = $ldap_query->rawFilter($iam_settings['filter']);
}
$response = $ldap_query->where($iam_settings['username_field'], "*")
->where($iam_settings['attribute_field'], "*")
->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname'])
->paginate($max);
// Process the users
foreach ($response as $user) {
// try get mailbox user
$stmt = $pdo->prepare("SELECT
mailbox.*,
domain.active AS d_active
FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $user[$iam_settings['username_field']][0]));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// check if matching attribute mapping exists
$user_template = $user[$iam_settings['attribute_field']][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
if (empty($user[$iam_settings['username_field']][0])){
logMsg("warning", "Skipping user " . $user['displayname'][0] . " due to empty LDAP ". $iam_settings['username_field'] . " property.");
continue;
}
$_SESSION['access_all_exception'] = '1';
if (!$row && intval($iam_settings['import_users']) == 1){
if ($mapper_key === false){
if (!empty($iam_settings['default_template'])) {
$mbox_template = $iam_settings['default_template'];
} else {
logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
continue;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// mailbox user does not exist, create...
logMsg("info", "Creating user " . $user[$iam_settings['username_field']][0]);
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user[$iam_settings['username_field']][0])[1],
'local_part' => explode('@', $user[$iam_settings['username_field']][0])[0],
'name' => $user['displayname'][0],
'authsource' => 'ldap',
'template' => $mbox_template
));
if (!$create_res){
logMsg("err", "Could not create user " . $user[$iam_settings['username_field']][0]);
continue;
}
} else if ($row && intval($iam_settings['periodic_sync']) == 1) {
if ($mapper_key === false){
logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
continue;
}
$mbox_template = $iam_settings['templates'][$mapper_key];
// mailbox user does exist, sync attribtues...
logMsg("info", "Syncing attributes for user " . $user[$iam_settings['username_field']][0]);
mailbox('edit', 'mailbox_from_template', array(
'username' => $user[$iam_settings['username_field']][0],
'name' => $user['displayname'][0],
'template' => $mbox_template
));
} else {
// skip mailbox user
logMsg("info", "Skipping user " . $user[$iam_settings['username_field']][0]);
}
$_SESSION['access_all_exception'] = '0';
sleep(0.025);
}
logMsg("info", "DONE!");
// add last execution time to lock file
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid() . "\n" . time());
fclose($lock_file_handle);
session_destroy();

View File

@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Sat Feb 1 00:18:03 UTC 2025 # Whitelist generated by Postwhite v3.4 on Sat Mar 1 00:19:29 UTC 2025
# https://github.com/stevejenkins/postwhite/ # https://github.com/stevejenkins/postwhite/
# 1984 total rules # 2000 total rules
2a00:1450:4000::/36 permit 2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit 2a01:111:f400::/48 permit
2a01:111:f403:8000::/50 permit 2a01:111:f403:8000::/50 permit
@ -8,6 +8,13 @@
2a01:111:f403::/49 permit 2a01:111:f403::/49 permit
2a01:111:f403:c000::/51 permit 2a01:111:f403:c000::/51 permit
2a01:111:f403:f000::/52 permit 2a01:111:f403:f000::/52 permit
2a01:b747:3000:200::/56 permit
2a01:b747:3001:200::/56 permit
2a01:b747:3002:200::/56 permit
2a01:b747:3003:200::/56 permit
2a01:b747:3004:200::/56 permit
2a01:b747:3005:200::/56 permit
2a01:b747:3006:200::/56 permit
2a02:a60:0:5::/64 permit 2a02:a60:0:5::/64 permit
2c0f:fb50:4000::/36 permit 2c0f:fb50:4000::/36 permit
2.207.151.53 permit 2.207.151.53 permit
@ -19,7 +26,6 @@
8.20.114.31 permit 8.20.114.31 permit
8.25.194.0/23 permit 8.25.194.0/23 permit
8.25.196.0/23 permit 8.25.196.0/23 permit
10.162.0.0/16 permit
12.130.86.238 permit 12.130.86.238 permit
13.110.208.0/21 permit 13.110.208.0/21 permit
13.110.209.0/24 permit 13.110.209.0/24 permit
@ -35,7 +41,9 @@
17.57.156.0/24 permit 17.57.156.0/24 permit
17.58.0.0/16 permit 17.58.0.0/16 permit
17.142.0.0/15 permit 17.142.0.0/15 permit
17.143.234.140/30 permit 18.97.0.8/30 permit
18.97.1.184/29 permit
18.97.2.64/26 permit
18.156.89.250 permit 18.156.89.250 permit
18.157.243.190 permit 18.157.243.190 permit
18.194.95.56 permit 18.194.95.56 permit
@ -283,6 +291,9 @@
64.207.219.13 permit 64.207.219.13 permit
64.207.219.14 permit 64.207.219.14 permit
64.207.219.15 permit 64.207.219.15 permit
64.207.219.24 permit
64.207.219.25 permit
64.207.219.26 permit
64.207.219.71 permit 64.207.219.71 permit
64.207.219.72 permit 64.207.219.72 permit
64.207.219.73 permit 64.207.219.73 permit
@ -292,6 +303,9 @@
64.207.219.77 permit 64.207.219.77 permit
64.207.219.78 permit 64.207.219.78 permit
64.207.219.79 permit 64.207.219.79 permit
64.207.219.88 permit
64.207.219.89 permit
64.207.219.90 permit
64.207.219.135 permit 64.207.219.135 permit
64.207.219.136 permit 64.207.219.136 permit
64.207.219.137 permit 64.207.219.137 permit
@ -1464,6 +1478,8 @@
159.135.224.0/20 permit 159.135.224.0/20 permit
159.135.228.10 permit 159.135.228.10 permit
159.183.0.0/16 permit 159.183.0.0/16 permit
159.183.68.71 permit
159.183.79.38 permit
160.1.62.192 permit 160.1.62.192 permit
161.38.192.0/20 permit 161.38.192.0/20 permit
161.38.204.0/22 permit 161.38.204.0/22 permit

View File

@ -1,2 +0,0 @@
servers = "redis:6379";
timeout = 10;

View File

@ -1,3 +1,11 @@
// redirect to mailcow login form
document.addEventListener('DOMContentLoaded', function () {
var loginForm = document.forms.namedItem("loginForm");
if (loginForm) {
window.location.href = '/user';
}
});
// Custom SOGo JS // Custom SOGo JS
// Change the visible font-size in the editor, this does not change the font of a html message by default // Change the visible font-size in the editor, this does not change the font of a html message by default
@ -5,3 +13,4 @@ CKEDITOR.addCss("body {font-size: 16px !important}");
// Enable scayt by default // Enable scayt by default
//CKEDITOR.config.scayt_autoStartup = true; //CKEDITOR.config.scayt_autoStartup = true;

View File

@ -1,28 +1,34 @@
<!-- #!/bin/bash
<example>
<key>canAuthenticate</key> domain="$1"
<string>YES</string> gal_status="$2"
<key>id</key>
<string>${line}_ldap</string> echo "
<key>isAddressBook</key> <!--
<string>NO</string> <example>
<key>IDFieldName</key> <key>canAuthenticate</key>
<string>mail</string> <string>YES</string>
<key>UIDFieldName</key> <key>id</key>
<string>uid</string> <string>"${domain}"_ldap</string>
<key>bindFields</key> <key>isAddressBook</key>
<array> <string>"${gal_status}"</string>
<string>mail</string> <key>IDFieldName</key>
</array> <string>mail</string>
<key>type</key> <key>UIDFieldName</key>
<string>ldap</string> <string>uid</string>
<key>bindDN</key> <key>bindFields</key>
<string>cn=admin,dc=example,dc=local</string> <array>
<key>bindPassword</key> <string>mail</string>
<string>password</string> </array>
<key>baseDN</key> <key>type</key>
<string>ou=People,dc=example,dc=local</string> <string>ldap</string>
<key>hostname</key> <key>bindDN</key>
<string>ldap://1.2.3.4:389</string> <string>cn=admin,dc=example,dc=local</string>
</example> <key>bindPassword</key>
--> <string>password</string>
<key>baseDN</key>
<string>ou=People,dc=example,dc=local</string>
<key>hostname</key>
<string>ldap://1.2.3.4:389</string>
</example>
-->"

View File

@ -1,8 +1,17 @@
<?php <?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") { if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /'); header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit(); exit();
} }
@ -15,7 +24,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
$_SESSION['gal'] = json_decode($license_cache, true); $_SESSION['gal'] = json_decode($license_cache, true);
} }
$js_minifier->add('/web/js/site/debug.js'); $js_minifier->add('/web/js/site/dashboard.js');
// vmail df // vmail df
$exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail'); $exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
@ -59,7 +68,7 @@ foreach ($containers_info as $container => $container_info) {
$hostname = getenv('MAILCOW_HOSTNAME'); $hostname = getenv('MAILCOW_HOSTNAME');
$timezone = getenv('TZ'); $timezone = getenv('TZ');
$template = 'debug.twig'; $template = 'dashboard.twig';
$template_data = [ $template_data = [
'log_lines' => getenv('LOG_LINES'), 'log_lines' => getenv('LOG_LINES'),
'vmail_df' => $vmail_df, 'vmail_df' => $vmail_df,

29
data/web/admin/index.php Normal file
View File

@ -0,0 +1,29 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
$template = 'admin_index.twig';
$template_data = [
'login_delay' => @$_SESSION['ldelay']
];
$js_minifier->add('/web/js/site/index.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@ -1,10 +1,20 @@
<?php <?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /'); header('Location: /domainadmin/mailbox');
exit(); exit();
} }
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
@ -14,7 +24,7 @@ $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');
$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin'; $role = "admin";
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false'; $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
$allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false'; $allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false';

View File

@ -1,8 +1,17 @@
<?php <?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") { if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /'); header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit(); exit();
} }
@ -11,7 +20,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$js_minifier->add('/web/js/site/queue.js'); $js_minifier->add('/web/js/site/queue.js');
$_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin'; $role = "admin";
$template = 'queue.twig'; $template = 'queue.twig';
$template_data = [ $template_data = [

View File

@ -1,8 +1,17 @@
<?php <?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") { if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /'); header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit(); exit();
} }
@ -86,6 +95,8 @@ $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allo
$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']); $cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
$f2b_data = fail2ban('get'); $f2b_data = fail2ban('get');
// mbox templates
$mbox_templates = mailbox('get', 'mailbox_templates');
$template = 'admin.twig'; $template = 'admin.twig';
$template_data = [ $template_data = [
@ -118,6 +129,8 @@ $template_data = [
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings, 'cors_settings' => $cors_settings,
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on', 'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
'iam_settings' => $iam_settings,
'mbox_templates' => $mbox_templates,
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),
'lang_datatables' => json_encode($lang['datatables']) 'lang_datatables' => json_encode($lang['datatables'])
]; ];

View File

@ -409,7 +409,7 @@ paths:
description: a list of domains for which a dkim key should be generated description: a list of domains for which a dkim key should be generated
type: string type: string
key_size: key_size:
description: the key size (1024 or 2048) description: the key size (1024, 2048, 3072 or 4096)
type: number type: number
type: object type: object
summary: Generate DKIM Key summary: Generate DKIM Key
@ -1112,6 +1112,7 @@ paths:
domain: domain.tld domain: domain.tld
local_part: info local_part: info
name: Full name name: Full name
authsource: mailcow
password: atedismonsin password: atedismonsin
password2: atedismonsin password2: atedismonsin
quota: "3072" quota: "3072"
@ -1132,11 +1133,16 @@ paths:
name: name:
description: Full name of the mailbox user description: Full name of the mailbox user
type: string type: string
authsource:
description: Specifies the authentication source for the mailbox.
type: string
enum: [mailcow, ldap, keycloak, generic-oidc]
default: mailcow
password2: password2:
description: mailbox password for confirmation description: mailbox password for confirmation
type: string type: string
password: password:
description: mailbox password description: mailbox password when using `mailcow` as the authentication source.
type: string type: string
quota: quota:
description: mailbox quota description: mailbox quota
@ -3374,6 +3380,7 @@ paths:
active: "1" active: "1"
force_pw_update: "0" force_pw_update: "0"
name: Full name name: Full name
authsource: mailcow
password: "" password: ""
password2: "" password2: ""
quota: "3072" quota: "3072"
@ -3398,11 +3405,15 @@ paths:
name: name:
description: Full name of the mailbox user description: Full name of the mailbox user
type: string type: string
authsource:
description: Specifies the authentication source for the mailbox.
type: string
enum: [mailcow, ldap, keycloak, generic-oidc]
password2: password2:
description: new mailbox password for confirmation description: new mailbox password for confirmation
type: string type: string
password: password:
description: new mailbox password description: new mailbox password when using `mailcow` as the authentication source.
type: string type: string
quota: quota:
description: mailbox quota description: mailbox quota
@ -5687,7 +5698,7 @@ paths:
- description: name of domain - description: name of domain
in: path in: path
name: domain name: domain
required: false required: true
schema: schema:
type: string type: string
- description: e.g. api-key-string - description: e.g. api-key-string
@ -5755,8 +5766,8 @@ paths:
tags: tags:
- Cross-Origin Resource Sharing (CORS) - Cross-Origin Resource Sharing (CORS)
description: >- description: >-
This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API. This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API.
CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests. CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests.
By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain. By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain.
operationId: Edit Cross-Origin Resource Sharing (CORS) settings operationId: Edit Cross-Origin Resource Sharing (CORS) settings
requestBody: requestBody:
@ -5814,6 +5825,220 @@ paths:
Using this endpoint you can get the global spam filter score or the spam filter score of a certain mailbox. Using this endpoint you can get the global spam filter score or the spam filter score of a certain mailbox.
operationId: Get mailbox or global spam filter score operationId: Get mailbox or global spam filter score
summary: Get mailbox or global spam filter score summary: Get mailbox or global spam filter score
/api/v1/edit/identity-provider:
post:
responses:
"401":
$ref: "#/components/responses/Unauthorized"
"200":
content:
application/json:
examples:
response:
value:
- type: "success"
log:
- "identity_provider"
- "edit"
- authsource: "keycloak"
server_url: "https://auth.mailcow.tld"
realm: "mailcow"
client_id: "mailcow_client"
client_secret: "*"
redirect_url: "https://mail.mailcow.tld"
version: "26.1.3"
default_template: "Default"
mappers:
- "small_mbox"
- "medium_mbox"
templates:
- "small"
- "medium"
ignore_ssl_error: true
mailpassword_flow: true
periodic_sync: true
import_users: true
sync_interval: 30
msg:
- "object_modified"
- ""
description: OK
headers: { }
tags:
- Identity Provider
description: >-
Configure an external Identity Provider to use as user authentication
operationId: Edit external Identity Provider settings
requestBody:
content:
application/json:
schema:
properties:
items:
type: array
default: ["identity-provider"]
attr:
type: object
properties:
authsource:
description: Specifies the type of the Identity Provider
type: string
enum: [ldap, keycloak, generic-oidc]
server_url:
description: The base URL of your Keycloak server. Required if `authsource` is keycloak.
type: string
realm:
description: The Keycloak realm where the mailcow client is configured. Required if `authsource` is keycloak.
type: string
client_id:
description: The Client ID assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc.
type: string
client_secret:
description: The Client Secret assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc.
type: string
redirect_url:
description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc.
type: string
version:
description: Specifies the Keycloak version. Required if `authsource` is keycloak.
type: string
default_template:
description: (Optional) If no matching Attribute Mapping exists for a User, the default template will be used for creating the mailbox, but not for updating the mailbox.
type: string
mappers:
description: (Optional) Attribute values used to match a mailbox template. Each element corresponds to the respective index in the templates array (i.e., the first element matches the first element of templates, the second matches the second, and so on).
type: array
templates:
description: (Optional) Defines the mailbox templates to be assigned. Each element corresponds to the respective index in the `mappers` array.
type: array
ignore_ssl_error:
description: If enabled, SSL certificate validation is bypassed
type: boolean
default: false
mailpassword_flow:
description: If enabled, mailcow will attempt to validate user credentials using the Keycloak Admin REST API instead of relying solely on the Authorization Code Flow.
type: boolean
default: false
periodic_sync:
description: If enabled, mailcow periodically performs a full sync of all users from Keycloak or LDAP.
type: boolean
default: false
import_users:
description: If enabled, new users are automatically imported from Keycloak or LDAP into mailcow.
type: boolean
default: false
sync_interval:
description: Defines the time interval (in minutes) for periodic synchronization and user imports.
type: number
default: 15
host:
description: The address of your LDAP server. You can provide a single hostname or a comma-separated list of hosts for fallback in case the primary server is unreachable. Required if `authsource` is ldap.
type: string
port:
description: The port used to connect to the LDAP server. Required if `authsource` is ldap.
type: string
use_ssl:
description: enable LDAPS connection. If Port is set to 389 it will be overriden to 636.
type: boolean
default: false
use_tls:
description: enable TLS connection. TLS is recommended over SSL. SSL Ports cannot be used.
type: boolean
default: false
basedn:
description: The Distinguished Name (DN) from which searches will be performed. Required if `authsource` is ldap.
type: string
username_field:
description: The LDAP attribute used to identify users during authentication. Required if `authsource` is ldap.
type: string
default: mail
filter:
description: An optional LDAP search filter to refine which users can authenticate.
type: string
attribute_field:
description: Specifies an LDAP attribute that holds a specific value which can be mapped to a mailbox template using the Attribute Mapping section. Required if `authsource` is ldap.
type: string
binddn:
description: The Distinguished Name (DN) of the LDAP user that will be used to authenticate and perform LDAP searches. This account should have sufficient permissions to read the required attributes. Required if `authsource` is ldap.
type: string
bindpass:
description: The password for the Bind DN user. It is required for authentication when connecting to the LDAP server. Required if `authsource` is ldap.
type: string
authorize_url:
description: The OIDC provider's authorization server URL. Required if `authsource` is generic-oidc.
type: string
token_url:
description: The OIDC provider's token server URL. Required if `authsource` is generic-oidc.
type: string
userinfo_url:
description: The OIDC provider's user info server URL. Required if `authsource` is generic-oidc.
type: string
client_scopes:
description: Specifies the OIDC scopes requested during authentication.
type: string
default: "openid profile email mailcow_template"
examples:
keycloak:
value:
items:
- "identity-provider"
attr:
authsource: "keycloak"
server_url: "https://auth.mailcow.tld"
realm: "mailcow"
client_id: "mailcow_client"
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
redirect_url: "https://mail.mailcow.tld"
version: "26.1.3"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
ignore_ssl_error: true
mailpassword_flow: true
periodic_sync: true
import_users: true
sync_interval: 30
ldap:
value:
items:
- "identity-provider"
attr:
authsource: "ldap"
host: "127.0.0.1"
port: "389"
use_ssl: false
use_tls: false
ignore_ssl_error: false
basedn: "DC=mailcow,DC=local"
username_field: "mail"
filter: "(memberOf:1.2.840.113556.1.4.1941:=DC=mailcow,DC=local)"
attribute_field: "othermailbox"
binddn: "CN=LDAP Read Only,CN=Users,DC=mailcow,DC=local"
bindpass: "moohoo"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
periodic_sync: true
import_users: true
sync_interval: 30
generic-oidc:
value:
items:
- "identity-provider"
attr:
authsource: "generic-oidc"
authorize_url: "https://auth.mailcow.tld/application/o/authorize/"
token_url: "https://auth.mailcow.tld/application/o/token/"
userinfo_url: "https://auth.mailcow.tld/application/o/userinfo/"
client_id: "mailcow_client"
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
redirect_url: "https://mail.mailcow.tld"
client_scopes: "openid profile email mailcow_template"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
ignore_ssl_error: true
summary: Edit external Identity Provider
tags: tags:
- name: Domains - name: Domains
@ -5860,3 +6085,5 @@ tags:
description: Edit domain ratelimits description: Edit domain ratelimits
- name: Cross-Origin Resource Sharing (CORS) - name: Cross-Origin Resource Sharing (CORS)
description: Manage Cross-Origin Resource Sharing (CORS) settings description: Manage Cross-Origin Resource Sharing (CORS) settings
- name: Identity Provider
description: Manage external Identity Provider settings

View File

@ -1,10 +1,13 @@
<?php <?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
$default_autodiscover_config = $autodiscover_config;
if(file_exists('inc/vars.local.inc.php')) { if(file_exists('inc/vars.local.inc.php')) {
include_once 'inc/vars.local.inc.php'; include_once 'inc/vars.local.inc.php';
} }
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
$default_autodiscover_config = $autodiscover_config;
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config); $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
// Redis // Redis
@ -50,6 +53,11 @@ $opt = [
PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_EMULATE_PREPARES => false,
]; ];
$pdo = new PDO($dsn, $database_user, $database_pass, $opt); $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
// Init Identity Provider
$iam_provider = identity_provider('init');
$iam_settings = identity_provider('get');
$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); $login_user = strtolower(trim($_SERVER['PHP_AUTH_USER']));
$login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW'])); $login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW']));

View File

@ -8,9 +8,6 @@
.dtr-details { .dtr-details {
width: 100%; width: 100%;
} }
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #F2F2F2;
}
td.child>ul>li { td.child>ul>li {
display: flex; display: flex;
} }

View File

@ -33,6 +33,13 @@
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2') format('woff2'), url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2') format('woff2'),
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff'); url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff');
} }
body {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #fbfbfb;
}
#maxmsgsize { min-width: 80px; } #maxmsgsize { min-width: 80px; }
#slider1 .slider-selection { #slider1 .slider-selection {
background: #FFD700; background: #FFD700;
@ -74,10 +81,23 @@
align-items: center; align-items: center;
padding: 0 10px !important; padding: 0 10px !important;
} }
.navbar-fixed-bottom .navbar-collapse, .navbar-fixed-bottom .navbar-collapse,
.navbar-fixed-top .navbar-collapse { .navbar-fixed-top .navbar-collapse {
max-height: 1000px max-height: 1000px
} }
.nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
border-color: #dfdfdf;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
border-color: #dfdfdf;
border-bottom: 1px solid #ffffff;
}
.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
border-color: #dfdfdf;
}
.nav-tabs {
border-bottom: 1px solid #dfdfdf;
}
.bi { .bi {
display: inline-block; display: inline-block;
font-size: 12pt; font-size: 12pt;
@ -123,18 +143,18 @@
} }
} }
@keyframes blink { @keyframes blink {
50% { 50% {
color: transparent color: transparent
} }
} }
.loader-dot { .loader-dot {
animation: 1s blink infinite animation: 1s blink infinite
} }
.loader-dot:nth-child(2) { .loader-dot:nth-child(2) {
animation-delay: 250ms animation-delay: 250ms
} }
.loader-dot:nth-child(3) { .loader-dot:nth-child(3) {
animation-delay: 500ms animation-delay: 500ms
} }
pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;} pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;}
@ -200,13 +220,13 @@ legend {
} }
.haveibeenpwned { .haveibeenpwned {
cursor: pointer; cursor: pointer;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
} }
.full-width-select { .full-width-select {
width: 100%!important; width: 100%!important;
} }
.tooltip { .tooltip {
font-family: inherit; font-family: inherit;
@ -330,7 +350,7 @@ code {
.caret { .caret {
transform: rotate(0deg); transform: rotate(0deg);
} }
a[aria-expanded='true'] > .caret, a[aria-expanded='true'] > .caret,
button[aria-expanded='true'] > .caret { button[aria-expanded='true'] > .caret {
transform: rotate(-180deg); transform: rotate(-180deg);
} }
@ -340,7 +360,7 @@ button[aria-expanded='true'] > .caret {
} }
.list-group-header { .list-group-header {
background: #f7f7f7; background: #f7f7f7;
} }
.bg-primary, .alert-primary, .btn-primary { .bg-primary, .alert-primary, .btn-primary {
@ -366,12 +386,13 @@ button[aria-expanded='true'] > .caret {
background-color: #f0f0f0; background-color: #f0f0f0;
} }
.btn.btn-outline-secondary { .btn.btn-outline-secondary {
border-color: #cfcfcf !important; color: #000000 !important;
border-color: #cfcfcf !important;
} }
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { .btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: #f0f0f0 !important; background-color: #f0f0f0 !important;
} }
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle { .btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
color: #fff; color: #fff;
background-color: #555; background-color: #555;
background-image: none; background-image: none;
@ -389,4 +410,26 @@ button[aria-expanded='true'] > .caret {
.badge.bg-danger > a { .badge.bg-danger > a {
color: #fff !important; color: #fff !important;
text-decoration: none; text-decoration: none;
}
.hr-title {
display: flex;
align-items: center;
text-align: center;
margin: 20px 0;
}
.hr-title::before,
.hr-title::after {
content: "";
flex: 1;
border-bottom: 1px solid #ccc;
}
.hr-title:not(:empty)::before {
margin-right: 10px;
}
.hr-title:not(:empty)::after {
margin-left: 10px;
} }

View File

@ -6,15 +6,9 @@
max-width: 350px; max-width: 350px;
} }
.card-login .apps .btn { .card .apps {
width: auto; display: flex;
float: left; flex-wrap: wrap;
margin-right: 10px;
margin-top: auto;
}
.card-login .apps .btn:hover {
margin-top: 1px !important;
border-bottom-width: 3px;
} }
.responsive-tabs .nav-tabs { .responsive-tabs .nav-tabs {
@ -43,16 +37,6 @@
opacity: 1; opacity: 1;
} }
.card-login .apps .btn {
width: 100%;
float: none;
margin-bottom: 10px;
}
.card-login .apps .btn {
border-bottom-width: 4px;
}
.xs-show { .xs-show {
display: block !important; display: block !important;
} }
@ -113,9 +97,6 @@
.btn-group.nowrap .dropdown-menu { .btn-group.nowrap .dropdown-menu {
width: 100%; width: 100%;
} }
.card-login .btn-group {
display: block;
}
.mass-actions-user .btn-group { .mass-actions-user .btn-group {
float: none; float: none;
} }
@ -191,9 +172,6 @@
.btn-group .btn i { .btn-group .btn i {
margin-right: 5px; margin-right: 5px;
} }
.card-login .btn-group .btn {
display: block !important;
}
.dt-sm-head-hidden .dtr-title { .dt-sm-head-hidden .dtr-title {
display: none !important; display: none !important;
@ -206,7 +184,7 @@
.senders-mw220 { .senders-mw220 {
max-width: 100% !important; max-width: 100% !important;
} }
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before,
table.dataTable td.dt-control:before { table.dataTable td.dt-control:before {
@ -215,7 +193,7 @@
line-height: 2rem; line-height: 2rem;
margin-top: -15px; margin-top: -15px;
} }
li .dtr-data { li .dtr-data {
padding: 0; padding: 0;
} }

View File

@ -59,9 +59,6 @@ body.modal-open {
.table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td { .table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td {
padding: 3px; padding: 3px;
} }
table tbody tr {
cursor: pointer;
}
table tbody tr td input[type="checkbox"] { table tbody tr td input[type="checkbox"] {
cursor: pointer; cursor: pointer;
} }

View File

@ -0,0 +1,28 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
$template = 'domainadmin_index.twig';
$template_data = [
'login_delay' => @$_SESSION['ldelay'],
];
$js_minifier->add('/web/js/site/index.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@ -0,0 +1,58 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "domainadmin") {
header('Location: /domainadmin');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$js_minifier->add('/web/js/site/mailbox.js');
$js_minifier->add('/web/js/presets/sieveMailbox.js');
$js_minifier->add('/web/js/site/pwgen.js');
$role = "domainadmin";
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
$allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false';
// domains
$domains = mailbox('get', 'domains');
// mailboxes
$mailboxes = [];
foreach ($domains as $domain) {
foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) {
$mailboxes[] = $mailbox;
}
}
$template = 'mailbox.twig';
$template_data = [
'acl' => $_SESSION['acl'],
'acl_json' => json_encode($_SESSION['acl']),
'role' => $role,
'is_dual' => $is_dual,
'allow_admin_email_login' => $allow_admin_email_login,
'global_filters' => mailbox('get', 'global_filter_details'),
'domains' => $domains,
'mailboxes' => $mailboxes,
'lang_mailbox' => json_encode($lang['mailbox']),
'lang_rl' => json_encode($lang['ratelimit']),
'lang_edit' => json_encode($lang['edit']),
'lang_datatables' => json_encode($lang['datatables']),
];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@ -0,0 +1,44 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
/*
/ DOMAIN ADMIN
*/
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$tfa_data = get_tfa();
$fido2_data = fido2(array("action" => "get_friendly_names"));
$username = $_SESSION['mailcow_cc_username'];
$template = 'domainadmin.twig';
$template_data = [
'acl' => $_SESSION['acl'],
'acl_json' => json_encode($_SESSION['acl']),
'user_spam_score' => mailbox('get', 'spam_score', $username),
'tfa_data' => $tfa_data,
'fido2_data' => $fido2_data,
'lang_user' => json_encode($lang['user']),
'lang_datatables' => json_encode($lang['datatables']),
];
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
else {
header('Location: /domainadmin');
exit();
}
$js_minifier->add('/web/js/site/user.js');
$js_minifier->add('/web/js/site/pwgen.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@ -131,7 +131,8 @@ if (isset($_SESSION['mailcow_cc_role'])) {
'rlyhosts' => $rlyhosts, 'rlyhosts' => $rlyhosts,
'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox), 'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox),
'user_acls' => acl('get', 'user', $mailbox), 'user_acls' => acl('get', 'user', $mailbox),
'mailbox_details' => $result 'mailbox_details' => $result,
'iam_settings' => $iam_settings,
]; ];
} }
} }

View File

@ -66,6 +66,8 @@ $globalVariables = [
'lang_acl' => json_encode($lang['acl']), 'lang_acl' => json_encode($lang['acl']),
'lang_tfa' => json_encode($lang['tfa']), 'lang_tfa' => json_encode($lang['tfa']),
'lang_fido2' => json_encode($lang['fido2']), 'lang_fido2' => json_encode($lang['fido2']),
'lang_success' => json_encode($lang['success']),
'lang_danger' => json_encode($lang['danger']),
'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'],

View File

@ -1,5 +1,5 @@
<?php <?php
function acl($_action, $_scope = null, $_data = null) { function acl($_action, $_scope = null, $_data = null, $_extra = null) {
global $pdo; global $pdo;
global $lang; global $lang;
$_data_log = $_data; $_data_log = $_data;
@ -24,7 +24,7 @@ function acl($_action, $_scope = null, $_data = null) {
} }
// Users cannot change their own ACL // Users cannot change their own ACL
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username) if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)
|| ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin' && $_SESSION['access_all_exception'] != '1')) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
@ -34,7 +34,7 @@ function acl($_action, $_scope = null, $_data = null) {
} }
// Read all available acl options by calling acl(get) // Read all available acl options by calling acl(get)
// Set all available acl options we cannot find in the post data to 0, else 1 // Set all available acl options we cannot find in the post data to 0, else 1
$is_now = acl('get', 'user', $username); $is_now = acl('get', 'user', $username, $_extra);
if (!empty($is_now)) { if (!empty($is_now)) {
foreach ($is_now as $acl_now_name => $acl_now_val) { foreach ($is_now as $acl_now_name => $acl_now_val) {
$set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0;

View File

@ -0,0 +1,680 @@
<?php
function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
global $pdo;
global $redis;
$is_internal = $extra['is_internal'];
$role = $extra['role'];
// Try validate admin
if (!isset($role) || $role == "admin") {
$result = admin_login($user, $pass);
if ($result !== false) return $result;
}
// Try validate domain admin
if (!isset($role) || $role == "domain_admin") {
$result = domainadmin_login($user, $pass);
if ($result !== false) return $result;
}
// Try validate user
if (!isset($role) || $role == "user") {
$result = user_login($user, $pass);
if ($result !== false) return $result;
}
// Try validate app password
if (!isset($role) || $role == "app") {
$result = apppass_login($user, $pass, $app_passwd_data);
if ($result !== false) return $result;
}
// skip log and only return false if it's an internal request
if ($is_internal == true) return false;
if (!isset($_SESSION['ldelay'])) {
$_SESSION['ldelay'] = "0";
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
elseif (!isset($_SESSION['mailcow_cc_username'])) {
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'login_failed'
);
sleep($_SESSION['ldelay']);
return false;
}
function admin_login($user, $pass){
global $pdo;
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$user = strtolower(trim($user));
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '1'
AND `active` = '1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// verify password
if (verify_hash($row['password'], $pass)) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
// active tfa authenticators found, set pending user login
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "admin";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
} else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "admin";
}
}
return false;
}
function domainadmin_login($user, $pass){
global $pdo;
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '0'
AND `active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// verify password
if (verify_hash($row['password'], $pass) !== false) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "domainadmin";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
}
else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "domainadmin";
}
}
return false;
}
function user_login($user, $pass, $extra = null){
global $pdo;
global $iam_provider;
global $iam_settings;
$is_internal = $extra['is_internal'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$stmt = $pdo->prepare("SELECT
mailbox.*,
domain.active AS d_active
FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// user does not exist, try call idp login and create user if possible via rest flow
if (!$row){
$result = false;
if ($iam_settings['authsource'] == 'keycloak' && intval($iam_settings['mailpassword_flow']) == 1){
$result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal, 'create' => true));
} else if ($iam_settings['authsource'] == 'ldap') {
$result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal, 'create' => true));
}
if ($result !== false){
// double check if mailbox is active
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active`='1'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)) {
return true;
}
}
clear_session();
return false;
}
switch ($row['authsource']) {
case 'keycloak':
// user authsource is keycloak, try using via rest flow
if (intval($iam_settings['mailpassword_flow']) == 1){
$result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal));
if ($result !== false) {
// double check if mailbox and domain is active
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active`='1'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
// authenticators found, init TFA flow
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "user";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: Keycloak'),
'msg' => array('logged_in_as', $user)
);
return "pending";
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
// no authenticators found, login successfull
if (!$is_internal){
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: Keycloak'),
'msg' => array('logged_in_as', $user)
);
}
return "user";
}
}
return $result;
} else {
return false;
}
break;
case 'ldap':
// user authsource is ldap
$result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal));
if ($result !== false) {
// double check if mailbox and domain is active
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active`='1'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
// authenticators found, init TFA flow
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "user";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: LDAP'),
'msg' => array('logged_in_as', $user)
);
return "pending";
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
// no authenticators found, login successfull
if (!$is_internal){
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: LDAP'),
'msg' => array('logged_in_as', $user)
);
}
return "user";
}
}
return $result;
break;
case 'mailcow':
if ($row['active'] != 1 || $row['d_active'] != 1) {
return false;
}
// verify password
if (verify_hash($row['password'], $pass) !== false) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
// authenticators found, init TFA flow
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "user";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: mailcow'),
'msg' => array('logged_in_as', $user)
);
return "pending";
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
// no authenticators found, login successfull
if (!$is_internal){
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: mailcow'),
'msg' => array('logged_in_as', $user)
);
}
return "user";
}
}
break;
}
return false;
}
function apppass_login($user, $pass, $app_passwd_data, $extra = null){
global $pdo;
$is_internal = $extra['is_internal'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$protocol = false;
if ($app_passwd_data['eas']){
$protocol = 'eas';
} else if ($app_passwd_data['dav']){
$protocol = 'dav';
} else if ($app_passwd_data['smtp']){
$protocol = 'smtp';
} else if ($app_passwd_data['imap']){
$protocol = 'imap';
} else if ($app_passwd_data['sieve']){
$protocol = 'sieve';
} else if ($app_passwd_data['pop3']){
$protocol = 'pop3';
} else if (!$is_internal) {
return false;
}
// fetch app password data
$stmt = $pdo->prepare("SELECT `app_passwd`.*, `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`
INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`
INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active` = '1'
AND `domain`.`active` = '1'
AND `app_passwd`.`active` = '1'
AND `app_passwd`.`mailbox` = :user"
);
// fetch password data
$stmt->execute(array(
':user' => $user,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if ($protocol && $row[$protocol . '_access'] != '1'){
continue;
}
// verify password
if (verify_hash($row['password'], $pass) !== false) {
if ($is_internal){
$remote_addr = $extra['remote_addr'];
} else {
$remote_addr = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
}
$service = strtoupper($is_app_passwd);
$stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
$stmt->execute(array(
':service' => $service,
':app_id' => $row['app_passwd_id'],
':username' => $user,
':remote_addr' => $remote_addr
));
unset($_SESSION['ldelay']);
return "user";
}
}
return false;
}
// Keycloak REST Api Flow - auth user by mailcow_password attribute
// This password will be used for direct UI, IMAP and SMTP Auth
// To use direct user credentials, only Authorization Code Flow is valid
function keycloak_mbox_login_rest($user, $pass, $extra = null){
global $pdo;
global $iam_provider;
global $iam_settings;
$is_internal = $extra['is_internal'];
$create = $extra['create'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
// get access_token for service account of mailcow client
$admin_token = identity_provider("get-keycloak-admin-token");
// get the mailcow_password attribute from keycloak user
$url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users";
$queryParams = array('email' => $user, 'exact' => true);
$queryString = http_build_query($queryParams);
$curl = curl_init();
curl_setopt($curl, CURLOPT_TIMEOUT, 7);
curl_setopt($curl, CURLOPT_URL, $url . '?' . $queryString);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Authorization: Bearer ' . $admin_token,
'Content-Type: application/json'
));
$user_res = json_decode(curl_exec($curl), true)[0];
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($code != 200) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'Identity Provider returned HTTP ' . $code),
'msg' => 'generic_server_error'
);
return false;
}
if (!isset($user_res['attributes']['mailcow_password']) || !is_array($user_res['attributes']['mailcow_password'])){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'User has no mailcow_password attribute'),
'msg' => 'generic_server_error'
);
return false;
}
if (empty($user_res['attributes']['mailcow_password'][0])){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', "User's mailcow_password attribute is empty"),
'msg' => 'generic_server_error'
);
return false;
}
// validate mailcow_password
$mailcow_password = $user_res['attributes']['mailcow_password'][0];
if (!verify_hash($mailcow_password, $pass)) {
return false;
}
// get mapped template
$user_template = $user_res['attributes']['mailcow_template'][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
if (!$create) {
// login success
if ($mapper_key !== false) {
// update user
$_SESSION['access_all_exception'] = '1';
mailbox('edit', 'mailbox_from_template', array(
'username' => $user,
'name' => $user_res['name'],
'template' => $iam_settings['templates'][$mapper_key]
));
$_SESSION['access_all_exception'] = '0';
}
return 'user';
}
// check if matching attribute exist
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
if (!empty($iam_settings['default_template'])) {
$mbox_template = $iam_settings['default_template'];
} else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'),
'msg' => 'generic_server_error'
);
return false;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// create mailbox
$_SESSION['access_all_exception'] = '1';
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user)[1],
'local_part' => explode('@', $user)[0],
'name' => $user_res['name'],
'authsource' => 'keycloak',
'template' => $mbox_template
));
$_SESSION['access_all_exception'] = '0';
if (!$create_res){
clear_session();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'Could not create mailbox on login'),
'msg' => 'generic_server_error'
);
return false;
}
return 'user';
}
function ldap_mbox_login($user, $pass, $extra = null){
global $pdo;
global $iam_provider;
global $iam_settings;
$is_internal = $extra['is_internal'];
$create = $extra['create'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
if (!$iam_provider) {
return false;
}
try {
$ldap_query = $iam_provider->query();
if (!empty($iam_settings['filter'])) {
$ldap_query = $ldap_query->rawFilter($iam_settings['filter']);
}
$ldap_query = $ldap_query->where($iam_settings['username_field'], '=', $user)
->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname', 'distinguishedname', 'dn']);
$user_res = $ldap_query->firstOrFail();
} catch (Exception $e) {
// clear $_SESSION['return'] to not leak data
$_SESSION['return'] = array();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', $e->getMessage()),
'msg' => 'generic_server_error'
);
return false;
}
try {
if (!$iam_provider->auth()->attempt($user_res['dn'], $pass)) {
return false;
}
} catch (Exception $e) {
// clear $_SESSION['return'] to not leak data
$_SESSION['return'] = array();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', $e->getMessage()),
'msg' => 'generic_server_error'
);
return false;
}
// get mapped template
$user_template = $user_res[$iam_settings['attribute_field']][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
if (!$create) {
// login success
if ($mapper_key !== false) {
// update user
$_SESSION['access_all_exception'] = '1';
mailbox('edit', 'mailbox_from_template', array(
'username' => $user,
'name' => $user_res['displayname'][0],
'template' => $iam_settings['templates'][$mapper_key]
));
$_SESSION['access_all_exception'] = '0';
}
return 'user';
}
// check if matching attribute exist
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
if (!empty($iam_settings['default_tempalte'])) {
$mbox_template = $iam_settings['default_tempalte'];
} else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'),
'msg' => 'generic_server_error'
);
return false;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// create mailbox
$_SESSION['access_all_exception'] = '1';
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user)[1],
'local_part' => explode('@', $user)[0],
'name' => $user_res['displayname'][0],
'authsource' => 'ldap',
'template' => $mbox_template
));
$_SESSION['access_all_exception'] = '0';
if (!$create_res){
clear_session();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'Could not create mailbox on login'),
'msg' => 'generic_server_error'
);
return false;
}
return 'user';
}

View File

@ -3,7 +3,7 @@ function customize($_action, $_item, $_data = null) {
global $redis; global $redis;
global $lang; global $lang;
global $LOGO_LIMITS; global $LOGO_LIMITS;
switch ($_action) { switch ($_action) {
case 'add': case 'add':
// disable functionality when demo mode is enabled // disable functionality when demo mode is enabled
@ -122,10 +122,16 @@ function customize($_action, $_item, $_data = null) {
case 'app_links': case 'app_links':
$apps = (array)$_data['app']; $apps = (array)$_data['app'];
$links = (array)$_data['href']; $links = (array)$_data['href'];
$user_links = (array)$_data['user_href'];
$hide = (array)$_data['hide'];
$out = array(); $out = array();
if (count($apps) == count($links)) { if (count($apps) == count($links) && count($apps) == count($user_links) && count($apps) == count($hide)) {
for ($i = 0; $i < count($apps); $i++) { for ($i = 0; $i < count($apps); $i++) {
$out[] = array($apps[$i] => $links[$i]); $out[] = array($apps[$i] => array(
'link' => $links[$i],
'user_link' => $user_links[$i],
'hide' => ($hide[$i] === '0' || $hide[$i] === 0) ? false : true
));
} }
try { try {
$redis->set('APP_LINKS', json_encode($out)); $redis->set('APP_LINKS', json_encode($out));
@ -256,7 +262,23 @@ function customize($_action, $_item, $_data = null) {
); );
return false; return false;
} }
return ($app_links) ? $app_links : false;
if (empty($app_links)){
return false;
}
// convert from old style
foreach($app_links as $i => $entry){
foreach($entry as $app => $link){
if (empty($link['link']) && empty($link['user_link'])){
$app_links[$i][$app] = array();
$app_links[$i][$app]['link'] = $link;
$app_links[$i][$app]['user_link'] = $link;
}
}
}
return $app_links;
break; break;
case 'main_logo': case 'main_logo':
case 'main_logo_dark': case 'main_logo_dark':

View File

@ -240,9 +240,12 @@ function dkim($_action, $_data = null, $privkey = false) {
if (strlen($dkimdata['pubkey']) < 391) { if (strlen($dkimdata['pubkey']) < 391) {
$dkimdata['length'] = "1024"; $dkimdata['length'] = "1024";
} }
elseif (strlen($dkimdata['pubkey']) < 736) { elseif (strlen($dkimdata['pubkey']) < 564) {
$dkimdata['length'] = "2048"; $dkimdata['length'] = "2048";
} }
elseif (strlen($dkimdata['pubkey']) < 736) {
$dkimdata['length'] = "3072";
}
elseif (strlen($dkimdata['pubkey']) < 1416) { elseif (strlen($dkimdata['pubkey']) < 1416) {
$dkimdata['length'] = "4096"; $dkimdata['length'] = "4096";
} }

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
global $redis; global $redis;
global $lang; global $lang;
global $MAILBOX_DEFAULT_ATTRIBUTES; global $MAILBOX_DEFAULT_ATTRIBUTES;
global $iam_settings;
$_data_log = $_data; $_data_log = $_data;
!isset($_data_log['password']) ?: $_data_log['password'] = '*'; !isset($_data_log['password']) ?: $_data_log['password'] = '*';
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; !isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
@ -1005,6 +1007,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$local_part = strtolower(trim($_data['local_part'])); $local_part = strtolower(trim($_data['local_part']));
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
$username = $local_part . '@' . $domain; $username = $local_part . '@' . $domain;
$authsource = 'mailcow';
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@ -1021,15 +1024,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
return false; return false;
} }
if ($_data['authsource'] == "mailcow" ||
in_array($_data['authsource'], array('keycloak', 'generic-oidc', 'ldap')) && $iam_settings['authsource'] == $_data['authsource']){
$authsource = $_data['authsource'];
}
if (empty($name)) { if (empty($name)) {
$name = $local_part; $name = $local_part;
} }
$template_attr = null; $template_attr = null;
if ($_data['template']){ if ($_data['template']){
$template_attr = mailbox('get', 'mailbox_templates', $_data['template'])['attributes']; $template_attr = mailbox('get', 'mailbox_templates', $_data['template'], $_extra)['attributes'];
} }
if (empty($template_attr)) { if (empty($template_attr)) {
$template_attr = mailbox('get', 'mailbox_templates')[0]['attributes']; $template_attr = mailbox('get', 'mailbox_templates', null, $_extra)[0]['attributes'];
} }
$MAILBOX_DEFAULT_ATTRIBUTES = array_merge($MAILBOX_DEFAULT_ATTRIBUTES, $template_attr); $MAILBOX_DEFAULT_ATTRIBUTES = array_merge($MAILBOX_DEFAULT_ATTRIBUTES, $template_attr);
@ -1038,7 +1045,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$name = ltrim(rtrim($_data['name'], '>'), '<'); $name = ltrim(rtrim($_data['name'], '>'), '<');
$tags = (isset($_data['tags'])) ? $_data['tags'] : $MAILBOX_DEFAULT_ATTRIBUTES['tags']; $tags = (isset($_data['tags'])) ? $_data['tags'] : $MAILBOX_DEFAULT_ATTRIBUTES['tags'];
$quota_m = (isset($_data['quota'])) ? intval($_data['quota']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['quota']) / 1024 ** 2; $quota_m = (isset($_data['quota'])) ? intval($_data['quota']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['quota']) / 1024 ** 2;
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) { if ($authsource != 'mailcow'){
$password = '';
$password2 = '';
$password_hashed = '';
}
if (!hasACLAccess("unlimited_quota") && $quota_m === 0) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -1067,6 +1079,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); $quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
$quota_b = ($quota_m * 1048576); $quota_b = ($quota_m * 1048576);
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
$force_pw_update = 0;
}
$mailbox_attrs = json_encode( $mailbox_attrs = json_encode(
array( array(
'force_pw_update' => strval($force_pw_update), 'force_pw_update' => strval($force_pw_update),
@ -1081,7 +1097,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'passwd_update' => time(), 'passwd_update' => time(),
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']), 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
'quarantine_notification' => strval($quarantine_notification), 'quarantine_notification' => strval($quarantine_notification),
'quarantine_category' => strval($quarantine_category) 'quarantine_category' => strval($quarantine_category),
'attribute_hash' => $attribute_hash
) )
); );
if (!is_valid_domain_name($domain)) { if (!is_valid_domain_name($domain)) {
@ -1156,10 +1173,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
return false; return false;
} }
if (password_check($password, $password2) !== true) { if ($authsource == 'mailcow'){
return false; if (password_check($password, $password2) !== true) {
return false;
}
$password_hashed = hash_password($password);
} }
$password_hashed = hash_password($password);
if ($MailboxData['count'] >= $DomainData['mailboxes']) { if ($MailboxData['count'] >= $DomainData['mailboxes']) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@ -1185,8 +1204,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
return false; return false;
} }
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`) $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `authsource`, `active`)
VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)"); VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :authsource, :active)");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
':password_hashed' => $password_hashed, ':password_hashed' => $password_hashed,
@ -1195,6 +1214,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':local_part' => $local_part, ':local_part' => $local_part,
':domain' => $domain, ':domain' => $domain,
':mailbox_attrs' => $mailbox_attrs, ':mailbox_attrs' => $mailbox_attrs,
':authsource' => $authsource,
':active' => $active ':active' => $active
)); ));
$stmt = $pdo->prepare("UPDATE `mailbox` SET $stmt = $pdo->prepare("UPDATE `mailbox` SET
@ -1214,11 +1234,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
break; break;
} }
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)"); try {
$stmt->execute(array( $stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
':username' => $username, $stmt->execute(array(
':tag_name' => $tag, ':username' => $username,
)); ':tag_name' => $tag,
));
} catch (Exception $e) {
}
} }
$stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`) $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)
VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';"); VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';");
@ -1312,16 +1335,62 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'object' => $username, 'object' => $username,
'rl_frame' => $_data['rl_frame'], 'rl_frame' => $_data['rl_frame'],
'rl_value' => $_data['rl_value'] 'rl_value' => $_data['rl_value']
)); ), $_extra);
} }
update_sogo_static_view($username); try {
update_sogo_static_view($username);
} catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_added', htmlspecialchars($username)) 'msg' => array('mailbox_added', htmlspecialchars($username))
); );
return true; break;
case 'mailbox_from_template':
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `template` = :template AND type = 'mailbox'");
$stmt->execute(array(
":template" => $_data['template']
));
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($mbox_template_data)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'template_missing'
);
return false;
}
$attribute_hash = sha1(json_encode($mbox_template_data["attributes"]));
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
$mbox_template_data['domain'] = $_data['domain'];
$mbox_template_data['name'] = $_data['name'];
$mbox_template_data['local_part'] = $_data['local_part'];
$mbox_template_data['authsource'] = $_data['authsource'];
$mbox_template_data['attribute_hash'] = $attribute_hash;
$mbox_template_data['quota'] = intval($mbox_template_data['quota'] / 1048576);
$mailbox_attributes = array('acl' => array());
foreach ($mbox_template_data as $key => $value){
switch (true) {
case (strpos($key, 'acl_') === 0 && $value != 0):
array_push($mailbox_attributes['acl'], str_replace('acl_' , '', $key));
break;
default:
$mailbox_attributes[$key] = $value;
break;
}
}
return mailbox('add', 'mailbox', $mailbox_attributes);
break; break;
case 'resource': case 'resource':
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
@ -1689,7 +1758,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else { else {
$usernames = $_data['username']; $usernames = $_data['username'];
} }
if (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1" ) { if (!hasACLAccess("tls_policy")) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -1698,7 +1767,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false; return false;
} }
foreach ($usernames as $username) { foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -1706,7 +1775,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
$is_now = mailbox('get', 'tls_policy', $username); $is_now = mailbox('get', 'tls_policy', $username, $_extra);
if (!empty($is_now)) { if (!empty($is_now)) {
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out'];
@ -1743,7 +1812,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else { else {
$usernames = $_data['username']; $usernames = $_data['username'];
} }
if (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1" ) { if (!hasACLAccess("quarantine_notification")) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -1752,7 +1821,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false; return false;
} }
foreach ($usernames as $username) { foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -1760,7 +1829,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
$is_now = mailbox('get', 'quarantine_notification', $username); $is_now = mailbox('get', 'quarantine_notification', $username, $_extra);
if (!empty($is_now)) { if (!empty($is_now)) {
$quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; $quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
} }
@ -1802,7 +1871,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else { else {
$usernames = $_data['username']; $usernames = $_data['username'];
} }
if (!isset($_SESSION['acl']['quarantine_category']) || $_SESSION['acl']['quarantine_category'] != "1" ) { if (!hasACLAccess("quarantine_category")) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -1811,7 +1880,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false; return false;
} }
foreach ($usernames as $username) { foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -1819,7 +1888,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
$is_now = mailbox('get', 'quarantine_category', $username); $is_now = mailbox('get', 'quarantine_category', $username, $_extra);
if (!empty($is_now)) { if (!empty($is_now)) {
$quarantine_category = (isset($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; $quarantine_category = (isset($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
} }
@ -2863,7 +2932,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
$is_now = mailbox('get', 'mailbox_details', $username); $is_now = mailbox('get', 'mailbox_details', $username, $_extra);
if (isset($_data['protocol_access'])) { if (isset($_data['protocol_access'])) {
$_data['protocol_access'] = (array)$_data['protocol_access']; $_data['protocol_access'] = (array)$_data['protocol_access'];
$_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
@ -2874,20 +2943,29 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (!empty($is_now)) { if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
(int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); (int)$sogo_access = (isset($_data['sogo_access']) && hasACLAccess("sogo_access")) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
(int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']); (int)$imap_access = (isset($_data['imap_access']) && hasACLAccess("protocol_access")) ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
(int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); (int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
(int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); (int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
(int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']); (int)$sieve_access = (isset($_data['sieve_access']) && hasACLAccess("protocol_access")) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
(int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']); (int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
$domain = $is_now['domain']; $domain = $is_now['domain'];
$quota_b = $quota_m * 1048576; $quota_b = $quota_m * 1048576;
$password = (!empty($_data['password'])) ? $_data['password'] : null; $password = (!empty($_data['password'])) ? $_data['password'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
$pw_recovery_email = (isset($_data['pw_recovery_email'])) ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
$tags = (is_array($_data['tags']) ? $_data['tags'] : array()); $tags = (is_array($_data['tags']) ? $_data['tags'] : array());
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
$authsource = $is_now['authsource'];
if ($_data['authsource'] == "mailcow" ||
in_array($_data['authsource'], array('keycloak', 'generic-oidc', 'ldap')) && $iam_settings['authsource'] == $_data['authsource']){
$authsource = $_data['authsource'];
}
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
$force_pw_update = 0;
}
$pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
} }
else { else {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -2898,7 +2976,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
continue; continue;
} }
// if already 0 == ok // if already 0 == ok
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) { if (!hasACLAccess("unlimited_quota") && ($quota_m == 0 && $is_now['quota'] != 0)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -2914,7 +2992,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
$DomainData = mailbox('get', 'domain_details', $domain); $DomainData = mailbox('get', 'domain_details', $domain, $_extra);
if ($quota_m > ($is_now['max_new_quota'] / 1048576)) { if ($quota_m > ($is_now['max_new_quota'] / 1048576)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@ -2933,7 +3011,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
$extra_acls = array(); $extra_acls = array();
if (isset($_data['extended_sender_acl'])) { if (isset($_data['extended_sender_acl'])) {
if (!isset($_SESSION['acl']['extend_sender_acl']) || $_SESSION['acl']['extend_sender_acl'] != "1" ) { if (!hasACLAccess("extend_sender_acl")) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -3126,7 +3204,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt = $pdo->prepare("UPDATE `mailbox` SET $stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed, `password` = :password_hashed,
`attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())
WHERE `username` = :username"); WHERE `username` = :username AND authsource = 'mailcow'");
$stmt->execute(array( $stmt->execute(array(
':password_hashed' => $password_hashed, ':password_hashed' => $password_hashed,
':username' => $username ':username' => $username
@ -3145,6 +3223,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`active` = :active, `active` = :active,
`name`= :name, `name`= :name,
`quota` = :quota_b, `quota` = :quota_b,
`authsource` = :authsource,
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update), `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access), `attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access), `attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
@ -3152,22 +3231,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access), `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
`attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost), `attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access), `attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access),
`attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email) `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email),
`attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash)
WHERE `username` = :username"); WHERE `username` = :username");
$stmt->execute(array( $stmt->execute(array(
':active' => $active, ':active' => $active,
':name' => $name, ':name' => $name,
':quota_b' => $quota_b, ':quota_b' => $quota_b,
':force_pw_update' => $force_pw_update, ':attribute_hash' => $attribute_hash,
':sogo_access' => $sogo_access, ':force_pw_update' => $force_pw_update,
':imap_access' => $imap_access, ':sogo_access' => $sogo_access,
':pop3_access' => $pop3_access, ':imap_access' => $imap_access,
':sieve_access' => $sieve_access, ':pop3_access' => $pop3_access,
':smtp_access' => $smtp_access, ':sieve_access' => $sieve_access,
':recovery_email' => $pw_recovery_email, ':smtp_access' => $smtp_access,
':relayhost' => $relayhost, ':recovery_email' => $pw_recovery_email,
':username' => $username ':relayhost' => $relayhost,
)); ':username' => $username,
':authsource' => $authsource
));
} }
catch (PDOException $e) { catch (PDOException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -3188,11 +3270,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
break; break;
} }
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)"); try {
$stmt->execute(array( $stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
':username' => $username, $stmt->execute(array(
':tag_name' => $tag, ':username' => $username,
)); ':tag_name' => $tag,
));
} catch (Exception $e) {
}
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -3201,7 +3286,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('mailbox_modified', $username) 'msg' => array('mailbox_modified', $username)
); );
update_sogo_static_view($username); try {
update_sogo_static_view($username);
} catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
} }
return true; return true;
break; break;
@ -3401,6 +3494,75 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('mailbox_renamed', $old_username, $new_username) 'msg' => array('mailbox_renamed', $old_username, $new_username)
); );
break; break;
case 'mailbox_from_template':
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `template` = :template AND type = 'mailbox'");
$stmt->execute(array(
":template" => $_data['template']
));
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($mbox_template_data)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'template_missing'
);
return false;
}
$attribute_hash = sha1(json_encode($mbox_template_data["attributes"]));
$is_now = mailbox('get', 'mailbox_details', $_data['username']);
$name = ltrim(rtrim($_data['name'], '>'), '<');
if ($is_now['attributes']['attribute_hash'] == $attribute_hash && $is_now['name'] == $name)
return true;
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
$mbox_template_data['attribute_hash'] = $attribute_hash;
$mbox_template_data['name'] = $name;
$quarantine_attributes = array('username' => $_data['username']);
$tls_attributes = array('username' => $_data['username']);
$ratelimit_attributes = array('object' => $_data['username']);
$acl_attributes = array('username' => $_data['username'], 'user_acl' => array());
$mailbox_attributes = array('username' => $_data['username']);
foreach ($mbox_template_data as $key => $value){
switch (true) {
case (strpos($key, 'quarantine_') === 0):
$quarantine_attributes[$key] = $value;
break;
case (strpos($key, 'tls_') === 0):
if ($value == null)
$value = 0;
$tls_attributes[$key] = $value;
break;
case (strpos($key, 'rl_') === 0):
$ratelimit_attributes[$key] = $value;
break;
case (strpos($key, 'acl_') === 0 && $value != 0):
array_push($acl_attributes['user_acl'], str_replace('acl_' , '', $key));
break;
default:
$mailbox_attributes[$key] = $value;
break;
}
}
$mailbox_attributes['quota'] = intval($mailbox_attributes['quota'] / 1048576);
$result = mailbox('edit', 'mailbox', $mailbox_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'tls_policy', $tls_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'quarantine_notification', $quarantine_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'quarantine_category', $quarantine_attributes);
if ($result === false) return $result;
$result = ratelimit('edit', 'mailbox', $ratelimit_attributes);
if ($result === false) return $result;
$result = acl('edit', 'user', $acl_attributes);
if ($result === false) return $result;
$_SESSION['return'] = array();
return true;
break;
case 'mailbox_templates': case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -4666,6 +4828,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`quota`, `mailbox`.`quota`,
`mailbox`.`created`, `mailbox`.`created`,
`mailbox`.`modified`, `mailbox`.`modified`,
`mailbox`.`authsource`,
`quota2`.`bytes`, `quota2`.`bytes`,
`attributes`, `attributes`,
`custom_attributes`, `custom_attributes`,
@ -4687,6 +4850,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`quota`, `mailbox`.`quota`,
`mailbox`.`created`, `mailbox`.`created`,
`mailbox`.`modified`, `mailbox`.`modified`,
`mailbox`.`authsource`,
`quota2replica`.`bytes`, `quota2replica`.`bytes`,
`attributes`, `attributes`,
`custom_attributes`, `custom_attributes`,
@ -4716,6 +4880,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100); $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['created'] = $row['created']; $mailboxdata['created'] = $row['created'];
$mailboxdata['modified'] = $row['modified']; $mailboxdata['modified'] = $row['modified'];
$mailboxdata['authsource'] = ($row['authsource']) ? $row['authsource'] : 'mailcow';
if ($mailboxdata['percent_in_use'] === '- ') { if ($mailboxdata['percent_in_use'] === '- ') {
$mailboxdata['percent_class'] = "info"; $mailboxdata['percent_class'] = "info";
@ -4746,7 +4911,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else if ($SaslLogs['service'] == 'pop3') { else if ($SaslLogs['service'] == 'pop3') {
$last_pop3_login = strtotime($SaslLogs['datetime']); $last_pop3_login = strtotime($SaslLogs['datetime']);
} }
else if ($SaslLogs['service'] == 'SSO') { else if ($SaslLogs['service'] == 'SSO') {
$last_sso_login = strtotime($SaslLogs['datetime']); $last_sso_login = strtotime($SaslLogs['datetime']);
} }
} }
@ -4759,7 +4924,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_pop3_login = 0; $last_pop3_login = 0;
} }
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;
} }
$mailboxdata['last_imap_login'] = $last_imap_login; $mailboxdata['last_imap_login'] = $last_imap_login;
@ -4811,7 +4976,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $mailboxdata; return $mailboxdata;
break; break;
case 'mailbox_templates': case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin" && $_SESSION['access_all_exception'] != "1") {
return false; return false;
} }
$_data = (isset($_data)) ? intval($_data) : null; $_data = (isset($_data)) ? intval($_data) : null;
@ -5565,7 +5730,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
continue; continue;
} }
update_sogo_static_view($username); try {
update_sogo_static_view($username);
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -5779,6 +5952,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
break; break;
} }
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") { if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") {
update_sogo_static_view(); try {
update_sogo_static_view();
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
} }
return true;
} }

View File

@ -1,10 +1,10 @@
<?php <?php
function ratelimit($_action, $_scope, $_data = null) { function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
global $redis; global $redis;
$_data_log = $_data; $_data_log = $_data;
switch ($_action) { switch ($_action) {
case 'edit': case 'edit':
if (!isset($_SESSION['acl']['ratelimit']) || $_SESSION['acl']['ratelimit'] != "1" ) { if (!hasACLAccess("ratelimit")) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -93,7 +93,7 @@ function ratelimit($_action, $_scope, $_data = null) {
continue; continue;
} }
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object) if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)
|| ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin' && $_SESSION['access_all_exception'] != '1')) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),

View File

@ -30,6 +30,40 @@ if(!file_exists($CSSPath)) {
cleanupCSS($hash); cleanupCSS($hash);
} }
$mailcow_apps_processed = $MAILCOW_APPS;
$app_links = customize('get', 'app_links');
$app_links_processed = $app_links;
$hide_mailcow_apps = true;
for ($i = 0; $i < count($mailcow_apps_processed); $i++) {
if ($hide_mailcow_apps && !$mailcow_apps_processed[$i]['hide']){
$hide_mailcow_apps = false;
}
if (!empty($_SESSION['mailcow_cc_username'])){
if ($app_links_processed[$i]['user_link']) {
$mailcow_apps_processed[$i]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $mailcow_apps_processed[$i]['user_link']);
} else {
$mailcow_apps_processed[$i]['user_link'] = $mailcow_apps_processed[$i]['link'];
}
}
}
if ($app_links_processed){
for ($i = 0; $i < count($app_links_processed); $i++) {
$key = array_key_first($app_links_processed[$i]);
if ($hide_mailcow_apps && !$app_links_processed[$i][$key]['hide']){
$hide_mailcow_apps = false;
}
if (!empty($_SESSION['mailcow_cc_username'])){
if ($app_links_processed[$i][$key]['user_link']) {
$app_links_processed[$i][$key]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $app_links_processed[$i][$key]['user_link']);
} else {
$app_links_processed[$i][$key]['user_link'] = $app_links_processed[$i][$key]['link'];
}
}
}
}
$globalVariables = [ $globalVariables = [
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'), 'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
'mailcow_locale' => @$_SESSION['mailcow_locale'], 'mailcow_locale' => @$_SESSION['mailcow_locale'],
@ -45,8 +79,11 @@ $globalVariables = [
'lang' => $lang, 'lang' => $lang,
'skip_sogo' => (getenv('SKIP_SOGO') == 'y'), 'skip_sogo' => (getenv('SKIP_SOGO') == 'y'),
'allow_admin_email_login' => (getenv('ALLOW_ADMIN_EMAIL_LOGIN') == 'n'), 'allow_admin_email_login' => (getenv('ALLOW_ADMIN_EMAIL_LOGIN') == 'n'),
'hide_mailcow_apps' => $hide_mailcow_apps,
'mailcow_apps' => $MAILCOW_APPS, 'mailcow_apps' => $MAILCOW_APPS,
'app_links' => customize('get', 'app_links'), 'mailcow_apps_processed' => $mailcow_apps_processed,
'app_links' => $app_links,
'app_links_processed' => $app_links_processed,
'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'), 'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'),
'uri' => $_SERVER['REQUEST_URI'], 'uri' => $_SERVER['REQUEST_URI'],
]; ];

View File

@ -4,7 +4,7 @@ function init_db_schema()
try { try {
global $pdo; global $pdo;
$db_version = "20112024_1105"; $db_version = "27012025_1555";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@ -368,6 +368,7 @@ function init_db_schema()
"custom_attributes" => "JSON NOT NULL DEFAULT ('{}')", "custom_attributes" => "JSON NOT NULL DEFAULT ('{}')",
"kind" => "VARCHAR(100) NOT NULL DEFAULT ''", "kind" => "VARCHAR(100) NOT NULL DEFAULT ''",
"multiple_bookings" => "INT NOT NULL DEFAULT -1", "multiple_bookings" => "INT NOT NULL DEFAULT -1",
"authsource" => "ENUM('mailcow', 'keycloak', 'generic-oidc', 'ldap') DEFAULT 'mailcow'",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'" "active" => "TINYINT(1) NOT NULL DEFAULT '1'"
@ -575,6 +576,20 @@ function init_db_schema()
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
), ),
"identity_provider" => array(
"cols" => array(
"key" => "VARCHAR(255) NOT NULL",
"value" => "TEXT NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("key")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"logs" => array( "logs" => array(
"cols" => array( "cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT", "id" => "INT NOT NULL AUTO_INCREMENT",
@ -1455,6 +1470,9 @@ function init_db_schema()
)); ));
} }
// remove old sogo views and triggers
$pdo->query("DROP TRIGGER IF EXISTS sogo_update_password");
if (php_sapi_name() == "cli") { if (php_sapi_name() == "cli") {
echo "DB initialization completed" . PHP_EOL; echo "DB initialization completed" . PHP_EOL;
} else { } else {
@ -1478,6 +1496,7 @@ function init_db_schema()
} }
if (php_sapi_name() == "cli") { if (php_sapi_name() == "cli") {
include '/web/inc/vars.inc.php'; include '/web/inc/vars.inc.php';
include '/web/inc/functions.inc.php';
include '/web/inc/functions.docker.inc.php'; include '/web/inc/functions.docker.inc.php';
// $now = new DateTime(); // $now = new DateTime();
// $mins = $now->getOffset() / 60; // $mins = $now->getOffset() / 60;
@ -1499,9 +1518,7 @@ if (php_sapi_name() == "cli") {
if (intval($res['OK_C']) === 2) { if (intval($res['OK_C']) === 2) {
// Be more precise when replacing into _sogo_static_view, col orders may change // Be more precise when replacing into _sogo_static_view, col orders may change
try { try {
$stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`) update_sogo_static_view();
SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view");
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
echo "Fixed _sogo_static_view" . PHP_EOL; echo "Fixed _sogo_static_view" . PHP_EOL;
} catch (Exception $e) { } catch (Exception $e) {
// Dunno // Dunno

View File

@ -1,7 +1,6 @@
{ {
"require": { "require": {
"robthree/twofactorauth": "^1.6", "robthree/twofactorauth": "^1.6",
"yubico/u2flib-server": "^1.0",
"phpmailer/phpmailer": "^6.1", "phpmailer/phpmailer": "^6.1",
"php-mime-mail-parser/php-mime-mail-parser": "^7", "php-mime-mail-parser/php-mime-mail-parser": "^7",
"soundasleep/html2text": "^0.5.0", "soundasleep/html2text": "^0.5.0",
@ -9,7 +8,9 @@
"matthiasmullie/minify": "^1.3", "matthiasmullie/minify": "^1.3",
"bshaffer/oauth2-server-php": "^1.11", "bshaffer/oauth2-server-php": "^1.11",
"mustangostang/spyc": "^0.6.3", "mustangostang/spyc": "^0.6.3",
"directorytree/ldaprecord": "^2.4", "directorytree/ldaprecord": "^3.3",
"twig/twig": "^3.0" "twig/twig": "^3.0",
"stevenmaguire/oauth2-keycloak": "^4.0",
"league/oauth2-client": "^2.7"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
namespace Composer; namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php'; $GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) { if (PHP_VERSION_ID < 80000) {
@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) {
{ {
private $handle; private $handle;
private $position; private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path) public function stream_open($path, $mode, $options, &$opened_path)
{ {
// get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 21); $opened_path = substr($path, 17);
$opened_path = realpath($opened_path) ?: $opened_path; $this->realpath = realpath($opened_path) ?: $opened_path;
$this->handle = fopen($opened_path, $mode); $opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0; $this->position = 0;
// remove all traces of this stream wrapper once it has been used
stream_wrapper_unregister('composer-bin-proxy');
return (bool) $this->handle; return (bool) $this->handle;
} }
@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) {
return $operation ? flock($this->handle, $operation) : true; return $operation ? flock($this->handle, $operation) : true;
} }
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell() public function stream_tell()
{ {
return $this->position; return $this->position;
@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) {
public function stream_stat() public function stream_stat()
{ {
return fstat($this->handle); return array();
} }
public function stream_set_option($option, $arg1, $arg2) public function stream_set_option($option, $arg1, $arg2)
{ {
return true; return true;
} }
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
} }
} }
if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { if (
include("composer-bin-proxy://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon'); (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
exit(0); || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
} }
} }
include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon'; return include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';

View File

@ -12,6 +12,7 @@
namespace Composer; namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php'; $GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) { if (PHP_VERSION_ID < 80000) {
@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) {
{ {
private $handle; private $handle;
private $position; private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path) public function stream_open($path, $mode, $options, &$opened_path)
{ {
// get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 21); $opened_path = substr($path, 17);
$opened_path = realpath($opened_path) ?: $opened_path; $this->realpath = realpath($opened_path) ?: $opened_path;
$this->handle = fopen($opened_path, $mode); $opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0; $this->position = 0;
// remove all traces of this stream wrapper once it has been used
stream_wrapper_unregister('composer-bin-proxy');
return (bool) $this->handle; return (bool) $this->handle;
} }
@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) {
return $operation ? flock($this->handle, $operation) : true; return $operation ? flock($this->handle, $operation) : true;
} }
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell() public function stream_tell()
{ {
return $this->position; return $this->position;
@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) {
public function stream_stat() public function stream_stat()
{ {
return fstat($this->handle); return array();
} }
public function stream_set_option($option, $arg1, $arg2) public function stream_set_option($option, $arg1, $arg2)
{ {
return true; return true;
} }
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
} }
} }
if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { if (
include("composer-bin-proxy://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'); (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
exit(0); || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
} }
} }
include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'; return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Carbon
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.

View File

@ -0,0 +1,14 @@
# carbonphp/carbon-doctrine-types
Types to use Carbon in Doctrine
## Documentation
[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/symfony/)
This package is an externalization of [src/Carbon/Doctrine](https://github.com/briannesbitt/Carbon/tree/2.71.0/src/Carbon/Doctrine)
from `nestbot/carbon` package.
Externalization allows to better deal with different versions of dbal. With
version 4.0 of dbal, it no longer sustainable to be compatible with all version
using a single code.

View File

@ -0,0 +1,36 @@
{
"name": "carbonphp/carbon-doctrine-types",
"description": "Types to use Carbon in Doctrine",
"type": "library",
"keywords": [
"date",
"time",
"DateTime",
"Carbon",
"Doctrine"
],
"require": {
"php": "^8.1"
},
"require-dev": {
"doctrine/dbal": "^4.0.0",
"nesbot/carbon": "^2.71.0 || ^3.0.0",
"phpunit/phpunit": "^10.3"
},
"conflict": {
"doctrine/dbal": "<4.0.0 || >=5.0.0"
},
"license": "MIT",
"autoload": {
"psr-4": {
"Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
}
},
"authors": [
{
"name": "KyleKatarn",
"email": "kylekatarnls@gmail.com"
}
],
"minimum-stability": "dev"
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
use Doctrine\DBAL\Platforms\AbstractPlatform;
interface CarbonDoctrineType
{
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform);
public function convertToPHPValue(mixed $value, AbstractPlatform $platform);
public function convertToDatabaseValue($value, AbstractPlatform $platform);
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
class CarbonImmutableType extends DateTimeImmutableType implements CarbonDoctrineType
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
class CarbonType extends DateTimeType implements CarbonDoctrineType
{
}

View File

@ -1,13 +1,6 @@
<?php <?php
/** declare(strict_types=1);
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Carbon\Doctrine; namespace Carbon\Doctrine;
@ -15,7 +8,12 @@ use Carbon\Carbon;
use Carbon\CarbonInterface; use Carbon\CarbonInterface;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Types\Exception\InvalidType;
use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
use Exception; use Exception;
/** /**
@ -23,6 +21,14 @@ use Exception;
*/ */
trait CarbonTypeConverter trait CarbonTypeConverter
{ {
/**
* This property differentiates types installed by carbonphp/carbon-doctrine-types
* from the ones embedded previously in nesbot/carbon source directly.
*
* @readonly
*/
public bool $external = true;
/** /**
* @return class-string<T> * @return class-string<T>
*/ */
@ -31,20 +37,12 @@ trait CarbonTypeConverter
return Carbon::class; return Carbon::class;
} }
/** public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
* @return string
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{ {
$precision = $fieldDeclaration['precision'] ?: 10; $precision = min(
$fieldDeclaration['precision'] ?? DateTimeDefaultPrecision::get(),
if ($fieldDeclaration['secondPrecision'] ?? false) { $this->getMaximumPrecision($platform),
$precision = 0; );
}
if ($precision === 10) {
$precision = DateTimeDefaultPrecision::get();
}
$type = parent::getSQLDeclaration($fieldDeclaration, $platform); $type = parent::getSQLDeclaration($fieldDeclaration, $platform);
@ -63,10 +61,25 @@ trait CarbonTypeConverter
/** /**
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return T|null
*/ */
public function convertToPHPValue($value, AbstractPlatform $platform) public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if ($value === null) {
return $value;
}
if ($value instanceof DateTimeInterface) {
return $value->format('Y-m-d H:i:s.u');
}
throw InvalidType::new(
$value,
static::class,
['null', 'DateTime', 'Carbon']
);
}
private function doConvertToPHPValue(mixed $value)
{ {
$class = $this->getCarbonClassName(); $class = $this->getCarbonClassName();
@ -88,9 +101,9 @@ trait CarbonTypeConverter
} }
if (!$date) { if (!$date) {
throw ConversionException::conversionFailedFormat( throw ValueNotConvertible::new(
$value, $value,
$this->getName(), static::class,
'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()', 'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()',
$error $error
); );
@ -99,25 +112,20 @@ trait CarbonTypeConverter
return $date; return $date;
} }
/** private function getMaximumPrecision(AbstractPlatform $platform): int
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return string|null
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{ {
if ($value === null) { if ($platform instanceof DB2Platform) {
return $value; return 12;
} }
if ($value instanceof DateTimeInterface) { if ($platform instanceof OraclePlatform) {
return $value->format('Y-m-d H:i:s.u'); return 9;
} }
throw ConversionException::conversionFailedInvalidType( if ($platform instanceof SQLServerPlatform || $platform instanceof SQLitePlatform) {
$value, return 3;
$this->getName(), }
['null', 'DateTime', 'Carbon']
); return 6;
} }
} }

View File

@ -1,13 +1,6 @@
<?php <?php
/** declare(strict_types=1);
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Carbon\Doctrine; namespace Carbon\Doctrine;

View File

@ -1,12 +1,12 @@
<?php <?php
/** declare(strict_types=1);
* Thanks to https://github.com/flaushi for his suggestion:
* https://github.com/doctrine/dbal/issues/2873#issuecomment-534956358
*/
namespace Carbon\Doctrine; namespace Carbon\Doctrine;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use DateTimeImmutable;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\VarDateTimeImmutableType; use Doctrine\DBAL\Types\VarDateTimeImmutableType;
class DateTimeImmutableType extends VarDateTimeImmutableType implements CarbonDoctrineType class DateTimeImmutableType extends VarDateTimeImmutableType implements CarbonDoctrineType
@ -14,6 +14,14 @@ class DateTimeImmutableType extends VarDateTimeImmutableType implements CarbonDo
/** @use CarbonTypeConverter<CarbonImmutable> */ /** @use CarbonTypeConverter<CarbonImmutable> */
use CarbonTypeConverter; use CarbonTypeConverter;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonImmutable
{
return $this->doConvertToPHPValue($value);
}
/** /**
* @return class-string<CarbonImmutable> * @return class-string<CarbonImmutable>
*/ */

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
use Carbon\Carbon;
use DateTime;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\VarDateTimeType;
class DateTimeType extends VarDateTimeType implements CarbonDoctrineType
{
/** @use CarbonTypeConverter<Carbon> */
use CarbonTypeConverter;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Carbon
{
return $this->doConvertToPHPValue($value);
}
}

View File

@ -13,9 +13,4 @@ return array(
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
'u2flib_server\\Error' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\RegisterRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\Registration' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\SignRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\U2F' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
); );

View File

@ -6,8 +6,12 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',

View File

@ -15,17 +15,27 @@ return array(
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
'Stevenmaguire\\OAuth2\\Client\\' => array($vendorDir . '/stevenmaguire/oauth2-keycloak/src'),
'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'), 'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
'PhpMimeMailParser\\' => array($vendorDir . '/php-mime-mail-parser/php-mime-mail-parser/src'), 'PhpMimeMailParser\\' => array($vendorDir . '/php-mime-mail-parser/php-mime-mail-parser/src'),
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'), 'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'),
'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'), 'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'),
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src'),
'LdapRecord\\' => array($vendorDir . '/directorytree/ldaprecord/src'), 'LdapRecord\\' => array($vendorDir . '/directorytree/ldaprecord/src'),
'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'), 'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
'Html2Text\\' => array($vendorDir . '/soundasleep/html2text/src'), 'Html2Text\\' => array($vendorDir . '/soundasleep/html2text/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
'Ddeboer\\Imap\\' => array($vendorDir . '/ddeboer/imap/src'), 'Ddeboer\\Imap\\' => array($vendorDir . '/ddeboer/imap/src'),
'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'),
'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'), 'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'),
); );

View File

@ -7,8 +7,12 @@ namespace Composer\Autoload;
class ComposerStaticInit873464e4bd965a3168f133248b1b218b class ComposerStaticInit873464e4bd965a3168f133248b1b218b
{ {
public static $files = array ( public static $files = array (
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
@ -24,12 +28,12 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
); );
public static $prefixLengthsPsr4 = array ( public static $prefixLengthsPsr4 = array (
'T' => 'T' =>
array ( array (
'Twig\\' => 5, 'Twig\\' => 5,
'Tightenco\\Collect\\' => 18, 'Tightenco\\Collect\\' => 18,
), ),
'S' => 'S' =>
array ( array (
'Symfony\\Polyfill\\Php81\\' => 23, 'Symfony\\Polyfill\\Php81\\' => 23,
'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23,
@ -38,141 +42,198 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
'Symfony\\Contracts\\Translation\\' => 30, 'Symfony\\Contracts\\Translation\\' => 30,
'Symfony\\Component\\VarDumper\\' => 28, 'Symfony\\Component\\VarDumper\\' => 28,
'Symfony\\Component\\Translation\\' => 30, 'Symfony\\Component\\Translation\\' => 30,
'Stevenmaguire\\OAuth2\\Client\\' => 28,
), ),
'R' => 'R' =>
array ( array (
'RobThree\\Auth\\' => 14, 'RobThree\\Auth\\' => 14,
), ),
'P' => 'P' =>
array ( array (
'Psr\\SimpleCache\\' => 16, 'Psr\\SimpleCache\\' => 16,
'Psr\\Log\\' => 8, 'Psr\\Log\\' => 8,
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
'Psr\\Container\\' => 14, 'Psr\\Container\\' => 14,
'Psr\\Clock\\' => 10,
'PhpMimeMailParser\\' => 18, 'PhpMimeMailParser\\' => 18,
'PHPMailer\\PHPMailer\\' => 20, 'PHPMailer\\PHPMailer\\' => 20,
), ),
'M' => 'M' =>
array ( array (
'MatthiasMullie\\PathConverter\\' => 29, 'MatthiasMullie\\PathConverter\\' => 29,
'MatthiasMullie\\Minify\\' => 22, 'MatthiasMullie\\Minify\\' => 22,
), ),
'L' => 'L' =>
array ( array (
'League\\OAuth2\\Client\\' => 21,
'LdapRecord\\' => 11, 'LdapRecord\\' => 11,
), ),
'I' => 'I' =>
array ( array (
'Illuminate\\Contracts\\' => 21, 'Illuminate\\Contracts\\' => 21,
), ),
'H' => 'H' =>
array ( array (
'Html2Text\\' => 10, 'Html2Text\\' => 10,
), ),
'D' => 'G' =>
array (
'GuzzleHttp\\Psr7\\' => 16,
'GuzzleHttp\\Promise\\' => 19,
'GuzzleHttp\\' => 11,
),
'F' =>
array (
'Firebase\\JWT\\' => 13,
),
'D' =>
array ( array (
'Ddeboer\\Imap\\' => 13, 'Ddeboer\\Imap\\' => 13,
), ),
'C' => 'C' =>
array ( array (
'Carbon\\Doctrine\\' => 16,
'Carbon\\' => 7, 'Carbon\\' => 7,
), ),
); );
public static $prefixDirsPsr4 = array ( public static $prefixDirsPsr4 = array (
'Twig\\' => 'Twig\\' =>
array ( array (
0 => __DIR__ . '/..' . '/twig/twig/src', 0 => __DIR__ . '/..' . '/twig/twig/src',
), ),
'Tightenco\\Collect\\' => 'Tightenco\\Collect\\' =>
array ( array (
0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect', 0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect',
), ),
'Symfony\\Polyfill\\Php81\\' => 'Symfony\\Polyfill\\Php81\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php81', 0 => __DIR__ . '/..' . '/symfony/polyfill-php81',
), ),
'Symfony\\Polyfill\\Php80\\' => 'Symfony\\Polyfill\\Php80\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80', 0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
), ),
'Symfony\\Polyfill\\Mbstring\\' => 'Symfony\\Polyfill\\Mbstring\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
), ),
'Symfony\\Polyfill\\Ctype\\' => 'Symfony\\Polyfill\\Ctype\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
), ),
'Symfony\\Contracts\\Translation\\' => 'Symfony\\Contracts\\Translation\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/translation-contracts', 0 => __DIR__ . '/..' . '/symfony/translation-contracts',
), ),
'Symfony\\Component\\VarDumper\\' => 'Symfony\\Component\\VarDumper\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/var-dumper', 0 => __DIR__ . '/..' . '/symfony/var-dumper',
), ),
'Symfony\\Component\\Translation\\' => 'Symfony\\Component\\Translation\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/translation', 0 => __DIR__ . '/..' . '/symfony/translation',
), ),
'RobThree\\Auth\\' => 'Stevenmaguire\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/stevenmaguire/oauth2-keycloak/src',
),
'RobThree\\Auth\\' =>
array ( array (
0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib', 0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib',
), ),
'Psr\\SimpleCache\\' => 'Psr\\SimpleCache\\' =>
array ( array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src', 0 => __DIR__ . '/..' . '/psr/simple-cache/src',
), ),
'Psr\\Log\\' => 'Psr\\Log\\' =>
array ( array (
0 => __DIR__ . '/..' . '/psr/log/src', 0 => __DIR__ . '/..' . '/psr/log/src',
), ),
'Psr\\Container\\' => 'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Http\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-client/src',
),
'Psr\\Container\\' =>
array ( array (
0 => __DIR__ . '/..' . '/psr/container/src', 0 => __DIR__ . '/..' . '/psr/container/src',
), ),
'PhpMimeMailParser\\' => 'Psr\\Clock\\' =>
array (
0 => __DIR__ . '/..' . '/psr/clock/src',
),
'PhpMimeMailParser\\' =>
array ( array (
0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src', 0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src',
), ),
'PHPMailer\\PHPMailer\\' => 'PHPMailer\\PHPMailer\\' =>
array ( array (
0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
), ),
'MatthiasMullie\\PathConverter\\' => 'MatthiasMullie\\PathConverter\\' =>
array ( array (
0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src', 0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src',
), ),
'MatthiasMullie\\Minify\\' => 'MatthiasMullie\\Minify\\' =>
array ( array (
0 => __DIR__ . '/..' . '/matthiasmullie/minify/src', 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src',
), ),
'LdapRecord\\' => 'League\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/league/oauth2-client/src',
),
'LdapRecord\\' =>
array ( array (
0 => __DIR__ . '/..' . '/directorytree/ldaprecord/src', 0 => __DIR__ . '/..' . '/directorytree/ldaprecord/src',
), ),
'Illuminate\\Contracts\\' => 'Illuminate\\Contracts\\' =>
array ( array (
0 => __DIR__ . '/..' . '/illuminate/contracts', 0 => __DIR__ . '/..' . '/illuminate/contracts',
), ),
'Html2Text\\' => 'Html2Text\\' =>
array ( array (
0 => __DIR__ . '/..' . '/soundasleep/html2text/src', 0 => __DIR__ . '/..' . '/soundasleep/html2text/src',
), ),
'Ddeboer\\Imap\\' => 'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
),
'GuzzleHttp\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
),
'GuzzleHttp\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
),
'Firebase\\JWT\\' =>
array (
0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
),
'Ddeboer\\Imap\\' =>
array ( array (
0 => __DIR__ . '/..' . '/ddeboer/imap/src', 0 => __DIR__ . '/..' . '/ddeboer/imap/src',
), ),
'Carbon\\' => 'Carbon\\Doctrine\\' =>
array (
0 => __DIR__ . '/..' . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine',
),
'Carbon\\' =>
array ( array (
0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon', 0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon',
), ),
); );
public static $prefixesPsr0 = array ( public static $prefixesPsr0 = array (
'O' => 'O' =>
array ( array (
'OAuth2' => 'OAuth2' =>
array ( array (
0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src', 0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src',
), ),
@ -187,11 +248,6 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
'u2flib_server\\Error' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\RegisterRequest' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\Registration' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\SignRequest' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\U2F' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
); );
public static function getInitializer(ClassLoader $loader) public static function getInitializer(ClassLoader $loader)

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'carbonphp/carbon-doctrine-types' => array(
'pretty_version' => '3.2.0',
'version' => '3.2.0.0',
'reference' => '18ba5ddfec8976260ead6e866180bd5d2f71aa1d',
'type' => 'library',
'install_path' => __DIR__ . '/../carbonphp/carbon-doctrine-types',
'aliases' => array(),
'dev_requirement' => false,
),
'ddeboer/imap' => array( 'ddeboer/imap' => array(
'pretty_version' => '1.13.1', 'pretty_version' => '1.13.1',
'version' => '1.13.1.0', 'version' => '1.13.1.0',
@ -38,9 +47,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'directorytree/ldaprecord' => array( 'directorytree/ldaprecord' => array(
'pretty_version' => 'v2.10.1', 'pretty_version' => 'v2.20.5',
'version' => '2.10.1.0', 'version' => '2.20.5.0',
'reference' => 'bf512d9af7a7b0e2ed7a666ab29cefdd027bee88', 'reference' => '5bd0a5a9d257cf1049ae83055dbba4c3479ddf16',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../directorytree/ldaprecord', 'install_path' => __DIR__ . '/../directorytree/ldaprecord',
'aliases' => array(), 'aliases' => array(),
@ -52,15 +61,60 @@
0 => '*', 0 => '*',
), ),
), ),
'firebase/php-jwt' => array(
'pretty_version' => 'v6.5.0',
'version' => '6.5.0.0',
'reference' => 'e94e7353302b0c11ec3cfff7180cd0b1743975d2',
'type' => 'library',
'install_path' => __DIR__ . '/../firebase/php-jwt',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/guzzle' => array(
'pretty_version' => '7.5.0',
'version' => '7.5.0.0',
'reference' => 'b50a2a1251152e43f6a37f0fa053e730a67d25ba',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/promises' => array(
'pretty_version' => '1.5.2',
'version' => '1.5.2.0',
'reference' => 'b94b2807d85443f9719887892882d0329d1e2598',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '2.4.5',
'version' => '2.4.5.0',
'reference' => '0454e12ef0cd597ccd2adb036f7bda4e7fface66',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'illuminate/contracts' => array( 'illuminate/contracts' => array(
'pretty_version' => 'v9.3.0', 'pretty_version' => 'v10.44.0',
'version' => '9.3.0.0', 'version' => '10.44.0.0',
'reference' => 'bf4b3c254c49d28157645d01e4883b5951b1e1d0', 'reference' => '8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/contracts', 'install_path' => __DIR__ . '/../illuminate/contracts',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'league/oauth2-client' => array(
'pretty_version' => '2.7.0',
'version' => '2.7.0.0',
'reference' => '160d6274b03562ebeb55ed18399281d8118b76c8',
'type' => 'library',
'install_path' => __DIR__ . '/../league/oauth2-client',
'aliases' => array(),
'dev_requirement' => false,
),
'matthiasmullie/minify' => array( 'matthiasmullie/minify' => array(
'pretty_version' => '1.3.66', 'pretty_version' => '1.3.66',
'version' => '1.3.66.0', 'version' => '1.3.66.0',
@ -95,9 +149,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'nesbot/carbon' => array( 'nesbot/carbon' => array(
'pretty_version' => '2.57.0', 'pretty_version' => '2.72.3',
'version' => '2.57.0.0', 'version' => '2.72.3.0',
'reference' => '4a54375c21eea4811dbd1149fe6b246517554e78', 'reference' => '0c6fd108360c562f6e4fd1dedb8233b423e91c83',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../nesbot/carbon', 'install_path' => __DIR__ . '/../nesbot/carbon',
'aliases' => array(), 'aliases' => array(),
@ -130,6 +184,21 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'psr/clock' => array(
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/clock',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/clock-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/container' => array( 'psr/container' => array(
'pretty_version' => '2.0.2', 'pretty_version' => '2.0.2',
'version' => '2.0.2.0', 'version' => '2.0.2.0',
@ -139,6 +208,51 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'psr/http-client' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-factory' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-message' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/log' => array( 'psr/log' => array(
'pretty_version' => '3.0.0', 'pretty_version' => '3.0.0',
'version' => '3.0.0.0', 'version' => '3.0.0.0',
@ -157,6 +271,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
'aliases' => array(),
'dev_requirement' => false,
),
'robthree/twofactorauth' => array( 'robthree/twofactorauth' => array(
'pretty_version' => '1.8.1', 'pretty_version' => '1.8.1',
'version' => '1.8.1.0', 'version' => '1.8.1.0',
@ -175,6 +298,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'stevenmaguire/oauth2-keycloak' => array(
'pretty_version' => '4.0.0',
'version' => '4.0.0.0',
'reference' => '05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d',
'type' => 'library',
'install_path' => __DIR__ . '/../stevenmaguire/oauth2-keycloak',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/deprecation-contracts' => array( 'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.5.0', 'pretty_version' => 'v3.5.0',
'version' => '3.5.0.0', 'version' => '3.5.0.0',
@ -194,18 +326,18 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-mbstring' => array( 'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.24.0', 'pretty_version' => 'v1.29.0',
'version' => '1.24.0.0', 'version' => '1.29.0.0',
'reference' => '0abb51d2f102e00a4eefcf46ba7fec406d245825', 'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-php80' => array( 'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.24.0', 'pretty_version' => 'v1.29.0',
'version' => '1.24.0.0', 'version' => '1.29.0.0',
'reference' => '57b712b08eddb97c762a8caa32c84e037892d2e9', 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(), 'aliases' => array(),
@ -221,18 +353,18 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/translation' => array( 'symfony/translation' => array(
'pretty_version' => 'v6.0.5', 'pretty_version' => 'v6.4.3',
'version' => '6.0.5.0', 'version' => '6.4.3.0',
'reference' => 'e69501c71107cc3146b32aaa45f4edd0c3427875', 'reference' => '637c51191b6b184184bbf98937702bcf554f7d04',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation', 'install_path' => __DIR__ . '/../symfony/translation',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/translation-contracts' => array( 'symfony/translation-contracts' => array(
'pretty_version' => 'v3.0.0', 'pretty_version' => 'v3.4.1',
'version' => '3.0.0.0', 'version' => '3.4.1.0',
'reference' => '1b6ea5a7442af5a12dba3dbd6d71034b5b234e77', 'reference' => '06450585bf65e978026bda220cdebca3f867fde7',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation-contracts', 'install_path' => __DIR__ . '/../symfony/translation-contracts',
'aliases' => array(), 'aliases' => array(),
@ -245,18 +377,18 @@
), ),
), ),
'symfony/var-dumper' => array( 'symfony/var-dumper' => array(
'pretty_version' => 'v6.0.5', 'pretty_version' => 'v6.4.3',
'version' => '6.0.5.0', 'version' => '6.4.3.0',
'reference' => '60d6a756d5f485df5e6e40b337334848f79f61ce', 'reference' => '0435a08f69125535336177c29d56af3abc1f69da',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/var-dumper', 'install_path' => __DIR__ . '/../symfony/var-dumper',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'tightenco/collect' => array( 'tightenco/collect' => array(
'pretty_version' => 'v8.83.2', 'pretty_version' => 'v9.52.7',
'version' => '8.83.2.0', 'version' => '9.52.7.0',
'reference' => 'd9c66d586ec2d216d8a31283d73f8df1400cc722', 'reference' => 'b15143cd11fe01a700fcc449df61adc64452fa6d',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../tightenco/collect', 'install_path' => __DIR__ . '/../tightenco/collect',
'aliases' => array(), 'aliases' => array(),
@ -271,14 +403,5 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'yubico/u2flib-server' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => '55d813acf68212ad2cadecde07551600d6971939',
'type' => 'library',
'install_path' => __DIR__ . '/../yubico/u2flib-server',
'aliases' => array(),
'dev_requirement' => false,
),
), ),
); );

View File

@ -7,7 +7,10 @@ assignees: ''
--- ---
<!-- Please update the below information with your environment. --> <!--
Please update the below information with your environment.
Issues filed without the below information will be closed.
-->
**Environment:** **Environment:**
- LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA] - LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA]
- PHP Version: [e.g. 7.3 / 7.4 / 8.0] - PHP Version: [e.g. 7.3 / 7.4 / 8.0]

View File

@ -11,7 +11,10 @@ assignees: ''
<!-- https://github.com/sponsors/stevebauman --> <!-- https://github.com/sponsors/stevebauman -->
<!-- Thank you for your understanding. --> <!-- Thank you for your understanding. -->
<!-- Please update the below information with your environment. --> <!--
Please update the below information with your environment.
Issues filed without the below information will be closed.
-->
**Environment:** **Environment:**
- LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA] - LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA]
- PHP Version: [e.g. 7.3 / 7.4 / 8.0] - PHP Version: [e.g. 7.3 / 7.4 / 8.0]

View File

@ -0,0 +1,62 @@
name: run-integration-tests
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
run-tests:
runs-on: ${{ matrix.os }}
services:
ldap:
image: osixia/openldap:1.4.0
env:
LDAP_TLS_VERIFY_CLIENT: try
LDAP_OPENLDAP_UID: 1000
LDAP_OPENLDAP_GID: 1000
LDAP_ORGANISATION: Local
LDAP_DOMAIN: local.com
LDAP_ADMIN_PASSWORD: secret
ports:
- 389:389
- 636:636
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.1, 8.0, 7.4]
name: ${{ matrix.os }} - P${{ matrix.php }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
- name: Set ldap.conf file permissions
run: sudo chown -R $USER:$USER /etc/ldap/ldap.conf
- name: Create ldap.conf file disabling TLS verification
run: sudo echo "TLS_REQCERT never" > "/etc/ldap/ldap.conf"
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ldap, json
coverage: none
- name: Install dependencies
run: composer update --prefer-dist --no-interaction
- name: Execute tests
run: vendor/bin/phpunit --testsuite Integration

View File

@ -9,20 +9,20 @@ on:
jobs: jobs:
run-tests: run-tests:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} - P${{ matrix.php }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
php: [8.1, 8.0, 7.4, 7.3] php: [8.1, 8.0, 7.4, 7.3]
name: ${{ matrix.os }} - P${{ matrix.php }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: ~/.composer/cache/files path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
@ -38,41 +38,4 @@ jobs:
run: composer update --prefer-dist --no-interaction run: composer update --prefer-dist --no-interaction
- name: Execute tests - name: Execute tests
run: vendor/bin/phpunit run: vendor/bin/phpunit --testsuite Unit
run-analysis:
runs-on: ${{ matrix.os }}
name: Static code analysis (PHP ${{ matrix.php }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.0]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ldap, json
coverage: none
tools: psalm
- name: Validate composer.json
run: composer validate
- name: Install dependencies
run: composer update --prefer-dist --no-interaction
- name: Run Psalm
run: psalm

View File

@ -1,8 +1 @@
preset: laravel preset: laravel
enabled:
- phpdoc_align
- phpdoc_separation
- unalign_double_arrow
disabled:
- laravel_phpdoc_alignment
- laravel_phpdoc_separation

View File

@ -32,11 +32,12 @@
"php": ">=7.3", "php": ">=7.3",
"ext-ldap": "*", "ext-ldap": "*",
"ext-json": "*", "ext-json": "*",
"psr/log": "*", "psr/log": "^1.0|^2.0|^3.0",
"psr/simple-cache": "^1.0|^2.0", "psr/simple-cache": "^1.0|^2.0",
"nesbot/carbon": "^1.0|^2.0", "nesbot/carbon": "^1.0|^2.0",
"tightenco/collect": "^5.6|^6.0|^7.0|^8.0", "tightenco/collect": "^5.6|^6.0|^7.0|^8.0|^9.0",
"illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0" "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0",
"symfony/polyfill-php80": "^1.25"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.0", "phpunit/phpunit": "^9.0",

View File

@ -0,0 +1,35 @@
version: '3'
services:
ldap:
image: osixia/openldap:1.4.0
container_name: ldap
restart: always
hostname: local.com
environment:
LDAP_TLS_VERIFY_CLIENT: try
LDAP_OPENLDAP_UID: 1000
LDAP_OPENLDAP_GID: 1000
LDAP_ORGANISATION: Local
LDAP_DOMAIN : local.com
LDAP_ADMIN_PASSWORD: secret
ports:
- "389:389"
- "636:636"
networks:
- local
ldapadmin:
image: osixia/phpldapadmin:0.9.0
container_name: ldapadmin
environment:
PHPLDAPADMIN_LDAP_HOSTS: ldap
restart: always
ports:
- "6443:443"
networks:
- local
networks:
local:
driver: bridge

View File

@ -10,8 +10,11 @@
stopOnFailure="false" stopOnFailure="false"
> >
<testsuites> <testsuites>
<testsuite name="LdapRecord Test Suite"> <testsuite name="Unit">
<directory suffix="Test.php">./tests/</directory> <directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory suffix="Test.php">./tests/Integration</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
</phpunit> </phpunit>

View File

@ -1,15 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@ -6,7 +6,7 @@
<p align="center"> <p align="center">
<a href="https://github.com/DirectoryTree/LdapRecord/actions"> <a href="https://github.com/DirectoryTree/LdapRecord/actions">
<img src="https://img.shields.io/github/workflow/status/directorytree/ldaprecord/run-tests.svg?style=flat-square"> <img src="https://img.shields.io/github/actions/workflow/status/directorytree/ldaprecord/run-tests.yml?branch=master&style=flat-square">
</a> </a>
<a href="https://scrutinizer-ci.com/g/DirectoryTree/LdapRecord/?branch=master"> <a href="https://scrutinizer-ci.com/g/DirectoryTree/LdapRecord/?branch=master">
<img src="https://img.shields.io/scrutinizer/g/DirectoryTree/LdapRecord/master.svg?style=flat-square"/> <img src="https://img.shields.io/scrutinizer/g/DirectoryTree/LdapRecord/master.svg?style=flat-square"/>
@ -45,7 +45,7 @@
⏲ **Up and Running Fast** ⏲ **Up and Running Fast**
Connect to your LDAP servers and start running queries at lightning speed. Connect to your LDAP servers and start running queries in a matter of minutes.
💡 **Fluent Filter Builder** 💡 **Fluent Filter Builder**

View File

@ -30,9 +30,9 @@ abstract class Event
/** /**
* Constructor. * Constructor.
* *
* @param LdapInterface $connection * @param LdapInterface $connection
* @param string $username * @param string $username
* @param string $password * @param string $password
*/ */
public function __construct(LdapInterface $connection, $username, $password) public function __construct(LdapInterface $connection, $username, $password)
{ {

View File

@ -38,8 +38,8 @@ class Guard
/** /**
* Constructor. * Constructor.
* *
* @param LdapInterface $connection * @param LdapInterface $connection
* @param DomainConfiguration $configuration * @param DomainConfiguration $configuration
*/ */
public function __construct(LdapInterface $connection, DomainConfiguration $configuration) public function __construct(LdapInterface $connection, DomainConfiguration $configuration)
{ {
@ -50,10 +50,9 @@ class Guard
/** /**
* Attempt binding a user to the LDAP server. * Attempt binding a user to the LDAP server.
* *
* @param string $username * @param string $username
* @param string $password * @param string $password
* @param bool $stayBound * @param bool $stayBound
*
* @return bool * @return bool
* *
* @throws UsernameRequiredException * @throws UsernameRequiredException
@ -90,8 +89,8 @@ class Guard
/** /**
* Attempt binding a user to the LDAP server. Supports anonymous binding. * Attempt binding a user to the LDAP server. Supports anonymous binding.
* *
* @param string|null $username * @param string|null $username
* @param string|null $password * @param string|null $password
* *
* @throws BindException * @throws BindException
* @throws \LdapRecord\ConnectionException * @throws \LdapRecord\ConnectionException
@ -148,8 +147,7 @@ class Guard
/** /**
* Set the event dispatcher instance. * Set the event dispatcher instance.
* *
* @param DispatcherInterface $dispatcher * @param DispatcherInterface $dispatcher
*
* @return void * @return void
*/ */
public function setDispatcher(DispatcherInterface $dispatcher) public function setDispatcher(DispatcherInterface $dispatcher)
@ -160,9 +158,8 @@ class Guard
/** /**
* Fire the attempting event. * Fire the attempting event.
* *
* @param string $username * @param string $username
* @param string $password * @param string $password
*
* @return void * @return void
*/ */
protected function fireAttemptingEvent($username, $password) protected function fireAttemptingEvent($username, $password)
@ -175,9 +172,8 @@ class Guard
/** /**
* Fire the passed event. * Fire the passed event.
* *
* @param string $username * @param string $username
* @param string $password * @param string $password
*
* @return void * @return void
*/ */
protected function firePassedEvent($username, $password) protected function firePassedEvent($username, $password)
@ -190,9 +186,8 @@ class Guard
/** /**
* Fire the failed event. * Fire the failed event.
* *
* @param string $username * @param string $username
* @param string $password * @param string $password
*
* @return void * @return void
*/ */
protected function fireFailedEvent($username, $password) protected function fireFailedEvent($username, $password)
@ -205,9 +200,8 @@ class Guard
/** /**
* Fire the binding event. * Fire the binding event.
* *
* @param string $username * @param string $username
* @param string $password * @param string $password
*
* @return void * @return void
*/ */
protected function fireBindingEvent($username, $password) protected function fireBindingEvent($username, $password)
@ -220,9 +214,8 @@ class Guard
/** /**
* Fire the bound event. * Fire the bound event.
* *
* @param string $username * @param string $username
* @param string $password * @param string $password
*
* @return void * @return void
*/ */
protected function fireBoundEvent($username, $password) protected function fireBoundEvent($username, $password)

View File

@ -58,7 +58,7 @@ class DomainConfiguration
/** /**
* Constructor. * Constructor.
* *
* @param array $options * @param array $options
* *
* @throws ConfigurationException When an option value given is an invalid type. * @throws ConfigurationException When an option value given is an invalid type.
*/ */
@ -74,9 +74,8 @@ class DomainConfiguration
/** /**
* Extend the configuration with a custom option, or override an existing. * Extend the configuration with a custom option, or override an existing.
* *
* @param string $option * @param string $option
* @param mixed $default * @param mixed $default
*
* @return void * @return void
*/ */
public static function extend($option, $default = null) public static function extend($option, $default = null)
@ -107,8 +106,8 @@ class DomainConfiguration
/** /**
* Set a configuration option. * Set a configuration option.
* *
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
* *
* @throws ConfigurationException When an option value given is an invalid type. * @throws ConfigurationException When an option value given is an invalid type.
*/ */
@ -122,8 +121,7 @@ class DomainConfiguration
/** /**
* Returns the value for the specified configuration options. * Returns the value for the specified configuration options.
* *
* @param string $key * @param string $key
*
* @return mixed * @return mixed
* *
* @throws ConfigurationException When the option specified does not exist. * @throws ConfigurationException When the option specified does not exist.
@ -140,8 +138,7 @@ class DomainConfiguration
/** /**
* Checks if a configuration option exists. * Checks if a configuration option exists.
* *
* @param string $key * @param string $key
*
* @return bool * @return bool
*/ */
public function has($key) public function has($key)
@ -152,9 +149,8 @@ class DomainConfiguration
/** /**
* Validate the configuration option. * Validate the configuration option.
* *
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
*
* @return bool * @return bool
* *
* @throws ConfigurationException When an option value given is an invalid type. * @throws ConfigurationException When an option value given is an invalid type.

View File

@ -30,8 +30,8 @@ abstract class Validator
/** /**
* Constructor. * Constructor.
* *
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
*/ */
public function __construct($key, $value) public function __construct($key, $value)
{ {

View File

@ -88,8 +88,8 @@ class Connection
/** /**
* Constructor. * Constructor.
* *
* @param array $config * @param array|DomainConfiguration $config
* @param LdapInterface|null $ldap * @param LdapInterface|null $ldap
*/ */
public function __construct($config = [], LdapInterface $ldap = null) public function __construct($config = [], LdapInterface $ldap = null)
{ {
@ -109,15 +109,18 @@ class Connection
/** /**
* Set the connection configuration. * Set the connection configuration.
* *
* @param array $config * @param array|DomainConfiguration $config
*
* @return $this * @return $this
* *
* @throws Configuration\ConfigurationException * @throws Configuration\ConfigurationException
*/ */
public function setConfiguration($config = []) public function setConfiguration($config = [])
{ {
$this->configuration = new DomainConfiguration($config); if (! $config instanceof DomainConfiguration) {
$config = new DomainConfiguration($config);
}
$this->configuration = $config;
$this->hosts = $this->configuration->get('hosts'); $this->hosts = $this->configuration->get('hosts');
@ -129,8 +132,7 @@ class Connection
/** /**
* Set the LDAP connection. * Set the LDAP connection.
* *
* @param LdapInterface $ldap * @param LdapInterface $ldap
*
* @return $this * @return $this
*/ */
public function setLdapConnection(LdapInterface $ldap) public function setLdapConnection(LdapInterface $ldap)
@ -143,8 +145,7 @@ class Connection
/** /**
* Set the event dispatcher. * Set the event dispatcher.
* *
* @param DispatcherInterface $dispatcher * @param DispatcherInterface $dispatcher
*
* @return $this * @return $this
*/ */
public function setDispatcher(DispatcherInterface $dispatcher) public function setDispatcher(DispatcherInterface $dispatcher)
@ -192,8 +193,7 @@ class Connection
/** /**
* Set the cache store. * Set the cache store.
* *
* @param CacheInterface $store * @param CacheInterface $store
*
* @return $this * @return $this
*/ */
public function setCache(CacheInterface $store) public function setCache(CacheInterface $store)
@ -238,9 +238,8 @@ class Connection
* *
* If no username or password is specified, then the configured credentials are used. * If no username or password is specified, then the configured credentials are used.
* *
* @param string|null $username * @param string|null $username
* @param string|null $password * @param string|null $password
*
* @return Connection * @return Connection
* *
* @throws Auth\BindException * @throws Auth\BindException
@ -298,6 +297,16 @@ class Connection
$this->initialize(); $this->initialize();
} }
/**
* Clone the connection.
*
* @return static
*/
public function replicate()
{
return new static($this->configuration, new $this->ldap);
}
/** /**
* Disconnect from the LDAP server. * Disconnect from the LDAP server.
* *
@ -311,8 +320,7 @@ class Connection
/** /**
* Dispatch an event. * Dispatch an event.
* *
* @param object $event * @param object $event
*
* @return void * @return void
*/ */
public function dispatch($event) public function dispatch($event)
@ -335,8 +343,7 @@ class Connection
/** /**
* Perform the operation on the LDAP connection. * Perform the operation on the LDAP connection.
* *
* @param Closure $operation * @param Closure $operation
*
* @return mixed * @return mixed
*/ */
public function run(Closure $operation) public function run(Closure $operation)
@ -359,11 +366,27 @@ class Connection
} }
} }
/**
* Perform the operation on an isolated LDAP connection.
*
* @param Closure $operation
* @return mixed
*/
public function isolate(Closure $operation)
{
$connection = $this->replicate();
try {
return $operation($connection);
} finally {
$connection->disconnect();
}
}
/** /**
* Attempt to get an exception for the cause of failure. * Attempt to get an exception for the cause of failure.
* *
* @param LdapRecordException $e * @param LdapRecordException $e
*
* @return mixed * @return mixed
*/ */
protected function getExceptionForCauseOfFailure(LdapRecordException $e) protected function getExceptionForCauseOfFailure(LdapRecordException $e)
@ -383,8 +406,7 @@ class Connection
/** /**
* Run the operation callback on the current LDAP connection. * Run the operation callback on the current LDAP connection.
* *
* @param Closure $operation * @param Closure $operation
*
* @return mixed * @return mixed
* *
* @throws LdapRecordException * @throws LdapRecordException
@ -439,9 +461,8 @@ class Connection
/** /**
* Attempt to retry an LDAP operation if due to a lost connection. * Attempt to retry an LDAP operation if due to a lost connection.
* *
* @param LdapRecordException $e * @param LdapRecordException $e
* @param Closure $operation * @param Closure $operation
*
* @return mixed * @return mixed
* *
* @throws LdapRecordException * @throws LdapRecordException
@ -461,8 +482,7 @@ class Connection
/** /**
* Retry the operation on the current host. * Retry the operation on the current host.
* *
* @param Closure $operation * @param Closure $operation
*
* @return mixed * @return mixed
* *
* @throws LdapRecordException * @throws LdapRecordException
@ -483,9 +503,8 @@ class Connection
/** /**
* Attempt the operation again on the next host. * Attempt the operation again on the next host.
* *
* @param LdapRecordException $e * @param LdapRecordException $e
* @param Closure $operation * @param Closure $operation
*
* @return mixed * @return mixed
* *
* @throws LdapRecordException * @throws LdapRecordException

View File

@ -81,9 +81,8 @@ class ConnectionManager
/** /**
* Forward missing method calls onto the instance. * Forward missing method calls onto the instance.
* *
* @param string $method * @param string $method
* @param mixed $args * @param mixed $args
*
* @return mixed * @return mixed
*/ */
public function __call($method, $args) public function __call($method, $args)
@ -104,9 +103,8 @@ class ConnectionManager
/** /**
* Add a new connection. * Add a new connection.
* *
* @param Connection $connection * @param Connection $connection
* @param string|null $name * @param string|null $name
*
* @return $this * @return $this
*/ */
public function add(Connection $connection, $name = null) public function add(Connection $connection, $name = null)
@ -123,8 +121,7 @@ class ConnectionManager
/** /**
* Remove a connection. * Remove a connection.
* *
* @param $name * @param $name
*
* @return $this * @return $this
*/ */
public function remove($name) public function remove($name)
@ -147,8 +144,7 @@ class ConnectionManager
/** /**
* Get a connection by name or return the default. * Get a connection by name or return the default.
* *
* @param string|null $name * @param string|null $name
*
* @return Connection * @return Connection
* *
* @throws ContainerException If the given connection does not exist. * @throws ContainerException If the given connection does not exist.
@ -185,8 +181,7 @@ class ConnectionManager
/** /**
* Checks if the connection exists. * Checks if the connection exists.
* *
* @param string $name * @param string $name
*
* @return bool * @return bool
*/ */
public function exists($name) public function exists($name)
@ -197,8 +192,7 @@ class ConnectionManager
/** /**
* Set the default connection name. * Set the default connection name.
* *
* @param string $name * @param string $name
*
* @return $this * @return $this
*/ */
public function setDefault($name = null) public function setDefault($name = null)
@ -237,8 +231,7 @@ class ConnectionManager
/** /**
* Set the event logger to use. * Set the event logger to use.
* *
* @param LoggerInterface $logger * @param LoggerInterface $logger
*
* @return void * @return void
*/ */
public function setLogger(LoggerInterface $logger) public function setLogger(LoggerInterface $logger)
@ -299,8 +292,7 @@ class ConnectionManager
/** /**
* Set the event dispatcher. * Set the event dispatcher.
* *
* @param DispatcherInterface $dispatcher * @param DispatcherInterface $dispatcher
*
* @return void * @return void
*/ */
public function setDispatcher(DispatcherInterface $dispatcher) public function setDispatcher(DispatcherInterface $dispatcher)

View File

@ -48,9 +48,8 @@ class Container
/** /**
* Forward missing static calls onto the current instance. * Forward missing static calls onto the current instance.
* *
* @param string $method * @param string $method
* @param mixed $args * @param mixed $args
*
* @return mixed * @return mixed
*/ */
public static function __callStatic($method, $args) public static function __callStatic($method, $args)
@ -71,8 +70,7 @@ class Container
/** /**
* Set the container instance. * Set the container instance.
* *
* @param Container|null $container * @param Container|null $container
*
* @return Container|null * @return Container|null
*/ */
public static function setInstance(self $container = null) public static function setInstance(self $container = null)
@ -103,9 +101,8 @@ class Container
/** /**
* Forward missing method calls onto the connection manager. * Forward missing method calls onto the connection manager.
* *
* @param string $method * @param string $method
* @param mixed $args * @param mixed $args
*
* @return mixed * @return mixed
*/ */
public function __call($method, $args) public function __call($method, $args)

View File

@ -28,9 +28,9 @@ class DetailedError
/** /**
* Constructor. * Constructor.
* *
* @param int $errorCode * @param int $errorCode
* @param string $errorMessage * @param string $errorMessage
* @param string $diagnosticMessage * @param string $diagnosticMessage
*/ */
public function __construct($errorCode, $errorMessage, $diagnosticMessage) public function __construct($errorCode, $errorMessage, $diagnosticMessage)
{ {

View File

@ -7,8 +7,7 @@ trait DetectsErrors
/** /**
* Determine if the error was caused by a lost connection. * Determine if the error was caused by a lost connection.
* *
* @param string $error * @param string $error
*
* @return bool * @return bool
*/ */
protected function causedByLostConnection($error) protected function causedByLostConnection($error)
@ -19,8 +18,7 @@ trait DetectsErrors
/** /**
* Determine if the error was caused by lack of pagination support. * Determine if the error was caused by lack of pagination support.
* *
* @param string $error * @param string $error
*
* @return bool * @return bool
*/ */
protected function causedByPaginationSupport($error) protected function causedByPaginationSupport($error)
@ -31,8 +29,7 @@ trait DetectsErrors
/** /**
* Determine if the error was caused by a size limit warning. * Determine if the error was caused by a size limit warning.
* *
* @param $error * @param $error
*
* @return bool * @return bool
*/ */
protected function causedBySizeLimit($error) protected function causedBySizeLimit($error)
@ -43,8 +40,7 @@ trait DetectsErrors
/** /**
* Determine if the error was caused by a "No such object" warning. * Determine if the error was caused by a "No such object" warning.
* *
* @param string $error * @param string $error
*
* @return bool * @return bool
*/ */
protected function causedByNoSuchObject($error) protected function causedByNoSuchObject($error)
@ -55,15 +51,14 @@ trait DetectsErrors
/** /**
* Determine if the error contains the any of the messages. * Determine if the error contains the any of the messages.
* *
* @param string $error * @param string $error
* @param string|array $messages * @param string|array $messages
*
* @return bool * @return bool
*/ */
protected function errorContainsMessage($error, $messages = []) protected function errorContainsMessage($error, $messages = [])
{ {
foreach ((array) $messages as $message) { foreach ((array) $messages as $message) {
if (strpos($error, $message) !== false) { if (str_contains((string) $error, $message)) {
return true; return true;
} }
} }

View File

@ -9,10 +9,9 @@ trait EscapesValues
/** /**
* Prepare a value to be escaped. * Prepare a value to be escaped.
* *
* @param string $value * @param string $value
* @param string $ignore * @param string $ignore
* @param int $flags * @param int $flags
*
* @return EscapedValue * @return EscapedValue
*/ */
public function escape($value, $ignore = '', $flags = 0) public function escape($value, $ignore = '', $flags = 0)

View File

@ -16,7 +16,7 @@ abstract class ConnectionEvent
/** /**
* Constructor. * Constructor.
* *
* @param Connection $connection * @param Connection $connection
*/ */
public function __construct(Connection $connection) public function __construct(Connection $connection)
{ {

View File

@ -46,7 +46,7 @@ class Dispatcher implements DispatcherInterface
public function listen($events, $listener) public function listen($events, $listener)
{ {
foreach ((array) $events as $event) { foreach ((array) $events as $event) {
if (strpos($event, '*') !== false) { if (str_contains((string) $event, '*')) {
$this->setupWildcardListen($event, $listener); $this->setupWildcardListen($event, $listener);
} else { } else {
$this->listeners[$event][] = $this->makeListener($listener); $this->listeners[$event][] = $this->makeListener($listener);
@ -57,9 +57,8 @@ class Dispatcher implements DispatcherInterface
/** /**
* Setup a wildcard listener callback. * Setup a wildcard listener callback.
* *
* @param string $event * @param string $event
* @param mixed $listener * @param mixed $listener
*
* @return void * @return void
*/ */
protected function setupWildcardListen($event, $listener) protected function setupWildcardListen($event, $listener)
@ -134,9 +133,8 @@ class Dispatcher implements DispatcherInterface
/** /**
* Parse the given event and payload and prepare them for dispatching. * Parse the given event and payload and prepare them for dispatching.
* *
* @param mixed $event * @param mixed $event
* @param mixed $payload * @param mixed $payload
*
* @return array * @return array
*/ */
protected function parseEventAndPayload($event, $payload) protected function parseEventAndPayload($event, $payload)
@ -168,8 +166,7 @@ class Dispatcher implements DispatcherInterface
/** /**
* Get the wildcard listeners for the event. * Get the wildcard listeners for the event.
* *
* @param string $eventName * @param string $eventName
*
* @return array * @return array
*/ */
protected function getWildcardListeners($eventName) protected function getWildcardListeners($eventName)
@ -190,9 +187,8 @@ class Dispatcher implements DispatcherInterface
* *
* This function is a direct excerpt from Laravel's Str::is(). * This function is a direct excerpt from Laravel's Str::is().
* *
* @param string $wildcard * @param string $wildcard
* @param string $eventName * @param string $eventName
*
* @return bool * @return bool
*/ */
protected function wildcardContainsEvent($wildcard, $eventName) protected function wildcardContainsEvent($wildcard, $eventName)
@ -229,9 +225,8 @@ class Dispatcher implements DispatcherInterface
/** /**
* Add the listeners for the event's interfaces to the given array. * Add the listeners for the event's interfaces to the given array.
* *
* @param string $eventName * @param string $eventName
* @param array $listeners * @param array $listeners
*
* @return array * @return array
*/ */
protected function addInterfaceListeners($eventName, array $listeners = []) protected function addInterfaceListeners($eventName, array $listeners = [])
@ -250,9 +245,8 @@ class Dispatcher implements DispatcherInterface
/** /**
* Register an event listener with the dispatcher. * Register an event listener with the dispatcher.
* *
* @param \Closure|string $listener * @param \Closure|string $listener
* @param bool $wildcard * @param bool $wildcard
*
* @return \Closure * @return \Closure
*/ */
public function makeListener($listener, $wildcard = false) public function makeListener($listener, $wildcard = false)
@ -273,9 +267,8 @@ class Dispatcher implements DispatcherInterface
/** /**
* Create a class based listener. * Create a class based listener.
* *
* @param string $listener * @param string $listener
* @param bool $wildcard * @param bool $wildcard
*
* @return \Closure * @return \Closure
*/ */
protected function createClassListener($listener, $wildcard = false) protected function createClassListener($listener, $wildcard = false)
@ -295,8 +288,7 @@ class Dispatcher implements DispatcherInterface
/** /**
* Create the class based event callable. * Create the class based event callable.
* *
* @param string $listener * @param string $listener
*
* @return callable * @return callable
*/ */
protected function createClassCallable($listener) protected function createClassCallable($listener)
@ -309,13 +301,12 @@ class Dispatcher implements DispatcherInterface
/** /**
* Parse the class listener into class and method. * Parse the class listener into class and method.
* *
* @param string $listener * @param string $listener
*
* @return array * @return array
*/ */
protected function parseListenerCallback($listener) protected function parseListenerCallback($listener)
{ {
return strpos($listener, '@') !== false return str_contains((string) $listener, '@')
? explode('@', $listener, 2) ? explode('@', $listener, 2)
: [$listener, 'handle']; : [$listener, 'handle'];
} }
@ -325,7 +316,7 @@ class Dispatcher implements DispatcherInterface
*/ */
public function forget($event) public function forget($event)
{ {
if (strpos($event, '*') !== false) { if (str_contains((string) $event, '*')) {
unset($this->wildcards[$event]); unset($this->wildcards[$event]);
} else { } else {
unset($this->listeners[$event]); unset($this->listeners[$event]);

View File

@ -7,9 +7,8 @@ interface DispatcherInterface
/** /**
* Register an event listener with the dispatcher. * Register an event listener with the dispatcher.
* *
* @param string|array $events * @param string|array $events
* @param mixed $listener * @param mixed $listener
*
* @return void * @return void
*/ */
public function listen($events, $listener); public function listen($events, $listener);
@ -17,8 +16,7 @@ interface DispatcherInterface
/** /**
* Determine if a given event has listeners. * Determine if a given event has listeners.
* *
* @param string $eventName * @param string $eventName
*
* @return bool * @return bool
*/ */
public function hasListeners($eventName); public function hasListeners($eventName);
@ -26,9 +24,8 @@ interface DispatcherInterface
/** /**
* Fire an event until the first non-null response is returned. * Fire an event until the first non-null response is returned.
* *
* @param string|object $event * @param string|object $event
* @param mixed $payload * @param mixed $payload
*
* @return array|null * @return array|null
*/ */
public function until($event, $payload = []); public function until($event, $payload = []);
@ -36,10 +33,9 @@ interface DispatcherInterface
/** /**
* Fire an event and call the listeners. * Fire an event and call the listeners.
* *
* @param string|object $event * @param string|object $event
* @param mixed $payload * @param mixed $payload
* @param bool $halt * @param bool $halt
*
* @return mixed * @return mixed
*/ */
public function fire($event, $payload = [], $halt = false); public function fire($event, $payload = [], $halt = false);
@ -47,10 +43,9 @@ interface DispatcherInterface
/** /**
* Fire an event and call the listeners. * Fire an event and call the listeners.
* *
* @param string|object $event * @param string|object $event
* @param mixed $payload * @param mixed $payload
* @param bool $halt * @param bool $halt
*
* @return array|null * @return array|null
*/ */
public function dispatch($event, $payload = [], $halt = false); public function dispatch($event, $payload = [], $halt = false);
@ -58,8 +53,7 @@ interface DispatcherInterface
/** /**
* Get all of the listeners for a given event name. * Get all of the listeners for a given event name.
* *
* @param string $eventName * @param string $eventName
*
* @return array * @return array
*/ */
public function getListeners($eventName); public function getListeners($eventName);
@ -67,8 +61,7 @@ interface DispatcherInterface
/** /**
* Remove a set of listeners from the dispatcher. * Remove a set of listeners from the dispatcher.
* *
* @param string $event * @param string $event
*
* @return void * @return void
*/ */
public function forget($event); public function forget($event);

Some files were not shown because too many files have changed in this diff Show More