Add CalDAV support, refactor

The filesystem storage backend now implements the required functions to
act as a basic CalDAV server. Some refactoring was done based on the
go-webdav development: introduce a UserPrincipalBackend, a new function
to serve the user principal URL, and more. See this PR for lots of
details: https://github.com/emersion/go-webdav/pull/62

Also adds a simple facility for debug output.
This commit is contained in:
Conrad Hoffmann 2022-03-23 10:38:14 +01:00
parent 5728f1ee27
commit 001917295d
7 changed files with 487 additions and 85 deletions

View file

@ -1,19 +1,83 @@
package main
import (
"context"
"encoding/base64"
"flag"
"fmt"
"log"
"net/http"
"os"
"github.com/emersion/go-webdav"
"github.com/emersion/go-webdav/caldav"
"github.com/emersion/go-webdav/carddav"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"git.sr.ht/~sircmpwn/tokidoki/auth"
"git.sr.ht/~sircmpwn/tokidoki/debug"
"git.sr.ht/~sircmpwn/tokidoki/storage"
)
type userPrincipalBackend struct{}
func (u *userPrincipalBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
authCtx, ok := auth.FromContext(ctx)
if !ok {
panic("Invalid data in auth context!")
}
if authCtx == nil {
return "", fmt.Errorf("unauthenticated requests are not supported")
}
userDir := base64.RawStdEncoding.EncodeToString([]byte(authCtx.UserName))
return "/" + userDir + "/", nil
}
type tokidokiHandler struct {
upBackend webdav.UserPrincipalBackend
authBackend auth.AuthProvider
caldavBackend caldav.Backend
carddavBackend carddav.Backend
}
func (u *tokidokiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
userPrincipalPath, err := u.upBackend.CurrentUserPrincipal(r.Context())
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
var homeSets []webdav.BackendSuppliedHomeSet
if u.caldavBackend != nil {
path, err := u.caldavBackend.CalendarHomeSetPath(r.Context())
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
} else {
homeSets = append(homeSets, caldav.NewCalendarHomeSet(path))
}
}
if u.carddavBackend != nil {
path, err := u.carddavBackend.AddressbookHomeSetPath(r.Context())
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
} else {
homeSets = append(homeSets, carddav.NewAddressBookHomeSet(path))
}
}
opts := webdav.ServeUserPrincipalOptions{
UserPrincipalPath: userPrincipalPath,
HomeSets: homeSets,
}
if webdav.ServeUserPrincipal(w, r, opts) {
return
}
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
func main() {
var (
addr string
@ -23,6 +87,7 @@ func main() {
flag.StringVar(&addr, "addr", ":8080", "listening address")
flag.StringVar(&authURL, "auth.url", "", "auth backend URL (required)")
flag.StringVar(&storageURL, "storage.url", "", "storage backend URL (required)")
flag.BoolVar(&debug.Enable, "debug", false, "enable debug output")
flag.Parse()
if len(flag.Args()) != 0 || authURL == "" || storageURL == "" {
@ -48,17 +113,36 @@ func main() {
}
mux.Use(authProvider.Middleware())
backend, err := storage.NewFromURL(storageURL)
upBackend := &userPrincipalBackend{}
caldavBackend, carddavBackend, err := storage.NewFromURL(storageURL, "/calendar/", "/contacts/", upBackend)
if err != nil {
log.Fatalf("failed to load storage backend: %s", err.Error())
}
mux.Mount("/", &carddav.Handler{Backend: backend})
carddavHandler := carddav.Handler{Backend: carddavBackend}
caldavHandler := caldav.Handler{Backend: caldavBackend}
handler := tokidokiHandler{
upBackend: upBackend,
authBackend: authProvider,
caldavBackend: caldavBackend,
carddavBackend: carddavBackend,
}
mux.Mount("/", &handler)
mux.Mount("/.well-known/caldav", &caldavHandler)
mux.Mount("/.well-known/carddav", &carddavHandler)
mux.Mount("/{user}/contacts", &carddavHandler)
mux.Mount("/{user}/calendar", &caldavHandler)
server := http.Server{
Addr: addr,
Handler: mux,
}
log.Printf("Server running on %s", addr)
debug.Printf("Debug output enabled")
err = server.ListenAndServe()
if err != http.ErrServerClosed {
log.Fatalf("ListenAndServe: %s", err.Error())