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.
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)) }) }
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.
O AddressObject tem vários atributos, o mais importante:
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 }
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"))
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.
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!
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