🏗️(caldav) migrate from davical to sabre/dav
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""CalDAV proxy views for forwarding requests to DAViCal."""
|
||||
"""CalDAV proxy views for forwarding requests to CalDAV server."""
|
||||
|
||||
import logging
|
||||
|
||||
@@ -10,15 +10,13 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
import requests
|
||||
|
||||
from core.services.caldav_service import DAViCalClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class CalDAVProxyView(View):
|
||||
"""
|
||||
Proxy view that forwards all CalDAV requests to DAViCal.
|
||||
Proxy view that forwards all CalDAV requests to CalDAV server.
|
||||
Handles authentication and adds appropriate headers.
|
||||
|
||||
CSRF protection is disabled because CalDAV uses non-standard HTTP methods
|
||||
@@ -27,7 +25,7 @@ class CalDAVProxyView(View):
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""Forward all HTTP methods to DAViCal."""
|
||||
"""Forward all HTTP methods to CalDAV server."""
|
||||
# Handle CORS preflight requests
|
||||
if request.method == "OPTIONS":
|
||||
response = HttpResponse(status=200)
|
||||
@@ -42,83 +40,40 @@ class CalDAVProxyView(View):
|
||||
if not request.user.is_authenticated:
|
||||
return HttpResponse(status=401)
|
||||
|
||||
# Ensure user exists in DAViCal before making requests
|
||||
try:
|
||||
davical_client = DAViCalClient()
|
||||
davical_client.ensure_user_exists(request.user)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to ensure user exists in DAViCal: %s", str(e))
|
||||
# Continue anyway - user might already exist
|
||||
|
||||
# Build the DAViCal URL
|
||||
davical_url = getattr(settings, "DAVICAL_URL", "http://davical:80")
|
||||
# Build the CalDAV server URL
|
||||
caldav_url = settings.CALDAV_URL
|
||||
path = kwargs.get("path", "")
|
||||
|
||||
# Use user email as the principal (DAViCal uses email as username)
|
||||
# Use user email as the principal (CalDAV server uses email as username)
|
||||
user_principal = request.user.email
|
||||
|
||||
# Handle root CalDAV requests - return principal collection
|
||||
if not path or path == user_principal:
|
||||
# For PROPFIND on root, return the user's principal collection
|
||||
if request.method == "PROPFIND":
|
||||
# Get the request path to match the href in response
|
||||
request_path = request.path
|
||||
if not request_path.endswith("/"):
|
||||
request_path += "/"
|
||||
# Build target URL - CalDAV server uses base URI /api/v1.0/caldav/
|
||||
# The proxy receives requests at /api/v1.0/caldav/... and forwards them
|
||||
# to the CalDAV server at the same path (sabre/dav expects requests at its base URI)
|
||||
base_uri_path = "/api/v1.0/caldav"
|
||||
clean_path = path.lstrip("/") if path else ""
|
||||
|
||||
# Return multistatus with href matching request URL and calendar-home-set
|
||||
multistatus = f"""<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<D:response>
|
||||
<D:href>{request_path}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:displayname>{user_principal}</D:displayname>
|
||||
<C:calendar-home-set>
|
||||
<D:href>/api/v1.0/caldav/{user_principal}/</D:href>
|
||||
</C:calendar-home-set>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>"""
|
||||
response = HttpResponse(
|
||||
content=multistatus,
|
||||
status=207,
|
||||
content_type="application/xml; charset=utf-8",
|
||||
)
|
||||
return response
|
||||
|
||||
# For other methods, redirect to principal URL
|
||||
target_url = f"{davical_url}/caldav.php/{user_principal}/"
|
||||
# Construct target URL - always include the base URI path
|
||||
if clean_path:
|
||||
target_url = f"{caldav_url}{base_uri_path}/{clean_path}"
|
||||
else:
|
||||
# Build target URL with path
|
||||
# Remove leading slash if present
|
||||
clean_path = path.lstrip("/")
|
||||
if clean_path.startswith(user_principal):
|
||||
# Path already includes principal
|
||||
target_url = f"{davical_url}/caldav.php/{clean_path}"
|
||||
else:
|
||||
# Path is relative to principal
|
||||
target_url = f"{davical_url}/caldav.php/{user_principal}/{clean_path}"
|
||||
# Root request - use base URI path
|
||||
target_url = f"{caldav_url}{base_uri_path}/"
|
||||
|
||||
# Prepare headers for DAViCal
|
||||
# Set headers to tell DAViCal it's behind a proxy so it generates correct URLs
|
||||
script_name = "/api/v1.0/caldav"
|
||||
# Prepare headers for CalDAV server
|
||||
# CalDAV server Apache backend reads REMOTE_USER, which we set via X-Forwarded-User
|
||||
headers = {
|
||||
"Content-Type": request.content_type or "application/xml",
|
||||
"X-Forwarded-User": user_principal,
|
||||
"X-Forwarded-For": request.META.get("REMOTE_ADDR", ""),
|
||||
"X-Forwarded-Prefix": script_name,
|
||||
"X-Forwarded-Host": request.get_host(),
|
||||
"X-Forwarded-Proto": request.scheme,
|
||||
"X-Script-Name": script_name, # Tell DAViCal the base path
|
||||
}
|
||||
|
||||
# DAViCal authentication: users with password '*' use external auth
|
||||
# CalDAV server authentication: Apache backend reads REMOTE_USER
|
||||
# We send the username via X-Forwarded-User header
|
||||
# For HTTP Basic Auth, we use the email as username with empty password
|
||||
# This works with DAViCal's external authentication when trust_x_forwarded is true
|
||||
# CalDAV server converts X-Forwarded-User to REMOTE_USER
|
||||
auth = (user_principal, "")
|
||||
|
||||
# Copy relevant headers from the original request
|
||||
@@ -135,11 +90,11 @@ class CalDAVProxyView(View):
|
||||
body = request.body if request.body else None
|
||||
|
||||
try:
|
||||
# Forward the request to DAViCal
|
||||
# Forward the request to CalDAV server
|
||||
# Use HTTP Basic Auth with username (email) and empty password
|
||||
# DAViCal will authenticate based on X-Forwarded-User header when trust_x_forwarded is true
|
||||
# CalDAV server will authenticate based on X-Forwarded-User header (converted to REMOTE_USER)
|
||||
logger.debug(
|
||||
"Forwarding %s request to DAViCal: %s (user: %s)",
|
||||
"Forwarding %s request to CalDAV server: %s (user: %s)",
|
||||
request.method,
|
||||
target_url,
|
||||
user_principal,
|
||||
@@ -157,7 +112,7 @@ class CalDAVProxyView(View):
|
||||
# Log authentication failures for debugging
|
||||
if response.status_code == 401:
|
||||
logger.warning(
|
||||
"DAViCal returned 401 for user %s at %s. Headers sent: %s",
|
||||
"CalDAV server returned 401 for user %s at %s. Headers sent: %s",
|
||||
user_principal,
|
||||
target_url,
|
||||
headers,
|
||||
@@ -170,7 +125,7 @@ class CalDAVProxyView(View):
|
||||
content_type=response.headers.get("Content-Type", "application/xml"),
|
||||
)
|
||||
|
||||
# Copy relevant headers from DAViCal response
|
||||
# Copy relevant headers from CalDAV server response
|
||||
for header in ["ETag", "DAV", "Allow", "Location"]:
|
||||
if header in response.headers:
|
||||
django_response[header] = response.headers[header]
|
||||
@@ -178,7 +133,7 @@ class CalDAVProxyView(View):
|
||||
return django_response
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error("DAViCal proxy error: %s", str(e))
|
||||
logger.error("CalDAV server proxy error: %s", str(e))
|
||||
return HttpResponse(
|
||||
content=f"CalDAV server error: {str(e)}",
|
||||
status=502,
|
||||
|
||||
Reference in New Issue
Block a user