تخطَّ إلى المستندات
API

الصلاحيات

ما يُسمح للمفتاح بفعله.

المفردات

مجموعة مغلقة، بصيغة resource:action. صغيرة بما يكفي لعرضها على إنسان في قائمة مربعات اختيار، وثابتة بما يكفي ليظل المنح المخزَّن يعني الشيء نفسه بعد عام. والمفردات نفسها هي ما يُكتب به دور مساحة العمل وما يحكم أدوات MCP، فالعميل المقصور على القراءة لا يستطيع حتى رؤية أداة إرسال. أبجدية واحدة، وثلاثة أسطح.

الصلاحيةتمنح
emails:sendإرسال البريد الإلكتروني
emails:readقراءة الرسائل المرسَلة وحالة تسليمها
drafts:readقراءة المسودات
drafts:writeإنشاء المسودات وتحريرها
threads:readقراءة السلاسل والرسائل
threads:writeتسمية السلاسل وقراءتها وأرشفتها
labels:readقراءة التسميات
labels:writeإنشاء التسميات وتحريرها
contacts:readقراءة جهات الاتصال
contacts:writeإضافة جهات الاتصال وتحريرها وإزالتها
audiences:readقراءة الجماهير ومن فيها
audiences:writeإنشاء الجماهير وتحريرها، وتغيير من فيها
calendar:readقراءة أحداث التقويم والدعوات
calendar:writeإنشاء أحداث التقويم وتغييرها والرد عليها
templates:readقراءة قوالب البريد ومعاينتها
templates:writeإنشاء قوالب البريد وتحريرها والإرسال بها
domains:readقراءة النطاقات وحالة DNS الخاصة بها
domains:writeتوثيق النطاقات وتهيئتها
webhooks:readقراءة نقاط نهاية الويب هوك وعمليات التسليم
webhooks:writeإنشاء الويب هوك وتحريره واختباره
rules:readقراءة قواعد البريد واختبارها
rules:writeإنشاء قواعد البريد وتحريرها وإعادة ترتيبها
connections:readقراءة أي صناديق بريد متصلة
members:readرؤية من في مساحة العمل وما يحمله
members:writeإضافة الأشخاص وإزالتهم، وتغيير ما يمكنهم الوصول إليه
roles:readقراءة الأدوار التي تعرّفها مساحة العمل هذه
roles:writeإنشاء الأدوار وتحريرها وحذفها
settings:readقراءة إعدادات صندوق البريد، بما فيها التوقيع
settings:writeتغيير إعدادات صندوق البريد والتوقيع
keys:writeاستبدال سرّه الخاص دون أن يفتح أحد لوحة التحكم

المفتاح المنشأ بلا قائمة صلاحيات مدروسة يحصل على emails:send ولا شيء غيرها. فالافتراض الآمن لبيانات اعتماد هو أضيق ما يجعلها مفيدة.

المفتاح مُقيَّد بالدور الذي خلفه

يمكن إصدار المفتاح مقابل دور، والدور سقف لا منح ثانٍ. فما يُسمح للمفتاح بفعله فعلًا هو صلاحياته هو متقاطعةً مع أذونات ذلك الدور (key.scopes ∩ role.permissions)، محسوبةً مرة واحدة عند الحدود، في كل طلب، قبل بلوغ أي نقطة نهاية. ولا شيء في المصب يعلم بوجود الأدوار: فالصلاحية التي لا يحملها الدور ببساطة ليست في القائمة التي تقرؤها فحوص الصلاحيات.

فالقائمتان تُقرآن معًا ولا تفوز إحداهما وحدها. فالمفتاح الحامل emails:send تحت دور لا يحملها لا يُسمح له بالإرسال؛ والدور الحامل emails:send لا يعطي شيئًا لمفتاح لم يطلبها قط. فتحديد صلاحية هو طلب سلطة، والدور يقرر كم ستنال مما طلبت.

المفتاح بلا دور بلا سقف، وهو لذلك بسعة مساحة العمل التي صدر مقابلها. وهذا ما يحمله كل مفتاح أُنشئ قبل وجود الأدوار وما يناله المالك بترك الحقل على حاله، فالدور الفارغ هو أوسع حالة يمكن أن يكون عليها مفتاح لا أضيقها. وهو أيضًا سبب أن حذف دور يُلزمك بقول أين تذهب مفاتيحه: فإيتامها كان سيرقّي كلًّا منها بصمت.

