"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > ¿Cómo sincronizar tus contactos con tu teléfono? Implementación de CardDAV en Go!

¿Cómo sincronizar tus contactos con tu teléfono? Implementación de CardDAV en Go!

Publicado el 2024-11-07
Navegar:982

How to synchronize your contacts with your phone? Implemeting CardDAV in Go!

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.

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

Implementación de la interfaz CardDAV

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.

Objeto de dirección

El AddressObject tiene múltiples atributos, el más importante:

  • la ruta para identificar este contacto en particular (puede ser arbitraria, comienza con una barra)
  • la ETag para permitir que el cliente verifique rápidamente si se realizó alguna actualización (si la olvida, iOS no mostrará nada)
  • la Tarjeta que espera una VCard

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
}

Tomando el atajo de sólo lectura

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"))

Pruebas localmente

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.

Conclusión

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!

Declaración de liberación Este artículo se reproduce en: https://dev.to/cmdscale/how-to-synchronize-your-contacts-with-your-phone-implemeting-carddav-in-go-9ia?1 Si hay alguna infracción, por favor contacto Study_golang@163 .comeliminar
Último tutorial Más>

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