Kozea / Radicale

A simple CalDAV (calendar) and CardDAV (contact) server.
https://radicale.org
GNU General Public License v3.0
3.31k stars 429 forks source link

Change calendar for existing event on iOS doesn't work #1461

Open sancoder opened 6 months ago

sancoder commented 6 months ago

Running Radicale 3.1.9 on Linux (Debian; nginx as reverse proxy, ssl via letsencrypt). For calendar sharing we use symlinks the following way: /var/lib/collections/collection-root has one folder calendars and few user folders which are symlinks to calendars folder in the same directory. Every user can see (!), read and write to other users' calendars.

The problem is that after event was created, changing calendar for the event has no effect. The user who made the change (put it from calendar1 to calendar2) sees the change. All other users don't see the change and see the old version.

That's weird because all other changes work as expected.

Download ics file via web interface I can find event in the original calendar, but not in the calendar the event was put into. That explains why all users see event in the "old" calendar. That doesn't explain why the user who made the change continues to see the change.

Will try to dig deeper into this some time later. In the meantime if anyone could test and reproduce the same feature - it would be helpful.

pbiering commented 6 months ago

I have similar setup here, having shared calendar by softlinking, clients are

I had similar issue in the past and finally it was solved by fixing code in the "move.py" section together with verifcation of the reverse proxy config in case "radicale" is behind the proxy. Your description look like the same, the event is only moved on iOS client, but not successful executed on server side. Check log of "radicale", it should mention here something. If required, enable debug mode.

sancoder commented 6 months ago

Ran radicale from console, and indeed here is what I got

[2024-04-09 05:07:36 +0000] [145517/Thread-28] [INFO] MOVE request for '/user/9-uuid-A/A-uuid-6.ics' received from 127.0.0.1 (forwarded for '7-ip-address-3') using 'iOS/17.4.1 (21E236) dataaccessd/1.0'
[2024-04-09 05:07:36 +0000] [145517/Thread-28] [INFO] Unsupported destination address: 'https://my.server.name/user/F-uuid-6/A-uuid-6.ics'
[2024-04-09 05:07:36 +0000] [145517/Thread-28] [INFO] MOVE response status for '/user/9-uuid-A/A-uuid-6.ics' in 0.002 seconds: 502 Bad Gateway

(I changed user name, sernver name, ip address, and uuid's).

pbiering commented 6 months ago

The "MOVE" code was extended related to such detection between 3.1.8 and 3.19, are you using latest version? And can you confirm your "nginx" configuration is similar to what is documented? https://radicale.org/master.html#reverse-proxy

sancoder commented 6 months ago

Yes, I am definitely on 3.1.9. Just ran python -m radicale --version and the output was 3.1.9. As to nginx config, I remember it didn't work as expected so I changed it to this

server {
    listen 443 ssl;
    server_name my.server.name;
    ssl_certificate     /etc/letsencrypt/live/my.server.name/cert.pem;
    ssl_certificate_key /etc/letsencrypt/live/my.server.name/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        proxy_pass      http://127.0.0.1:5232/;
        proxy_set_header     X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass_header    Authorization;
    }
    location /radicale/ {
        proxy_pass      http://127.0.0.1:5232/;
        proxy_set_header     X-Script-Name /radicale;
        proxy_set_header     X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass_header    Authorization;
    }
}

That config was made after I found a comment how to setup radicale to work with iOS.

sancoder commented 6 months ago

Sorry, I think the link to reddit is not the real source of the config. Looks like I saved a wrong one to my notes.

pbiering commented 5 months ago

Is it working now? If not, the related code is here: https://github.com/Kozea/Radicale/blob/master/radicale/app/move.py

Check also why your config misses

proxy_set_header  Host $http_host;
sancoder commented 5 months ago

Investigated. to_netloc_with_port = my.server.name:443 while get_server_netloc(environ, force_port=True) = my.server.name:5232

Condition on line 57 of move.py fails, and we see "Unsupported destination"

sancoder commented 5 months ago

My guess is that initial code was to compare netloc1 and netloc2 without ports but someone wanted to run several servers on one machine utilizing different ports, and hence comparison is with ports. My expectation from radicale would be to work without comparing ports, so no issues when running behind reverse proxy. If someone (author?) has better alternative feel free to share.

sancoder commented 5 months ago

That works (for me at least)

