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); } }