لنفترض أنك تساعد في إدارة منظمة صغيرة أو نادي ولديك قاعدة بيانات تخزن جميع تفاصيل الأعضاء (الأسماء، الهاتف، البريد الإلكتروني...).
ألن يكون من الرائع أن تتمكن من الوصول إلى هذه المعلومات المحدثة في كل مكان تحتاج إليها؟ حسنًا، مع CardDAV يمكنك ذلك!
يعد CardDAV معيارًا مفتوحًا مدعومًا جيدًا لإدارة جهات الاتصال؛ يحتوي على تكامل أصلي في تطبيق جهات اتصال iOS والعديد من التطبيقات المتاحة لنظام Android.
من جانب الخادم، تنفيذ CardDAV هو خادم http يستجيب لأساليب http غير العادية (PROPFIND، REPORT بدلاً من GET، POST...). لحسن الحظ، توجد وحدة Go لتبسيط العمل إلى حد كبير: github.com/emersion/go-webdav. تتوقع هذه المكتبة واجهة خلفية مطبقة وتوفر http.Handler قياسي والذي يجب أن يخدم طلبات HTTP بعد المصادقة.
ومن المثير للاهتمام أن المكتبة لا تقدم أي مساعدة فيما يتعلق بمصادقة المستخدم، ولكن بفضل قابلية التركيب Go، فإن هذه ليست مشكلة.
يستخدم CardDAV بيانات اعتماد المصادقة الأساسية. بمجرد التحقق من بيانات الاعتماد، يمكننا حفظ بيانات الاعتماد هذه في السياق (ستكون مفيدة لاحقًا):
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)) }) }
يجب أن تقوم البنية ownBackend بتنفيذ واجهة Carddav.Backend، وهي ليست رقيقة جدًا، ولكن لا تزال قابلة للإدارة.
يجب أن يوفر CurrentUserPrincipal وAddressBookHomeSetPath عناوين URL (تبدأ وتنتهي بشرطة مائلة). عادةً ما يكون اسم المستخدم/جهات الاتصال. هذا هو المكان الذي تحتاج فيه إلى استخراج اسم المستخدم من السياق (وهو الوسيط الوحيد المتاح):
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 }
بعد ذلك يمكن أن تبدأ المتعة: تحتاج إلى تنفيذ أساليب AddressBook وGetAddressObject وListAddressObjects.
يرجع AddressBook بنية بسيطة، حيث يجب أن يبدأ المسار بـ AddressBookHomeSetPath أعلاه (وينتهي بشرطة مائلة)
يجب على GetAddressObject وListAddressObjects التحقق من المسار الحالي (للتأكد من أن المستخدم المصادق عليه حاليًا يمكنه الوصول إلى جهات الاتصال هذه) ثم إرجاع جهات الاتصال كـ AddressObject.
يحتوي AddressObject على سمات متعددة، أهمها:
تمثل VCard بيانات جهة الاتصال الفعلية ومن المحتمل أن يتم تكييفها وفقًا لكيفية تخزين جهات الاتصال الخاصة بك. في حالتي، انتهى الأمر على النحو التالي:
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 }
تسمح بعض الطرق بتحديث جهة اتصال. نظرًا لأنني لا أريد تحديث قائمة الأعضاء الخاصة بي عبر CardDAV، أقوم بإرجاع خطأ 403 إلى أساليب الحذف والحذف: return webdav.NewHTTPERror(http.StatusForbidden, Errors.New("carddav: العملية غير مدعومة"))
يتطلب iOS أن يعمل خادم CardDAV عبر https. يمكنك إنشاء شهادات موقعة ذاتيًا محليًا باستخدام opensl (استبدل 192.168.XXX.XXX بعنوان IP الخاص بك) ليتم إدخالها في 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
بعد ذلك يجب أن تكون قادرًا على التجربة محليًا عن طريق إضافة "حساب اتصال CardDAV" يشير إلى عنوان IP الخاص بك والمنفذ.
يعد تنفيذ خادم CardDAV في Go أمرًا معقدًا بعض الشيء، ولكن من الواضح أنه يستحق ذلك: ستتم مزامنة جهات الاتصال الخاصة بك تلقائيًا مع البيانات الموجودة على خادم مؤسستك!
هل تعرف بروتوكولات رائعة أخرى تسمح بهذا النوع من التكامل الأصلي؟ لا تتردد في مشاركة تجاربك!
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3