diff --git a/bin/build.sh b/bin/build.sh index 0db0604a..73c24436 100755 --- a/bin/build.sh +++ b/bin/build.sh @@ -1,50 +1,52 @@ #!/bin/bash set -e if [[ $1 == "--podman" ]]; then echo "Building with podman" shift podman build docker/swoole/ -t apheleia/swoole podman build docker/base/ -f almalinux9 -t apheleia/almalinux9 podman build --ulimit nofile=65535:65535 docker/webapp -t kolab-webapp \ ${KOLAB_GIT_REMOTE:+"--build-arg=GIT_REMOTE=$KOLAB_GIT_REMOTE"} \ ${KOLAB_GIT_REF:+"--build-arg=GIT_REF=$KOLAB_GIT_REF"} podman build --ulimit nofile=65535:65535 docker/meet -t kolab-meet \ ${KOLAB_GIT_REMOTE:+"--build-arg=GIT_REMOTE=$KOLAB_GIT_REMOTE"} \ ${KOLAB_GIT_REF:+"--build-arg=GIT_REF=$KOLAB_GIT_REF"} podman build docker/postfix -t kolab-postfix podman build docker/imap -t kolab-imap podman build docker/amavis -t kolab-amavis podman build docker/collabora -t kolab-collabora --build-arg=REPOSITORY="https://www.collaboraoffice.com/repos/CollaboraOnline/23.05-CODE/CODE-rpm/" podman build docker/mariadb -t mariadb podman build docker/redis -t redis podman build docker/proxy -t kolab-proxy podman build docker/coturn -t kolab-coturn podman build docker/utils -t kolab-utils podman build docker/fluentbit -t fluentbit + podman build docker/synapse -t synapse + podman build docker/element -t element podman build --ulimit nofile=65535:65535 docker/roundcube -t roundcube \ ${GIT_REMOTE_ROUNDCUBEMAIL:+"--build-arg=GIT_REMOTE_ROUNDCUBEMAIL=$GIT_REMOTE_ROUNDCUBEMAIL"} \ ${GIT_REF_ROUNDCUBEMAIL:+"--build-arg=GIT_REF_ROUNDCUBEMAIL=$GIT_REF_ROUNDCUBEMAIL"} \ ${GIT_REMOTE_ROUNDCUBEMAIL_PLUGINS:+"--build-arg=GIT_REMOTE_ROUNDCUBEMAIL_PLUGINS=$GIT_REMOTE_ROUNDCUBEMAIL_PLUGINS"} \ ${GIT_REF_ROUNDCUBEMAIL_PLUGINS:+"--build-arg=GIT_REF_ROUNDCUBEMAIL_PLUGINS=$GIT_REF_ROUNDCUBEMAIL_PLUGINS"} \ ${GIT_REMOTE_CHWALA:+"--build-arg=GIT_REMOTE_CHWALA=$GIT_REMOTE_CHWALA"} \ ${GIT_REF_CHWALA:+"--build-arg=GIT_REF_CHWALA=$GIT_REF_CHWALA"} \ ${GIT_REMOTE_SYNCROTON:+"--build-arg=GIT_REMOTE_SYNCROTON=$GIT_REMOTE_SYNCROTON"} \ ${GIT_REF_SYNCROTON:+"--build-arg=GIT_REF_SYNCROTON=$GIT_REF_SYNCROTON"} \ ${GIT_REMOTE_AUTOCONF:+"--build-arg=GIT_REMOTE_AUTOCONF=$GIT_REMOTE_AUTOCONF"} \ ${GIT_REF_AUTOCONF:+"--build-arg=GIT_REF_AUTOCONF=$GIT_REF_AUTOCONF"} \ ${GIT_REMOTE_IRONY:+"--build-arg=GIT_REMOTE_IRONY=$GIT_REMOTE_IRONY"} \ ${GIT_REF_IRONY:+"--build-arg=GIT_REF_IRONY=$GIT_REF_IRONY"} \ ${GIT_REMOTE_FREEBUSY:+"--build-arg=GIT_REMOTE_FREEBUSY=$GIT_REMOTE_FREEBUSY"} \ ${GIT_REF_FREEBUSY:+"--build-arg=GIT_REF_FREEBUSY=$GIT_REF_FREEBUSY"} else echo "Building with docker compose" # Workaround because docker-compose doesn't know build dependencies, so we build the dependencies first # (It does respect depends_on, but we don't actually want the dependencies started, so....) docker compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.build.yml build $@ swoole almalinux8 almalinux9 docker compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.build.yml build $@ webapp docker compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.build.yml build $@ fi diff --git a/ci/testctl b/ci/testctl index cdb00804..22ed5055 100755 --- a/ci/testctl +++ b/ci/testctl @@ -1,491 +1,517 @@ #!/bin/bash base_dir="$(dirname $(realpath "$0"))" pushd "${base_dir}" pushd .. set -e set -x PASSPORT_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- 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 bakhie7hJBP7BoOup+dD5NQPlXBQ434= -----END PRIVATE KEY-----" PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApmHkae115z3vMF9IjiUa XrJH1Ljifxqs+XbDCFhQenz2iOf/bZDBMqfrwhBe3hVywa13kIuhf1TZlcJ/nRhE IZIbg6BF6/GeHN998Cx/IjhBeFkG7f1hdxEXY5ibd7yq8vAfzizZpnCu4+LM/NvI ei9mU+hj3GxrDbpMQTHCDDqX5aNEix8uFU4XaOm9UOJw9ivz19cqcnxWqJ49znxw XUlS5k4Hewbwf2s2BWFhz+CDWPHMpWViLQ17qj5QRklNn39sUymP1kXKP3Y76zvW zNVniPvFt+hHixxEa7tUEaTdtk4FgkNnWy0bFa7DIPC8ukjHvHHg10LeQiygDduI xbdVJwYK/qitU68ZAbbufldlj2FoVLwSyP9qpxUIdRoljCop1kDu6xQiAq0VHEAV LtgMpZhtD/6D4z644QJ8atx4Ljp/HEtTngaTh+/vLc9AnhHclX0/JmlbP1VkWWxG 9FIxS0TVPolRBs8948kLioTif9lrltJicxd6T0OuA5fny8l6hdSlzaD3pnr5pkYw mVn5FgBAFnd/skX6DsRs+eb2lNahkvNZZ15z9UeU2Rl6J9uBx0vbkcB7eiSCPhRM IKq/CtkyEgz8ce4kG18fJHDFDASRvGjaBu91lV5h7NLmhFLFzMvb7XoHrv/yD6TV E5W47J/DrfrevJkzS1GV++MCAwEAAQ== -----END PUBLIC KEY-----" KOLAB_GIT_REF=dev/mollekopf KOLAB_GIT_REMOTE=https://git.kolab.org/source/kolab GIT_REF_ROUNDCUBEMAIL=dev/kolab-1.5 GIT_REMOTE_ROUNDCUBEMAIL=https://git.kolab.org/source/roundcubemail.git GIT_REF_ROUNDCUBEMAIL_PLUGINS=master GIT_REMOTE_ROUNDCUBEMAIL_PLUGINS=https://git.kolab.org/diffusion/RPK/roundcubemail-plugins-kolab.git GIT_REF_CHWALA=dev/mollekopf GIT_REMOTE_CHWALA=https://git.kolab.org/diffusion/C/chwala.git GIT_REF_SYNCROTON=master GIT_REMOTE_SYNCROTON=https://git.kolab.org/diffusion/S/syncroton.git GIT_REF_AUTOCONF=master GIT_REMOTE_AUTOCONF=https://git.kolab.org/diffusion/AC/autoconf.git GIT_REF_IRONY=master GIT_REMOTE_IRONY=https://git.kolab.org/source/iRony.git GIT_REF_FREEBUSY=master GIT_REMOTE_FREEBUSY=https://git.kolab.org/diffusion/F/freebusy.git IMAP_GIT_REF=dev/mollekopf IMAP_GIT_REMOTE=https://git.kolab.org/source/cyrus-imapd PODMAN="podman" # Teardown the currently running environment kolab__teardown() { $PODMAN pod rm --force tests $PODMAN pod rm --force dev } # Build all containers required for testing kolab__build() { $PODMAN build docker/base/ -f almalinux9 -t apheleia/almalinux9 $PODMAN build docker/swoole/ -t apheleia/swoole $PODMAN build --ulimit nofile=65535:65535 docker/webapp -t kolab-webapp \ ${KOLAB_GIT_REMOTE:+"--build-arg=GIT_REMOTE=$KOLAB_GIT_REMOTE"} \ ${KOLAB_GIT_REF:+"--build-arg=GIT_REF=$KOLAB_GIT_REF"} $PODMAN build --ulimit nofile=65535:65535 docker/meet -t kolab-meet \ ${KOLAB_GIT_REMOTE:+"--build-arg=GIT_REMOTE=$KOLAB_GIT_REMOTE"} \ ${KOLAB_GIT_REF:+"--build-arg=GIT_REF=$KOLAB_GIT_REF"} $PODMAN build docker/imap -t kolab-imap $PODMAN build docker/mariadb -t mariadb $PODMAN build docker/redis -t redis $PODMAN build docker/proxy -t proxy + $PODMAN build docker/synapse -t synapse + $PODMAN build docker/element -t element $PODMAN build --ulimit nofile=65535:65535 docker/tests -t kolab-tests $PODMAN build --ulimit nofile=65535:65535 docker/roundcube -t roundcube } # Setup the test environment kolab__setup() { kolab__teardown echo "Build" kolab__build echo "Setup" POD=tests # Create the pod first $PODMAN pod create --name $POD podman__run_mariadb podman__run_redis podman__healthcheck mariadb redis podman__run_webapp config.demo podman__healthcheck webapp podman__run_imap podman__healthcheck imap # Ensure all commands are processed echo "Flushing work queue" $PODMAN exec -ti webapp ./artisan queue:work --stop-when-empty podman__run_minio podman__healthcheck minio # Validate the test environment kolab__validate } # "testsuite" # "quicktest" # "tests/Feature/Jobs/WalletCheckTest.php" kolab__test() { $PODMAN run -ti --pod tests --name kolab-tests --replace \ --env-file=ci/env \ -v ./src:/src/kolabsrc.orig:ro \ -e APP_SERVICES_DOMAINS="localhost" \ -e PASSPORT_PRIVATE_KEY="$PASSPORT_PRIVATE_KEY" \ -e PASSPORT_PUBLIC_KEY="$PASSPORT_PUBLIC_KEY" \ kolab-tests:latest /init.sh $@ } kolab__proxytest() { $PODMAN run -ti --pod tests --name proxy-tests --replace \ -v ./docker/certs/:/etc/certs/:ro \ --env-file=ci/env \ -e SSL_CERTIFICATE=/etc/certs/imap.hosted.com.cert \ -e SSL_CERTIFICATE_KEY=/etc/certs/imap.hosted.com.key \ proxy:latest /init.sh validate } kolab__lint() { kolab__test lint } # Setup the test environment and run a complete testsuite kolab__testrun() { echo "Setup" kolab__setup echo "Test" kolab__test testsuite } # Setup the test environment and run all testsuites kolab__testrun_complete() { echo "Setup" kolab__setup echo "Test" kolab__test lint kolab__test testsuite kolab__rctest syncroton lint kolab__rctest syncroton testsuite kolab__rctest irony lint # kolab__rctest irony testsuite kolab__rctest roundcubemail-plugins-kolab lint # kolab__rctest roundcubemail-plugins-kolab testsuite } # Get a shell inside the test container to run/debug tests kolab__shell() { kolab__test shell } # Run the roundcube testsuite kolab__rctest() { $PODMAN run -t --pod tests --name roundcube --replace \ -v ./ext:/src.orig:ro \ -e APP_DOMAIN=kolab.test \ -e DES_KEY=kBxUM/53N9p9abusAoT0ZEAxwI2pxFz/ \ -e DB_HOST=127.0.0.1 \ -e DB_RC_DATABASE=roundcube \ -e DB_RC_USERNAME=roundcube \ -e DB_RC_PASSWORD=simple123 \ -e IMAP_HOST=localhost \ -e IMAP_PORT=11143 \ -e IMAP_ADMIN_LOGIN=cyrus-admin \ -e IMAP_ADMIN_PASSWORD=simple123 \ -e IMAP_DEBUG=false \ -e SQL_DEBUG=false \ -e ACTIVESYNC_DEBUG=false \ -e RUN_MIGRATIONS=true \ -e MAIL_HOST=localhost \ -e MAIL_PORT=10587 \ -e FILEAPI_WOPI_OFFICE=https://kolab.local \ -e CALENDAR_CALDAV_SERVER=http://imap:11080/dav \ -e KOLAB_ADDRESSBOOK_CARDDAV_SERVER=http://imap:11080/dav \ roundcube:latest ./init.sh $@ } # Get a shell inside the roundcube test container to run/debug tests kolab__rcshell() { $PODMAN run -ti --pod tests --name roundcube --replace \ -v ./ext:/src.orig:ro \ -e APP_DOMAIN=kolab.test \ -e DES_KEY=kBxUM/53N9p9abusAoT0ZEAxwI2pxFz/ \ -e DB_HOST=127.0.0.1 \ -e DB_RC_DATABASE=roundcube \ -e DB_RC_USERNAME=roundcube \ -e DB_RC_PASSWORD=simple123 \ -e IMAP_HOST=localhost \ -e IMAP_PORT=11143 \ -e IMAP_ADMIN_LOGIN=cyrus-admin \ -e IMAP_ADMIN_PASSWORD=simple123 \ -e MAIL_HOST=localhost \ -e MAIL_PORT=10587 \ -e FILEAPI_WOPI_OFFICE=https://kolab.local \ -e CALENDAR_CALDAV_SERVER=http://localhost:11080/dav \ -e KOLAB_ADDRESSBOOK_CARDDAV_SERVER=http://localhost:11080/dav \ roundcube:latest ./init.sh shell } kolab__validate() { $PODMAN exec imap testsaslauthd -u cyrus-admin -p simple123 $PODMAN exec imap testsaslauthd -u "john@kolab.org" -p simple123 # Ensure the inbox is created FOUND=false for i in {1..60}; do if $PODMAN exec imap bash -c 'echo "lm" | cyradm --auth PLAIN -u cyrus-admin -w simple123 --port 11143 localhost | grep "user/john@kolab.org"'; then echo "Found mailbox"; FOUND=true break else echo "Waiting for mailbox"; sleep 1; fi done if ! $FOUND; then echo "Failed to find the inbox for john@kolab.org" exit 1 fi } podman__healthcheck() { for CONTAINER in $@; do echo "Waiting for ${CONTAINER} become healthy " while [ $(podman healthcheck run ${CONTAINER}) ]; do echo -n "."; sleep 5; done done } podman__run_proxy() { $PODMAN run -dt --pod $POD --name proxy --replace \ -v ./docker/certs:/etc/certs:ro \ -e APP_WEBSITE_DOMAIN="kolab.local" \ -e SSL_CERTIFICATE="/etc/certs/imap.hosted.com.cert" \ -e SSL_CERTIFICATE_KEY="/etc/certs/imap.hosted.com.key" \ -e WEBAPP_BACKEND="http://localhost:8000" \ -e MEET_BACKEND="https://localhost:12443" \ -e ROUNDCUBE_BACKEND="http://localhost:8080" \ -e DAV_BACKEND="http://localhost:11080/dav" \ -e COLLABORA_BACKEND="http://localhost:9980" \ - kolab-proxy:latest + -e MATRIX_BACKEND="http://localhost:8008" \ + -e ELEMENT_BACKEND="http://localhost:8880" \ + proxy:latest } podman__run_roundcube() { $PODMAN run -dt --pod $POD --name roundcube --replace \ -v ./ext:/src.orig:ro \ -e APP_DOMAIN=kolab.test \ -e DES_KEY=kBxUM/53N9p9abusAoT0ZEAxwI2pxFz/ \ -e DB_HOST=127.0.0.1 \ -e DB_RC_DATABASE=roundcube \ -e DB_RC_USERNAME=roundcube \ -e DB_RC_PASSWORD=simple123 \ -e IMAP_HOST=localhost \ -e IMAP_PORT=11143 \ -e IMAP_ADMIN_LOGIN=cyrus-admin \ -e IMAP_ADMIN_PASSWORD=simple123 \ -e MAIL_HOST=localhost \ -e MAIL_PORT=10587 \ -e IMAP_DEBUG=true \ -e FILEAPI_WOPI_OFFICE=https://kolab.local \ -e CALENDAR_CALDAV_SERVER=http://imap:11080/dav \ -e KOLAB_ADDRESSBOOK_CARDDAV_SERVER=http://imap:11080/dav \ roundcube:latest } podman__run_mariadb() { $PODMAN run -dt --pod $POD --name mariadb --replace \ --mount=type=tmpfs,tmpfs-size=512M,destination=/var/lib/mysql,U=true \ -e MYSQL_ROOT_PASSWORD=simple123 \ -e TZ="+02:00" \ -e DB_HKCCP_DATABASE=kolabdev \ -e DB_HKCCP_USERNAME=kolabdev \ -e DB_HKCCP_PASSWORD=simple123 \ -e DB_KOLAB_DATABASE=kolab \ -e DB_KOLAB_USERNAME=kolab \ -e DB_KOLAB_PASSWORD=simple123 \ -e DB_RC_DATABASE=roundcube \ -e DB_RC_USERNAME=roundcube \ -e DB_RC_PASSWORD=simple123 \ --health-cmd "mysqladmin -u root ping && test -e /tmp/initialized" \ mariadb:latest } podman__run_redis() { $PODMAN run -dt --pod $POD --name redis --replace \ --mount=type=tmpfs,tmpfs-size=128M,destination=/var/lib/redis,U=true \ --health-cmd "redis-cli ping || exit 1" \ redis:latest } podman__run_minio() { $PODMAN run -dt --pod $POD --name minio --replace \ --mount=type=tmpfs,tmpfs-size=128M,destination=/data,U=true \ -e MINIO_ROOT_USER=minio \ -e MINIO_ROOT_PASSWORD=simple123 \ --health-cmd "mc ready local || exit 1" \ --entrypoint sh \ quay.io/minio/minio:latest -c 'mkdir -p /data/kolab && minio server /data --console-address ":9001"' } podman__run_webapp() { # We run with a fixed config.demo overlay and override the environment with ci/env $PODMAN run -dt --pod $POD --name webapp --replace \ --env-file=ci/env \ -v ./src:/src/kolabsrc.orig:ro \ -v ./$1/src:/src/overlay:ro \ -e NOENVFILE=true \ -e APP_SERVICES_ALLOWED_DOMAINS="webapp,localhost,services.kolab.local" \ -e KOLAB_ROLE=combined \ -e PASSPORT_PRIVATE_KEY="$PASSPORT_PRIVATE_KEY" \ -e PASSPORT_PUBLIC_KEY="$PASSPORT_PUBLIC_KEY" \ --health-cmd "./artisan octane:status || exit 1" \ kolab-webapp:latest } podman__run_imap() { $PODMAN run -dt --pod $POD --name imap --replace \ --mount=type=tmpfs,tmpfs-size=128M,tmpfs-mode=777,destination=/var/spool/imap,U=true,notmpcopyup \ --mount=type=tmpfs,tmpfs-size=128M,tmpfs-mode=777,destination=/var/lib/imap,U=true,notmpcopyup \ -e APP_SERVICES_DOMAIN="localhost" \ -e SERVICES_PORT=8000 \ -e IMAP_ADMIN_LOGIN=cyrus-admin \ -e IMAP_ADMIN_PASSWORD=simple123 \ --health-cmd "test -e /run/saslauthd/mux && kill -0 \$(cat /var/run/master.pid)" \ kolab-imap:latest } +podman__run_synapse() { + $PODMAN run -dt --pod $POD --name synapse --replace \ + --mount=type=tmpfs,tmpfs-size=128M,tmpfs-mode=777,destination=/data,U=true,notmpcopyup \ + -e APP_DOMAIN="kolab.local" \ + -e KOLAB_URL="http://127.0.0.1:8000" \ + synapse:latest +} + +podman__run_element() { + $PODMAN run -dt --pod $POD --name element --replace \ + -e APP_DOMAIN="kolab.local" \ + element:latest +} + podman__run_meet() { $PODMAN run -dt --pod $POD --name meet --replace \ -v ./meet/server:/src/meet:ro \ -v ./docker/certs/meet.kolab.local.cert:/etc/pki/tls/certs/meet.kolab.local.cert \ -v ./docker/certs/meet.kolab.local.key:/etc/pki/tls/private/meet.kolab.local.key \ -e WEBRTC_LISTEN_IP=127.0.0.1 \ -e WEBRTC_ANNOUNCED_ADDRESS=127.0.0.1 \ -e PUBLIC_DOMAIN=kolab.local \ -e LISTENING_HOST=127.0.0.1 \ -e LISTENING_PORT=12443 \ -e DEBUG="*" \ -e TURN_SERVER=none \ -e AUTH_TOKEN=simple123 \ -e WEBHOOK_TOKEN=simple123 \ -e WEBHOOK_URL=kolab.local/api/webhooks/meet \ -e SSL_CERT=/etc/pki/tls/certs/meet.kolab.local.cert \ -e SSL_KEY=/etc/pki/tls/private/meet.kolab.local.key \ kolab-meet:latest } +kolab__run() { + POD=dev + podman__run_$1 +} + kolab__deploy() { POD=dev # Create the pod first $PODMAN pod create \ --replace \ --add-host=kolab.local:127.0.0.1 \ --publish "443:6443" \ --publish "465:6465" \ --publish "587:6587" \ --publish "143:6143" \ --publish "993:6993" \ --publish "44444:44444/udp" \ --publish "44444:44444/tcp" \ --name $POD podman__run_mariadb podman__run_redis podman__healthcheck mariadb redis podman__run_webapp config.prod podman__healthcheck webapp podman__run_imap podman__healthcheck imap # Ensure all commands are processed echo "Flushing work queue" $PODMAN exec -ti webapp ./artisan queue:work --stop-when-empty $PODMAN exec webapp ./artisan user:password "admin@kolab.local" "simple123" + podman__run_synapse + podman__run_element + podman__run_minio podman__healthcheck minio podman__run_meet podman__healthcheck meet podman__run_roundcube podman__run_proxy } kolab__generate_mail() { $PODMAN run --pod=dev -t --rm kolab-utils:latest ./generatemail.py --maxAttachmentSize=3 --type=mail --count 100 --username admin@kolab.local --password simple123 --host localhost --port 11143 INBOX } kolab__syncroton_sync() { $PODMAN run -t --network=host --add-host=kolab.local:127.0.0.1 --rm kolab-utils:latest ./activesynccli.py --host kolab.local --user admin@kolab.local --password simple123 sync 38b950ebd62cd9a66929c89615d0fc04 } kolab__db() { $PODMAN exec -ti mariadb /bin/bash -c "mysql -h 127.0.0.1 -u kolabdev --password=simple123 kolabdev" } kolab__help() { cat </dev/null 2>&1; then "kolab__$cmdname" "${@:1}" else echo "Function $cmdname not recognized" >&2 kolab__help exit 1 fi diff --git a/docker/element/Dockerfile b/docker/element/Dockerfile new file mode 100644 index 00000000..ed9c2d44 --- /dev/null +++ b/docker/element/Dockerfile @@ -0,0 +1,42 @@ +FROM apheleia/almalinux9 + +RUN dnf -y install \ + --setopt 'tsflags=nodocs' \ + wget \ + nginx && \ + dnf clean all + +ARG ELEMENTWEB_VERSION="v1.11.35" + +RUN dnf -y install \ + --setopt 'tsflags=nodocs' \ + nginx \ + wget \ + && dnf clean all + +# Forward request logs to Docker log collector +RUN ln -sf /dev/stdout /var/log/nginx/access.log \ + && ln -sf /dev/stderr /var/log/nginx/error.log + +RUN wget "https://github.com/vector-im/element-web/releases/download/${ELEMENTWEB_VERSION}/element-${ELEMENTWEB_VERSION}.tar.gz" -O "/opt/element-${ELEMENTWEB_VERSION}.tar.gz" && \ + tar -xf /opt/element-${ELEMENTWEB_VERSION}.tar.gz -C /opt && \ + ln -s /opt/element-${ELEMENTWEB_VERSION} /opt/element-web && \ + rm /opt/element-${ELEMENTWEB_VERSION}.tar.gz + +COPY rootfs/ / + +STOPSIGNAL SIGTERM + +RUN id default || (groupadd -g 1001 default && useradd -d /opt/app-root/ -u 1001 -g 1001 default) + +RUN PATHS=(/opt/app-root/src /opt/element-web /etc/nginx /var/lib/nginx) && \ + mkdir -p ${PATHS[@]} && \ + chmod -R 777 ${PATHS[@]} && \ + chown -R 1001:0 ${PATHS[@]} && \ + chmod -R g=u ${PATHS[@]} + +USER 1001 + +CMD ["/init.sh"] + +EXPOSE 8880/tcp diff --git a/docker/element/rootfs/etc/nginx/nginx.conf b/docker/element/rootfs/etc/nginx/nginx.conf new file mode 100644 index 00000000..2c1dded5 --- /dev/null +++ b/docker/element/rootfs/etc/nginx/nginx.conf @@ -0,0 +1,75 @@ +# For more information on configuration, see: +# * Official English Documentation: http://nginx.org/en/docs/ +# * Official Russian Documentation: http://nginx.org/ru/docs/ + +worker_processes auto; +error_log /var/log/nginx/error.log; +pid /tmp/nginx.pid; + +# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + # Load modular configuration files from the /etc/nginx/conf.d directory. + # See http://nginx.org/en/docs/ngx_core_module.html#include + # for more information. + include /etc/nginx/conf.d/*.conf; + + server { + listen 8880 default_server; + listen [::]:8880 default_server; + server_name _; + + root /opt/element-web; + index index.html index.htm; + + # Load configuration files for the default server block. + include /etc/nginx/default.d/*.conf; + + location /element { + rewrite ^/element(/.*)$ $1 last; + } + + location / { + try_files $uri $uri/ =404; + add_header Access-Control-Allow-Origin *; + } + + location /.well-known/matrix/client { + return 200 '{"m.homeserver": {"base_url": "https://APP_DOMAIN"}}'; + default_type application/json; + add_header Access-Control-Allow-Origin *; + } + + location /.well-known/matrix/server { + return 200 '{"m.server": "APP_DOMAIN:443"}'; + default_type application/json; + } + + } +} + diff --git a/docker/element/rootfs/init.sh b/docker/element/rootfs/init.sh new file mode 100755 index 00000000..6cbffba2 --- /dev/null +++ b/docker/element/rootfs/init.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +sed -i -r \ + -e "s|APP_DOMAIN|$APP_DOMAIN|g" \ + /etc/nginx/nginx.conf + +if [[ ! -f /opt/element-web/config.json ]]; then + cp /opt/app-root/src/config.json /opt/element-web/config.json + sed -i -r \ + -e "s|APP_DOMAIN|$APP_DOMAIN|g" \ + /opt/element-web/config.json +fi + +if [[ $1 == "validate" ]]; then + exec nginx -t +else + exec nginx -g "daemon off;" +fi diff --git a/docker/element/rootfs/opt/app-root/src/config.json b/docker/element/rootfs/opt/app-root/src/config.json new file mode 100644 index 00000000..adbc9848 --- /dev/null +++ b/docker/element/rootfs/opt/app-root/src/config.json @@ -0,0 +1,39 @@ +{ + "default_server_config": { + "m.homeserver": { + "base_url": "https://APP_DOMAIN", + "server_name": "matrix.kolab" + } + }, + "brand": "Kolab Chat", + "permalink_prefix": "https://APP_DOMAIN", + "disable_custom_urls": true, + "disable_guests": true, + "disable_login_language_selector": false, + "disable_3pid_login": true, + "brand": "Kolab Chat", + "integrations_ui_url": null, + "integrations_rest_url": null, + "integrations_widgets_urls": [ + ], + "bug_report_endpoint_url": "https://element.io/bugreports/submit", + "default_country_code": "GB", + "show_labs_settings": false, + "features": { }, + "default_federate": false, + "default_theme": "light", + "room_directory": { + "servers": [ + "APP_DOMAIN" + ] + }, + "enable_presence_by_hs_url": { + "https://matrix.org": false, + "https://matrix-client.matrix.org": false, + "https://kolab.local": true + }, + "setting_defaults": { + "breadcrumbs": true + }, + "jitsi": {} +} diff --git a/docker/proxy/Dockerfile b/docker/proxy/Dockerfile index 8d26aaf9..86de68e5 100644 --- a/docker/proxy/Dockerfile +++ b/docker/proxy/Dockerfile @@ -1,33 +1,35 @@ FROM apheleia/almalinux9 RUN dnf -y install \ --setopt 'tsflags=nodocs' \ nginx \ nginx-mod-mail \ nginx-mod-stream && \ dnf clean all # ENV APP_WEBSITE_DOMAIN # ENV SSL_CERTIFICATE # ENV SSL_CERTIFICATE_KEY COPY rootfs/ / RUN chgrp -R 0 /etc/nginx /run /var/log/nginx && \ chmod -R g=u /etc/nginx /run /var/log/nginx && \ chown -R 1001:0 /etc/nginx /run /var/log/nginx STOPSIGNAL SIGTERM ENV WEBAPP_BACKEND http://webapp:8000 ENV MEET_BACKEND https://meet:12443 ENV ROUNDCUBE_BACKEND http://roundcube:8080 ENV DAV_BACKEND http://imap:11080/dav ENV COLLABORA_BACKEND http://collabora:9980 ENV WEBMAIL_PATH /roundcubemail +ENV MATRIX_BACKEND http://synapse:8008 +ENV ELEMENT_BACKEND http://element:8880 USER 1001 CMD ["/init.sh"] EXPOSE 6443/tcp 6465/tcp 6587/tcp 6143/tcp 6144/tcp 6993/tcp diff --git a/docker/proxy/rootfs/etc/nginx/nginx.conf b/docker/proxy/rootfs/etc/nginx/nginx.conf index 5d74a134..17f7a0b7 100644 --- a/docker/proxy/rootfs/etc/nginx/nginx.conf +++ b/docker/proxy/rootfs/etc/nginx/nginx.conf @@ -1,317 +1,351 @@ # For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ worker_processes auto; error_log stderr info; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /dev/stdout main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; map $http_upgrade $connection_upgrade { default upgrade; '' close; } # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { listen 6080; listen 6443 default_server ssl; listen [::]:6443 ssl ipv6only=on; ssl_certificate SSL_CERTIFICATE_CERT; ssl_certificate_key SSL_CERTIFICATE_KEY; server_name APP_WEBSITE_DOMAIN; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; + + location /element { + proxy_pass ELEMENT_BACKEND; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + # Synapse responses may be chunked, which is an HTTP/1.1 feature. + proxy_http_version 1.1; + } + location / { proxy_pass WEBAPP_BACKEND; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_no_cache 1; proxy_cache_bypass 1; # Mostly for files, swoole has a 10MB limit client_max_body_size 11m; } location /meetmedia { proxy_pass MEET_BACKEND; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; } location /meetmedia/api { proxy_pass MEET_BACKEND; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_no_cache 1; proxy_cache_bypass 1; } location WEBMAIL_PATH { proxy_pass ROUNDCUBE_BACKEND; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_no_cache 1; proxy_cache_bypass 1; } location /chwala { proxy_pass ROUNDCUBE_BACKEND; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_no_cache 1; proxy_cache_bypass 1; } location /Microsoft-Server-ActiveSync { auth_request /auth; #auth_request_set $auth_status $upstream_status; proxy_pass ROUNDCUBE_BACKEND; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_send_timeout 910s; proxy_read_timeout 910s; fastcgi_send_timeout 910s; fastcgi_read_timeout 910s; } location ~* ^/\\.well-known/autoconfig { proxy_pass ROUNDCUBE_BACKEND; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~* ^/\\autodiscover/autodiscover.xml { proxy_pass ROUNDCUBE_BACKEND; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~ ^/\\.well-known/(caldav|carddav)(.*)$ { proxy_pass ROUNDCUBE_BACKEND; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /dav { #auth_request_set $auth_status $upstream_status; proxy_pass DAV_BACKEND; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # static files location ^~ /browser { proxy_pass COLLABORA_BACKEND; proxy_set_header Host $http_host; } # WOPI discovery URL location ^~ /hosting/discovery { proxy_pass COLLABORA_BACKEND; proxy_set_header Host $http_host; } # Capabilities location ^~ /hosting/capabilities { proxy_pass COLLABORA_BACKEND; proxy_set_header Host $http_host; } # main websocket location ~ ^/cool/(.*)/ws$ { proxy_pass COLLABORA_BACKEND; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $http_host; proxy_read_timeout 36000s; } # download, presentation and image upload location ~ ^/(c|l)ool { proxy_pass COLLABORA_BACKEND; proxy_set_header Host $http_host; } # Admin Console websocket location ^~ /cool/adminws { proxy_pass COLLABORA_BACKEND; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $http_host; proxy_read_timeout 36000s; } + # We are not doing federation for the time being + # # For the federation port + # listen 8448 ssl default_server; + # listen [::]:8448 ssl default_server; + location ~ ^(/_matrix|/_synapse/client) { + proxy_pass MATRIX_BACKEND; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + + # Nginx by default only allows file uploads up to 1M in size + # Increase client_max_body_size to match max_upload_size defined in homeserver.yaml + client_max_body_size 50M; + + # Synapse responses may be chunked, which is an HTTP/1.1 feature. + proxy_http_version 1.1; + } + + location /.well-known/matrix/client { + return 200 '{"m.homeserver": {"base_url": "https://APP_WEBSITE_DOMAIN"}}'; + default_type application/json; + add_header Access-Control-Allow-Origin *; + } + location = /auth { internal; proxy_pass WEBAPP_BACKEND/api/webhooks/nginx-httpauth; proxy_pass_request_body off; proxy_set_header Host services.APP_WEBSITE_DOMAIN; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /healthz { auth_basic off; allow all; return 200; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } } mail { server_name APP_WEBSITE_DOMAIN; auth_http WEBAPP_BACKEND/api/webhooks/nginx; auth_http_header Host services.APP_WEBSITE_DOMAIN; proxy_pass_error_message on; proxy_smtp_auth on; xclient off; server { listen 6143; protocol imap; proxy on; starttls on; ssl_certificate SSL_CERTIFICATE_CERT; ssl_certificate_key SSL_CERTIFICATE_KEY; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; } # Roundcube specific imap endpoint with proxy-protocol enabled server { listen 6144 proxy_protocol; protocol imap; auth_http WEBAPP_BACKEND/api/webhooks/nginx-roundcube; proxy on; starttls on; ssl_certificate SSL_CERTIFICATE_CERT; ssl_certificate_key SSL_CERTIFICATE_KEY; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; } server { listen 6465 ssl; protocol smtp; proxy on; ssl_certificate SSL_CERTIFICATE_CERT; ssl_certificate_key SSL_CERTIFICATE_KEY; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; } server { listen 6587; protocol smtp; proxy on; starttls on; ssl_certificate SSL_CERTIFICATE_CERT; ssl_certificate_key SSL_CERTIFICATE_KEY; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; } server { listen 6993 ssl; protocol imap; proxy on; ssl_certificate SSL_CERTIFICATE_CERT; ssl_certificate_key SSL_CERTIFICATE_KEY; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; } } stream { server { listen 6190; proxy_pass imap:4190; } } diff --git a/docker/proxy/rootfs/init.sh b/docker/proxy/rootfs/init.sh index 2f1caca5..225a16ca 100755 --- a/docker/proxy/rootfs/init.sh +++ b/docker/proxy/rootfs/init.sh @@ -1,19 +1,21 @@ #!/bin/bash sed -i -r \ -e "s|APP_WEBSITE_DOMAIN|$APP_WEBSITE_DOMAIN|g" \ -e "s|SSL_CERTIFICATE_CERT|$SSL_CERTIFICATE|g" \ -e "s|SSL_CERTIFICATE_KEY|$SSL_CERTIFICATE_KEY|g" \ -e "s|WEBAPP_BACKEND|$WEBAPP_BACKEND|g" \ -e "s|MEET_BACKEND|$MEET_BACKEND|g" \ -e "s|ROUNDCUBE_BACKEND|$ROUNDCUBE_BACKEND|g" \ -e "s|DAV_BACKEND|$DAV_BACKEND|g" \ -e "s|COLLABORA_BACKEND|$COLLABORA_BACKEND|g" \ -e "s|WEBMAIL_PATH|$WEBMAIL_PATH|g" \ + -e "s|MATRIX_BACKEND|$MATRIX_BACKEND|g" \ + -e "s|ELEMENT_BACKEND|$ELEMENT_BACKEND|g" \ /etc/nginx/nginx.conf if [[ $1 == "validate" ]]; then exec nginx -t else exec nginx -g "daemon off;" fi diff --git a/docker/synapse/Dockerfile b/docker/synapse/Dockerfile new file mode 100644 index 00000000..80448b6e --- /dev/null +++ b/docker/synapse/Dockerfile @@ -0,0 +1,44 @@ +FROM apheleia/almalinux9 + +ENV HOME=/opt/app-root/src + + +RUN dnf -y install \ + --setopt=install_weak_deps=False \ + --setopt 'tsflags=nodocs' \ + libtiff-devel \ + libjpeg-devel \ + libzip-devel \ + freetype-devel \ + lcms2 \ + libwebp-devel \ + tcl-devel \ + tk-devel \ + python3 \ + python3-pip \ + libffi-devel \ + openssl-devel \ + sed \ + wget && \ + pip3 install matrix-synapse && \ + dnf clean all + +COPY /rootfs / + +RUN id default || (groupadd -g 1001 default && useradd -d /opt/app-root/ -u 1001 -g 1001 default) + +RUN PATHS=(/opt/app-root/src) && \ + mkdir -p ${PATHS[@]} && \ + chmod -R 777 ${PATHS[@]} && \ + chown -R 1001:0 ${PATHS[@]} && \ + chmod -R g=u ${PATHS[@]} + +USER 1001 + +WORKDIR ${HOME} + +VOLUME /data/ + +CMD ["/opt/app-root/src/init.sh"] + +EXPOSE 8008/tcp diff --git a/docker/synapse/rootfs/opt/app-root/src/homeserver.yaml b/docker/synapse/rootfs/opt/app-root/src/homeserver.yaml new file mode 100644 index 00000000..5dc42bbd --- /dev/null +++ b/docker/synapse/rootfs/opt/app-root/src/homeserver.yaml @@ -0,0 +1,146 @@ +server_name: "APP_DOMAIN" +public_baseurl: "https://APP_DOMAIN" +pid_file: /opt/app-root/src/homeserver.pid +listeners: + - port: 8008 + tls: false + type: http + x_forwarded: true + bind_addresses: ['::'] + resources: + - names: [client, federation] + compress: false +database: + name: sqlite3 + args: + database: /data/homeserver.db +log_config: "/opt/app-root/src/log.config" +web_client: False +soft_file_limit: 0 +# We have no registration +# registration_shared_secret: "REGISTRATION_SHARED_SECRET" +# We just use a default derived from the signing key +# macaroon_secret_key: "MACAROON_SECRET_KEY" +# Only required for consent forms that we don't use +# form_secret: "FORM_SECRET" +report_stats: false +enable_metrics: false +signing_key_path: "/data/signing.key" +old_signing_keys: {} +key_refresh_interval: "1d" +trusted_key_servers: [] + +## Performance ## +event_cache_size: "10K" + +## Ratelimiting ## +rc_messages_per_second: 0.2 +rc_message_burst_count: 10.0 +federation_rc_window_size: 1000 +federation_rc_sleep_limit: 10 +federation_rc_sleep_delay: 500 +federation_rc_reject_limit: 50 +federation_rc_concurrent: 3 + +## Files ## +media_store_path: /data/media_store +max_upload_size: 50M +max_image_pixels: 32M +dynamic_thumbnails: false + +# media_retention: +# local_media_lifetime: 90d +# remote_media_lifetime: 14d + +# List of thumbnail to precalculate when an image is uploaded. +thumbnail_sizes: +- width: 32 + height: 32 + method: crop +- width: 96 + height: 96 + method: crop +- width: 320 + height: 240 + method: scale +- width: 640 + height: 480 + method: scale +- width: 800 + height: 600 + method: scale + +url_preview_enabled: False +max_spider_size: "10M" + +## Captcha ## +enable_registration_captcha: False + +## Turn ## +turn_uris: [TURN_URIS] +turn_shared_secret: "TURN_SHARED_SECRET" +turn_user_lifetime: "1h" +turn_allow_guests: false + +## Registration ## +enable_registration: false +enable_registration_without_verification: false +bcrypt_rounds: 12 +allow_guest_access: false +enable_group_creation: false +inhibit_user_in_use_error: true + +user_directory: + enabled: false + search_all_users: false + prefer_local_users: false + +allow_public_rooms_without_auth: false + +enable_set_displayname: false +enable_set_avatar_url: false +enable_3pid_changes: false + +# Avoid leaking profile information +require_auth_for_profile_requests: true +limit_profile_requests_to_users_who_share_rooms: true +include_profile_data_on_invite: false + +federation_domain_whitelist: + - APP_DOMAIN + +# oidc_providers: +# - idp_id: kolab +# idp_name: "Kolab" +# issuer: "https://127.0.0.1:8443/auth/realms/{realm_name}" +# client_id: "synapse" +# client_secret: "copy secret generated from above" +# scopes: ["openid", "profile"] +# user_mapping_provider: +# config: +# localpart_template: "\{\{ user.preferred_username }}" +# display_name_template: "\{\{ user.name }}" + +modules: +- module: kolab_auth_provider.KolabAuthProvider + config: + kolab_url: "KOLAB_URL" + + +## API Configuration ## + +# app_service_config_files: +# - /config/hookshot.yaml + +expire_access_token: false + +password_config: + enabled: true + +# Configure a default retention policy (can be overriden ber room) +retention: + allowed_lifetime_min: 1d + allowed_lifetime_max: 1y + default_policy: + min_lifetime: 1d + max_lifetime: 1y diff --git a/docker/synapse/rootfs/opt/app-root/src/init.sh b/docker/synapse/rootfs/opt/app-root/src/init.sh new file mode 100755 index 00000000..62351d45 --- /dev/null +++ b/docker/synapse/rootfs/opt/app-root/src/init.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +sed -i -r \ + -e "s|APP_DOMAIN|$APP_DOMAIN|g" \ + -e "s|KOLAB_URL|$KOLAB_URL|g" \ + -e "s|TURN_SHARED_SECRET|$TURN_SHARED_SECRET|g" \ + -e "s|TURN_URIS|$TURN_URIS|g" \ + /opt/app-root/src/homeserver.yaml + +exec synctl --no-daemonize start ${CONFIGFILE:-/opt/app-root/src/homeserver.yaml} diff --git a/docker/synapse/rootfs/opt/app-root/src/log.config b/docker/synapse/rootfs/opt/app-root/src/log.config new file mode 100644 index 00000000..64fe9f65 --- /dev/null +++ b/docker/synapse/rootfs/opt/app-root/src/log.config @@ -0,0 +1,22 @@ +version: 1 + +formatters: + precise: + format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s' + +handlers: + console: + class: logging.StreamHandler + formatter: precise + +loggers: + synapse.storage.SQL: + # beware: increasing this to DEBUG will make synapse log sensitive + # information such as access tokens. + level: INFO + +root: + level: "INFO" + handlers: [console] + +disable_existing_loggers: false diff --git a/docker/synapse/rootfs/usr/lib/python3.9/site-packages/kolab_auth_provider.py b/docker/synapse/rootfs/usr/lib/python3.9/site-packages/kolab_auth_provider.py new file mode 100644 index 00000000..05302d02 --- /dev/null +++ b/docker/synapse/rootfs/usr/lib/python3.9/site-packages/kolab_auth_provider.py @@ -0,0 +1,97 @@ +from typing import Awaitable, Callable, Optional, Tuple + +import logging +import synapse +from synapse import module_api + +logger = logging.getLogger(__name__) + + +class KolabAuthProvider: + def __init__(self, config: dict, api: module_api): + self.api = api + + self.kolab_url = config['kolab_url'] + + api.register_password_auth_provider_callbacks( + check_3pid_auth=self.check_3pid_auth, + auth_checkers={ + ("m.login.password", ("password",)): self.check_pass, + }, + ) + + async def check_kolab_login(self, username, password): + # Raises an exception on non-200 status + response = await self.api.http_client.post_json_get_json( + self.kolab_url + "/api/auth/login", {"email": username, "password": password} + ) + logger.info("RESPONSE") + logger.info(response) + + return True + + def email_to_username(self, email): + # We simply use the entire email address as username + if "=" in email: + raise Exception("Can't deal with = in email.") + return email.replace("@", "=") + + def username_to_email(self, username): + return username.replace("=", "@") + + # This is used when logging in via email address + async def check_3pid_auth( + self, + medium: str, + address: str, + password: str, + ) -> Optional[ + Tuple[ + str, + Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]] + ] + ]: + logger.info("Got email password check for " + address) + + if not await self.check_kolab_login(address, password): + logger.info("Password mismatch " + address) + return None + + logger.info("password check success for " + address) + username = self.email_to_username(address) + userid = self.api.get_qualified_user_id(username) + if not await self.api.check_user_exists(userid): + logger.info("Account not found so registering " + address) + # TODO set admin flag based on if user is admin + # Maybe copy over some other details? + userid = await self.api.register_user(username, address, [address]) + + logger.info("auth complete " + userid) + return userid, None + + # This will be used to check the password on existing users + async def check_pass( + self, + username: str, + login_type: str, + login_dict: "synapse.module_api.JsonDict", + ) -> Optional[ + Tuple[ + str, + Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]], + ] + ]: + logger.info("Got password check for " + username) + if login_type != "m.login.password": + logger.info("wrong type" + login_type) + return None + + localpart = username.split(":", 1)[0][1:] + logger.info("localpart %s authenticated", localpart) + + email = self.username_to_email(localpart) + if not await self.check_kolab_login(email, login_dict.get("password")): + logger.info("Password mismatch " + email) + return None + + return self.api.get_qualified_user_id(username), None