diff --git a/docker/proxy/rootfs/etc/nginx/nginx.conf b/docker/proxy/rootfs/etc/nginx/nginx.conf
index 422c4452..bd5a9ab4 100644
--- a/docker/proxy/rootfs/etc/nginx/nginx.conf
+++ b/docker/proxy/rootfs/etc/nginx/nginx.conf
@@ -1,324 +1,325 @@
# 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 {
# Add trailing slashes to paths
rewrite ^([^.]*[^/])$ $scheme://$server_name$1/ permanent;
}
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_pass DAV_BACKEND;
+ proxy_redirect http:// $scheme://;
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 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;
proxy_pass_error_message on;
proxy_smtp_auth on;
xclient off;
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 6143;
protocol imap;
proxy on;
starttls on;
}
# Roundcube specific imap endpoint with proxy-protocol enabled
server {
listen 6144 proxy_protocol;
protocol imap;
proxy on;
starttls on;
auth_http WEBAPP_BACKEND/api/webhooks/nginx-roundcube;
}
server {
listen 6465 ssl;
protocol smtp;
proxy on;
}
server {
listen 6587;
protocol smtp;
proxy on;
starttls on;
}
server {
listen 6993 ssl;
protocol imap;
proxy on;
}
}
stream {
server {
listen 6190;
proxy_pass SIEVE_BACKEND;
}
}
diff --git a/src/tests/Infrastructure/DavTest.php b/src/tests/Infrastructure/DavTest.php
index d5380416..96c79799 100644
--- a/src/tests/Infrastructure/DavTest.php
+++ b/src/tests/Infrastructure/DavTest.php
@@ -1,335 +1,324 @@
user) {
$this->user = $this->getTestUser('davtest@kolab.org', ['password' => 'simple123'], true);
}
$baseUri = \config('services.dav.uri');
$this->isCyrus = strpos($baseUri, '/iRony') === false;
$this->path = $this->isCyrus ? '/dav' : '/iRony';
if (!$this->client) {
$this->client = new \GuzzleHttp\Client([
'http_errors' => false, // No exceptions
'base_uri' => $baseUri,
'verify' => false,
'auth' => [$this->user->email, 'simple123'],
'connect_timeout' => 10,
'timeout' => 10,
'headers' => [
'Content-Type' => 'application/xml; charset=utf-8',
'Depth' => '1',
]
]);
}
}
public function testDiscoverPrincipal(): void
{
$body = "";
$response = $this->client->request('PROPFIND', '', ['body' => $body]);
$this->assertEquals(207, $response->getStatusCode());
$data = $response->getBody();
$email = $this->user->email;
if ($this->isCyrus) {
$this->assertStringContainsString("{$this->path}/principals/user/{$email}/", $data);
} else {
$this->assertStringContainsString("{$this->path}/principals/{$email}/", $data);
}
}
/**
* This codepath is triggerd by MacOS CalDAV when it tries to login.
* Verify we don't crash and end up with a 500 status code.
*/
public function testFailingLogin(): void
{
$body = "";
$params = [
'Content-Type' => 'application/xml; charset=utf-8',
'Depth' => '1',
'body' => $body,
'auth' => ['invaliduser@kolab.org', 'invalid']
];
$response = $this->client->request('PROPFIND', '', $params);
$this->assertSame($this->isCyrus ? 401 : 403, $response->getStatusCode());
}
/**
* This codepath is triggerd by MacOS CardDAV when it tries to login.
* NOTE: This depends on the username_domain roundcube config option.
*/
public function testShortlogin(): void
{
$this->markTestSkipped('Shortlogins dont work with the nginx proxy.');
// @phpstan-ignore-next-line "Code above always terminates"
$body = "";
$response = $this->client->request('PROPFIND', '', [
'body' => $body,
'auth' => ['davtest', 'simple123']
]);
$this->assertEquals(207, $response->getStatusCode());
}
public function testDiscoverCalendarHomeset(): void
{
$body = <<
EOF;
$email = $this->user->email;
$href = $this->isCyrus ? "principals/user/{$email}" : '';
$response = $this->client->request('PROPFIND', $href, ['body' => $body]);
$this->assertEquals(207, $response->getStatusCode());
$data = $response->getBody();
if ($this->isCyrus) {
$this->assertStringContainsString("{$this->path}/calendars/user/{$email}/", $data);
} else {
$this->assertStringContainsString("{$this->path}/calendars/{$email}/", $data);
}
}
public function testDiscoverCalendars(): string
{
$body = <<
EOF;
$params = [
'headers' => [
'Depth' => 'infinity',
],
'body' => $body,
];
$email = $this->user->email;
$href = $this->isCyrus ? "calendars/user/{$email}" : "calendars/{email}";
$response = $this->client->request('PROPFIND', $href, $params);
$this->assertEquals(207, $response->getStatusCode());
$data = $response->getBody();
if ($this->isCyrus) {
$this->assertStringContainsString("{$this->path}/calendars/user/{$email}/", $data);
} else {
$this->assertStringContainsString("{$this->path}/calendars/{$email}/", $data);
}
$doc = new \DOMDocument('1.0', 'UTF-8');
$doc->loadXML($data);
$response = $doc->getElementsByTagName('response')->item(1);
$doc->getElementsByTagName('href')->item(0);
$this->assertEquals('d:href', $response->childNodes->item(0)->nodeName);
$href = $response->childNodes->item(0)->nodeValue;
return $href;
}
/**
* @depends testDiscoverCalendars
*/
public function testPropfindCalendar($href): void
{
$body = <<
EOF;
$params = [
'headers' => [
'Depth' => '0',
],
'body' => $body,
];
$response = $this->client->request('PROPFIND', $href, $params);
$this->assertEquals(207, $response->getStatusCode());
$data = $response->getBody();
$this->assertStringContainsString("$href", $data);
}
/**
* Thunderbird does this and relies on the WWW-Authenticate header response to
* start sending authenticated requests.
*
* @depends testDiscoverCalendars
*/
public function testPropfindCalendarWithoutAuth($href): void
{
$body = <<
EOF;
$params = [
'headers' => [
'Depth' => '0',
],
'body' => $body,
'auth' => [],
];
$response = $this->client->request('PROPFIND', $href, $params);
$this->assertEquals(401, $response->getStatusCode());
$this->assertStringContainsString('Basic realm=', $response->getHeader('WWW-Authenticate')[0]);
$data = $response->getBody();
if ($this->isCyrus) {
$this->assertStringContainsString("Unauthorized", $data);
} else {
$this->assertStringContainsString("Sabre\DAV\Exception\NotAuthenticated", $data);
}
}
/**
* Required for MacOS autoconfig
*/
public function testOptions(): void
{
- $body = <<
-
-
-
-
-
-
-
- EOF;
-
- $response = $this->client->request('OPTIONS', "principals/{$this->user->email}", ['body' => $body]);
+ $response = $this->client->request('OPTIONS', "principals/{$this->user->email}");
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('PROPFIND', implode(', ', $response->getHeader('Allow')));
}
public function testWellKnown(): void
{
$body = <<
EOF;
$email = $this->user->email;
$path = trim(\config('services.dav.uri'), '/');
$baseUri = preg_replace('|/[^/]+$|', '', $path);
$params = [
'headers' => [
'Depth' => 'infinity',
],
'body' => $body,
'allow_redirects' => false,
'base_uri' => $baseUri,
];
// The base URL needs to work as a redirect
$response = $this->client->request('PROPFIND', "/.well-known/caldav/", $params);
$this->assertEquals(301, $response->getStatusCode());
$redirectTarget = $response->getHeader('location')[0];
$this->assertEquals($path . '/calendars', trim($redirectTarget, '/'));
// Follow the redirect
$response = $this->client->request('PROPFIND', $redirectTarget, $params);
$this->assertEquals(207, $response->getStatusCode());
// Any URL should result in a redirect to the same path
- $url = $this->isCyrus ? "/user/{$email}" : "/calendars/{$email}";
+ $url = $this->isCyrus ? "user/{$email}" : "calendars/{$email}";
$response = $this->client->request('PROPFIND', "/.well-known/caldav/{$url}", $params);
$this->assertEquals(301, $response->getStatusCode());
$redirectTarget = $response->getHeader('location')[0];
$expected = $path . ($this->isCyrus ? "/calendars/user/{$email}" : "/calendars/{$email}");
$this->assertEquals($expected, $redirectTarget);
// Follow the redirect
$response = $this->client->request('PROPFIND', $redirectTarget, $params);
$this->assertEquals(207, $response->getStatusCode());
$data = $response->getBody();
if ($this->isCyrus) {
$this->assertStringContainsString("{$this->path}/calendars/user/{$email}/", $data);
} else {
$this->assertStringContainsString("{$this->path}/calendars/{$email}/", $data);
}
}
/**
* @doesNotPerformAssertions
*/
public function testCleanup(): void
{
$this->deleteTestUser($this->user->email);
}
}