"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Como sincronizar seus contatos com seu telefone? Implementando CardDAV no Go!

Como sincronizar seus contatos com seu telefone? Implementando CardDAV no Go!

Publicado em 2024-11-07
Navegar:469

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

Digamos que você ajuda a administrar uma pequena organização ou clube e tem um banco de dados armazenando todos os detalhes dos membros (nomes, telefone, e-mail...).
Não seria bom ter acesso a essas informações atualizadas em qualquer lugar que você precisar? Bem, com CardDAV você pode!

CardDAV é um padrão aberto com bom suporte para gerenciamento de contatos; possui integração nativa no aplicativo de contatos do iOS e diversos aplicativos disponíveis para Android.

Do lado do servidor, a implementação do CardDAV é um servidor http que responde a métodos http incomuns (PROPFIND, REPORT em vez de GET, POST...). Felizmente existe um módulo Go para simplificar bastante o trabalho: github.com/emersion/go-webdav. Esta biblioteca espera um Backend implementado e fornece um http.Handler padrão que deve atender solicitações HTTP após autenticação.

Autenticação

Curiosamente, a biblioteca não fornece nenhuma ajuda em relação à autenticação do usuário, no entanto, graças à capacidade de composição do Go, isso não é um problema.
CardDAV usa credenciais de autenticação básica. Depois que as credenciais forem verificadas, podemos salvá-las no contexto (será útil mais tarde):

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

Implementando a interface CardDAV

A estrutura ownBackend deve implementar a interface carddav.Backend, que não é muito fina, mas ainda gerenciável.

O CurrentUserPrincipal e o AddressBookHomeSetPath devem fornecer URLs (começando e terminando com uma barra). Geralmente será nome de usuário/contatos. É aqui que você precisa extrair o nome de usuário do contexto (que é o único argumento disponível):

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
}

Depois disso a diversão pode começar: você precisa implementar os métodos AddressBook, GetAddressObject e ListAddressObjects.

AddressBook retorna uma estrutura simples, onde o caminho deve começar com AddressBookHomeSetPath acima (e terminar com uma barra)

GetAddressObject e ListAddressObjects devem verificar o caminho atual (para garantir que o usuário atualmente autenticado possa acessar esses contatos) e então retornar os contatos como AddressObject.

EndereçoObject

O AddressObject tem vários atributos, o mais importante:

  • o caminho para identificar esse contato específico (pode ser arbitrário, começar com uma barra)
  • a ETag para permitir que o cliente verifique rapidamente se alguma atualização aconteceu (se você esquecer, o iOS não mostrará nada)
  • o cartão que espera um VCard

O VCard representa os dados de contato reais e provavelmente deve ser adaptado dependendo de como você armazena seus contatos. No meu caso, terminou assim:

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
}

Usando o atalho somente leitura

Alguns métodos permitem atualizar um contato. Como não quero que minha lista de membros seja atualizada via CardDAV, retorno um erro 403 para os métodos Put e Delete: return webdav.NewHTTPError(http.StatusForbidden, erros.New("carddav: operação não suportada"))

Testando localmente

iOS requer que o servidor CardDAV sirva por https. Você pode gerar certificados autoassinados localmente usando openssl (substitua 192.168.XXX.XXX pelo seu endereço IP) para serem inseridos em 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

Depois disso, você poderá experimentar localmente adicionando uma "conta de contato CardDAV" apontando para seu próprio endereço IP e porta.

Conclusão

Implementar um servidor CardDAV em Go é um pouco complicado, mas claramente vale a pena: seus contatos estarão automaticamente sincronizados com os dados que você tem no servidor da sua organização!

Você conhece outros protocolos legais que permitem esse tipo de integração nativa? Sinta-se à vontade para compartilhar suas experiências!

Declaração de lançamento Este artigo está reproduzido em: https://dev.to/cmdscale/how-to-synchronize-your-contacts-with-your-phone-implemeting-carddav-in-go-9ia?1 Se houver alguma violação, por favor entre em contato com study_golang@163 .comdelete
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3