Disons que vous aidez à gérer une petite organisation ou un club et que vous disposez d'une base de données stockant tous les détails des membres (noms, téléphone, email...).
Ne serait-il pas agréable d'avoir accès à ces informations actualisées partout où vous en avez besoin ? Eh bien, avec CardDAV, vous pouvez !
CardDAV est un standard ouvert bien pris en charge pour la gestion des contacts ; il dispose d'une intégration native dans l'application iOS Contacts et de nombreuses applications disponibles pour Android.
Côté serveur, l'implémentation de CardDAV est un serveur http qui répond aux méthodes http inhabituelles (PROPFIND, REPORT au lieu de GET, POST...). Heureusement il existe un module Go pour simplifier grandement le travail : github.com/emersion/go-webdav. Cette bibliothèque attend un Backend implémenté et fournit un http.Handler standard qui devrait servir les requêtes HTTP après authentification.
Il est intéressant de noter que la bibliothèque ne fournit aucune aide concernant l'authentification des utilisateurs, mais grâce à la composabilité Go, ce n'est pas un problème.
CardDAV utilise les informations d'identification de base. Une fois les identifiants vérifiés, nous pouvons enregistrer ces identifiants dans le contexte (sera utile plus tard) :
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 structure ownBackend doit implémenter l'interface carddav.Backend, qui n'est pas très fine, mais toujours gérable.
CurrentUserPrincipal et AddressBookHomeSetPath doivent fournir des URL (commençant et se terminant par une barre oblique). Il s'agira généralement du nom d'utilisateur/des contacts. C'est ici que vous devez extraire le nom d'utilisateur du contexte (qui est le seul argument 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 }
Après cela, le plaisir peut commencer : vous devez implémenter les méthodes AddressBook, GetAddressObject et ListAddressObjects.
AddressBook renvoie une structure simple, où le chemin doit commencer par AddressBookHomeSetPath ci-dessus (et se terminer par une barre oblique)
GetAddressObject et ListAddressObjects doivent vérifier le chemin actuel (pour garantir que l'utilisateur actuellement authentifié peut accéder à ces contacts), puis renvoyer les contacts en tant qu'AddressObject.
L'AddressObject a plusieurs attributs, le plus important :
La VCard représente les données de contact réelles et doit probablement être adaptée en fonction de la façon dont vous stockez vos contacts. Dans mon cas, ça s'est terminé comme ça :
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 }
Certaines méthodes permettent de mettre à jour un contact. Comme je ne souhaite pas que ma liste de membres soit mise à jour via CardDAV, je renvoie une erreur 403 aux méthodes Put et Delete : return webdav.NewHTTPError(http.StatusForbidden, erreurs.New("carddav: opération non prise en charge"))
iOS nécessite que le serveur CardDAV serve via https. Vous pouvez générer des certificats auto-signés localement à l'aide d'openssl (remplacez 192.168.XXX.XXX par votre adresse IP) à insérer dans 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
Après cela, vous devriez pouvoir expérimenter localement en ajoutant un « compte de contact CardDAV » pointant vers votre propre adresse IP et votre propre port.
Implémenter un serveur CardDAV dans Go est un peu complexe, mais cela en vaut clairement la peine : vos contacts seront automatiquement synchronisés avec les données que vous avez sur le serveur de votre organisation !
Connaissez-vous d'autres protocoles sympas qui permettent ce type d'intégration native ? N'hésitez pas à partager vos expériences !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!