Digamos que ayudas a gestionar una pequeña organización o club y tienes una base de datos que almacena todos los detalles de los miembros (nombres, teléfono, correo electrónico...).
¿No sería bueno tener acceso a esta información actualizada dondequiera que la necesite? Bueno, ¡con CardDAV puedes!
CardDAV es un estándar abierto con buen soporte para la gestión de contactos; tiene una integración nativa en la aplicación Contactos de iOS y muchas aplicaciones disponibles para Android.
En el lado del servidor, la implementación de CardDAV es un servidor http que responde a métodos http inusuales (PROPFIND, REPORT en lugar de GET, POST...). Afortunadamente existe un módulo Go para simplificar enormemente el trabajo: github.com/emersion/go-webdav. Esta biblioteca espera un backend implementado y proporciona un http.Handler estándar que debería atender solicitudes HTTP después de la autenticación.
Curiosamente, la biblioteca no proporciona ninguna ayuda con respecto a la autenticación del usuario; sin embargo, gracias a la capacidad de composición de Go, esto no es un problema.
CardDAV utiliza credenciales de autenticación básica. Una vez que se verifican las credenciales, podemos guardarlas en el contexto (será útil más adelante):
package main import ( "context" "net/http" "github.com/emersion/go-webdav/carddav" ) type ( ctxKey struct{} ctxValue struct { username string } ) func NewCardDAVHandler() http.Handler { actualHandler := carddav.Handler{ Backend: &ownBackend{}, } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() // check username and password: adjust the logic to your system (do NOT store passwords in plaintext) if !ok || username != "admin" || password != "s3cr3t" { // abort the request handling on failure w.Header().Add("WWW-Authenticate", `Basic realm="Please authenticate", charset="UTF-8"`) http.Error(w, "HTTP Basic auth is required", http.StatusUnauthorized) return } // user is authenticated: store this info in the context ctx := context.WithValue(r.Context(), ctxKey{}, ctxValue{username}) // delegate the work to the CardDAV handle actualHandler.ServeHTTP(w, r.WithContext(ctx)) }) }
La estructura ownBackend debe implementar la interfaz carddav.Backend, que no es muy delgada, pero sí manejable.
CurrentUserPrincipal y AddressBookHomeSetPath deben proporcionar URL (que comienzan y terminan con una barra diagonal). Normalmente será nombre de usuario/contactos. Aquí es donde necesitas extraer el nombre de usuario del contexto (que es el único argumento disponible):
func currentUsername(ctx context.Context) (string, error) { if v, ok := ctx.Value(ctxKey{}).(ctxValue); ok { return v.username, nil } return "", errors.New("not authenticated") } type ownBackend struct{} // must begin and end with a slash func (b *ownBackend) CurrentUserPrincipal(ctx context.Context) (string, error) { username, err := currentUsername(ctx) return "/" url.PathEscape(username) "/", err } // must begin and end with a slash as well func (b *ownBackend) AddressBookHomeSetPath(ctx context.Context) (string, error) { principal, err := b.CurrentUserPrincipal(ctx) return principal "contacts/", err }
Después de eso, puede comenzar la diversión: debes implementar los métodos AddressBook, GetAddressObject y ListAddressObjects.
AddressBook devuelve una estructura simple, donde la ruta debe comenzar con AddressBookHomeSetPath arriba (y terminar con una barra diagonal)
GetAddressObject y ListAddressObjects deben verificar la ruta actual (para garantizar que el usuario actualmente autenticado pueda acceder a esos contactos) y luego devolver los contactos como AddressObject.
El AddressObject tiene múltiples atributos, el más importante:
La VCard representa los datos de contacto reales y probablemente deba adaptarse dependiendo de cómo almacene sus contactos. En mi caso terminó así:
func utf8Field(v string) *vcard.Field { return &vcard.Field{ Value: v, Params: vcard.Params{ "CHARSET": []string{"UTF-8"}, }, } } func vcardFromUser(u graphqlient.User) vcard.Card { c := vcard.Card{} c.Set(vcard.FieldFormattedName, utf8Field(u.Firstname " " u.Lastname)) c.SetName(&vcard.Name{ Field: utf8Field(""), FamilyName: u.Lastname, GivenName: u.Firstname, }) c.SetRevision(u.UpdatedAt) c.SetValue(vcard.FieldUID, u.Extid) c.Set(vcard.FieldOrganization, utf8Field(u.Unit)) // addFields sorts the key to ensure a stable order addFields := func(fieldName string, values map[string]string) { for _, k := range slices.Sorted(maps.Keys(values)) { v := values[k] c.Add(fieldName, &vcard.Field{ Value: v, Params: vcard.Params{ vcard.ParamType: []string{k ";CHARSET=UTF-8"}, // hacky but prevent maps ordering issues // "CHARSET": []string{"UTF-8"}, }, }) } } addFields(vcard.FieldEmail, u.Emails) addFields(vcard.FieldTelephone, u.Phones) vcard.ToV4(c) return c }
Algunos métodos permiten actualizar un contacto. Como no quiero que mi lista de miembros se actualice a través de CardDAV, devuelvo un error 403 a los métodos Put y Delete: return webdav.NewHTTPError(http.StatusForbidden, errores.New("carddav: operación no admitida"))
iOS requiere que el servidor CardDAV funcione a través de https. Puede generar certificados autofirmados localmente usando openssl (reemplace 192.168.XXX.XXX con su dirección IP) para ingresarlos en http.ListenAndServeTLS(addr, "localhost.crt", "localhost.key", NewCardDAVHandler())
openssl req -new -subj "/C=US/ST=Utah/CN=192.168.XXX.XXX" -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt
Después de eso, deberías poder experimentar localmente agregando una "cuenta de contacto CardDAV" que apunte a tu propia dirección IP y puerto.
Implementar un servidor CardDAV en Go es un poco complicado, pero claramente vale la pena: ¡tus contactos se sincronizarán automáticamente con los datos que tienes en el servidor de tu organización!
¿Conoce otros protocolos interesantes que permitan este tipo de integración nativa? ¡Siéntete libre de compartir tus experiencias!
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3