diff --git a/radicale/app/move.py b/radicale/app/move.py
index 5bd8a57..6df226b 100644
--- a/radicale/app/move.py
+++ b/radicale/app/move.py
@@ -27,7 +27,7 @@ from radicale.app.base import Access, ApplicationBase
 from radicale.log import logger

-def get_server_netloc(environ: types.WSGIEnviron, force_port: bool = False):
+def get_server_netloc(environ: types.WSGIEnviron):
     if environ.get("HTTP_X_FORWARDED_HOST"):
         host = environ["HTTP_X_FORWARDED_HOST"]
         proto = environ.get("HTTP_X_FORWARDED_PROTO") or "http"
@@ -37,10 +37,7 @@ def get_server_netloc(environ: types.WSGIEnviron, force_port: bool = False):
         host = environ.get("HTTP_HOST") or environ["SERVER_NAME"]
         proto = environ["wsgi.url_scheme"]
         port = environ["SERVER_PORT"]
-    if (not force_port and port == ("443" if proto == "https" else "80") or
-            re.search(r":\d+$", host)):
-        return host
-    return host + ":" + port
+    return host

 class ApplicationPartMove(ApplicationBase):
@@ -50,11 +47,7 @@ class ApplicationPartMove(ApplicationBase):
         """Manage MOVE request."""
         raw_dest = environ.get("HTTP_DESTINATION", "")
         to_url = urlparse(raw_dest)
-        to_netloc_with_port = to_url.netloc
-        if to_url.port is None:
-            to_netloc_with_port += (":443" if to_url.scheme == "https"
-                                    else ":80")
-        if to_netloc_with_port != get_server_netloc(environ, force_port=True):
+        if to_url.netloc != get_server_netloc(environ):
             logger.info("Unsupported destination address: %r", raw_dest)
             # Remote destination server, not supported
             return httputils.REMOTE_DESTINATION
pbiering commented 5 months ago

will investigate next days

pbiering commented 5 months ago

Is your case also working if you simply change this line

-        if to_netloc_with_port != get_server_netloc(environ, force_port=True):
-        if to_netloc_with_port != get_server_netloc(environ, force_port=False):

BTW: nobody else is currently complaining about issues with MOVE since the last code change here (2023-04)

sancoder commented 5 months ago

As I said above, to_netloc_with_port = my.server.name:443 so even if the right part of the condition will be without port, there is no chance it will work.

sancoder commented 5 months ago

Is there a way to determine (in code) whether radicale is working behind reverse proxy? If yes, the condition should be without port, if no as it was.

sancoder commented 5 months ago

BTW: I was considering not to use radicale because of the bug. Maybe this is the reason why noone else complains?

pbiering commented 5 months ago

"MOVE" is working fine behind an Apache reverse proxy using config sniplet from documentation https://radicale.org/master.html#reverse-proxy

Can you add following line to your "nginx" configuration

    proxy_set_header  X-Forwarded-Proto "https";

This is conditionally set in the Apache example in case HTTPS is active, see also https://github.com/Kozea/Radicale/issues/1301, the examples for other reverse proxies are missing this still potentially.

sancoder commented 4 months ago

Added header to nginx config. Changed code back to as it was. Restarted nginx, restarted radicale. Created a test event, changed calendar for the event. Same result (unsupported destination).

Honestly, I don't understand how is it effective to try different stuff and see what sticks to the wall. Maybe better is to understand what is happening (see result of my investigation above)?

pbiering commented 3 months ago

I've extended (currently only in RPM packaging) the config templates for Apache https://src.fedoraproject.org/rpms/radicale/blob/rawhide/f/radicale-httpd

"MOVE" tested, working well.

Can be tested btw. using such request (example):

# move cal1->cal2
curl --insecure --request MOVE -u USER:PASS https://FQDN/radicale/USER/cal1/event1.ics/ --header 'Destination: https://FQDN/radicale/USER/cal2/event1.ics'
# move cal2->cal1
curl --insecure --request MOVE -u USER:PASS https://FQDN/radicale/USER/cal2/event1.ics/ --header 'Destination: https://FQDN/radicale/USER/cal1/event1.ics'
sancoder commented 3 months ago

If you're testing without reverse proxy, it makes sense. If (as above) reverse proxy is used, then:

pbiering commented 3 months ago

I've tested also via reverse proxy. Tested "apache" config now added to repository: https://github.com/Kozea/Radicale/tree/master/contrib/apache

Please verify your "nginx" settings for equal headers and post your (anonymized) request headers, best sniffed between "nginx" and "radicale" using ngrep.