يُحسَم التقاطع لكل طلب بدل ختمه على المفتاح وقت الإصدار. وهذا يجعل تضييق دور سحبًا حيًّا، ساريًا على النداء التالي للمُنادي دون الحاجة إلى تدوير المفتاح، ويجعل توسيعه حيًّا بالطريقة نفسها تمامًا، وهو النصف الجدير بالتذكّر.

GET /ping وGET /keys/self يُبلغان بـ scopes إلى جانب grantedScopes وroleId لأجل إخفاق واحد بعينه. فـ scopes هي القائمة الفعّالة والوحيدة التي تُفوّض أي شيء؛ وgrantedScopes هي ما صدر به المفتاح. وأي شيء في الثانية وغائب عن الأولى أخذه الدور، وذلك الفرق هو الجواب الكامل عن «مفتاحي يحمل emails:send وأنا أحصل على insufficient_scope». والحل تغيير الدور لا مفتاح آخر.

مفتاح ضيّقه الدور
curl "$OE/ping" -H "$AUTH" {  "ok": true,  "keyId": "4c1b257a66287fd113bd89d0",  "mode": "live",  "scopes": ["emails:read", "threads:read"],  "roleId": "role_c40a95f21cc65d31c2a89e07",  "grantedScopes": ["emails:send", "emails:read", "threads:read"],  "workspaceId": "10417196-e324-4283-af98-66ec62167c47"}

خمسة أذونات لا يمكن أن تصل إلى مفتاح إطلاقًا: api-keys:read وapi-keys:write وbilling:read وbilling:write وworkspace:manage. فهي أذونات لا صلاحيات، فلا يستطيع أي دور مهما كان سخيًّا وضعها على رمز: فصكّ مفتاح آخر، أو تغيير ما يُسمح لمفتاح آخر بفعله، أو نقل الخطة، شيء لا يفعله إلا شخص مسجَّل الدخول. والشيء الوحيد الذي يُسمح للمفتاح بفعله بنفسه هو استبدال سرّه الخاص، خلف الصلاحية keys:write. وGET /roles/permissions يضع على الخمسة scope: false، وهو ما يتيح لمكوّن واحد أن يرسم مصفوفة الأدوار وقائمة مربعات إنشاء المفتاح معًا.

roles:write هي عمليًا المفردات كلها، والتظاهر بغير ذلك كان سيصير التوثيق الأخطر. فالمفتاح الذي يحملها يستطيع تعديل الدور نفسه الذي يقيّده بـ PATCH ويمنح نفسه كل شيء آخر، ولأن السقف يُحسَم لكل طلب فإن الأوسع يسري على النداء التالي مباشرة. وهذه ليست ثغرة تُسَد، لأن محرر أدوار لا يستطيع تحرير الأدوار ليس محرر أدوار. بل هي سبب ألّا تضع roles:write على مفتاح لم يكن يحتاج إلا إلى قراءة قائمة الأعضاء.

نطاق الإرسال

بمعزل عن الصلاحيات، يمكن تضييق ما يُسمح للمفتاح بالإرسال باسمه. فهو يحمل قائمتين. domainAllowlist تحمل نطاقات كاملة، والمفتاح الحامل نطاقًا يستطيع الإرسال باسم أي عنوان عليه، بما في ذلك عناوين أُنشئت بعد المفتاح. وaddressAllowlist تحمل عناوين مفردة. اترك الاثنتين فارغتين ويكون المفتاح بسعة مساحة العمل، لا أوسع أبدًا. وGET /keys/self يعرض القائمتين، وGET /addresses يُبلغ بما يُسمح لمفتاح معين باستخدامه فعلًا، وهو الجواب عن from_address_forbidden غير المفسَّر.

والمجموعة نفسها تضيّق ما يقرؤه المفتاح. فالبريد المرسَل والتتبع والتقويم لا تجيب إلا عن العناوين التي يُسمح للمفتاح بالإرسال باسمها، فمفتاح محصور بنطاق واحد لا يرسل ولا يقرأ نيابةً عن نطاق آخر. والنطاق الكامل يتيح للمفتاح أيضًا ضبط مضيف التتبع لذلك النطاق، وهو ما لا يستطيعه مفتاح محصور بعناوين مفردة.

ثلاثة تضييقات إذن، وهي تتركّب لا يلغي بعضها بعضًا: صلاحيات المفتاح، وأذونات الدور فوقه، والنطاقات والعناوين التي يُسمح له بوضعها في ترويسة From. والإرسال يحتاج الثلاثة، والرفض يسمّي أول ما صادفه منها فقط.