Secure Data Connect مع التفويض والإقرار

توفّر Firebase Data Connect أمانًا قويًا من جهة العميل من خلال:

  • تفويض برامج الويب والأجهزة الجوّالة
  • عناصر التحكّم في أذونات الاستعلامات والتعديلات الفردية
  • خدمة "إثبات صحة التطبيق" باستخدام Firebase App Check

توفّر Data Connect وسائل الحماية الإضافية التالية:

  • تفويض من جهة الخادم
  • أمان مشاريع Firebase ومستخدمي Cloud SQL باستخدام IAM

تفويض طلبات البحث والتعديلات من العملاء

تتكامل Data Connect بشكل كامل مع Firebase Authentication، ما يتيح لك استخدام بيانات تفصيلية عن المستخدمين الذين يصلون إلى بياناتك (المصادقة) في تصميمك لتحديد البيانات التي يمكن لهؤلاء المستخدمين الوصول إليها (التفويض).

توفّر Data Connect توجيه @auth للاستعلامات والتعديلات يتيح لك ضبط مستوى المصادقة المطلوب لتفويض العملية. يقدّم هذا الدليل تعليمات @auth مع أمثلة.

بالإضافة إلى ذلك، تتيح Data Connect تنفيذ طلبات البحث المضمّنة في عمليات التغيير، ما يتيح لك استرداد معايير التفويض الإضافية التي خزّنتها في قاعدة البيانات واستخدام هذه المعايير في توجيهات @check لتحديد ما إذا كانت عمليات التغيير المضمّنة مسموحًا بها. في حالة التفويض هذه، تتيح لك توجيه @redact التحكّم في ما إذا كان سيتم عرض نتائج طلب البحث للعملاء في بروتوكول النقل السلكي وما إذا كان سيتم حذف طلب البحث المضمّن في حِزم تطوير البرامج (SDK) التي تم إنشاؤها. يمكنك الاطّلاع على مقدمة عن هذه التوجيهات مع أمثلة.

التعرّف على توجيه @auth

يمكنك تحديد مَعلمات للتوجيه @auth من أجل اتّباع أحد مستويات الوصول المتعدّدة المحدّدة مسبقًا والتي تغطي العديد من سيناريوهات الوصول الشائعة. تتراوح هذه المستويات من PUBLIC (الذي يسمح بالاستعلامات والتعديلات من جميع العملاء بدون أي نوع من المصادقة) إلى NO_ACCESS (الذي لا يسمح بالاستعلامات والتعديلات خارج بيئات الخادم ذات الامتيازات باستخدام حزمة تطوير البرامج (SDK) الخاصة بمشرف Firebase). ويرتبط كل مستوى من هذه المستويات بعمليات المصادقة التي توفّرها Firebase Authentication.

المستوى التعريف
PUBLIC يمكن لأي مستخدم تنفيذ العملية سواء كان قد أجرى المصادقة أم لا.
PUBLIC يمكن لأي مستخدم تنفيذ العملية سواء كان قد أجرى المصادقة أم لا.
USER_ANON يتم تفويض أي مستخدم تم تحديد هويته، بما في ذلك المستخدمون الذين سجّلوا الدخول بشكل مجهول باستخدام Firebase Authentication، لتنفيذ طلب البحث أو التعديل.
USER يُسمح لأي مستخدم سجّل الدخول باستخدام Firebase Authentication بإجراء طلب البحث أو التعديل، باستثناء المستخدمين الذين سجّلوا الدخول بدون الكشف عن هويتهم.
USER_EMAIL_VERIFIED يُسمح لأي مستخدم سجّل الدخول باستخدام Firebase Authentication وعنوان بريد إلكتروني تم إثبات ملكيته بإجراء طلب البحث أو التعديل.
NO_ACCESS لا يمكن تنفيذ هذه العملية خارج سياق حزمة تطوير البرامج (SDK) الخاصة بالمشرف.

باستخدام مستويات الوصول المحدّدة مسبقًا كنقطة بداية، يمكنك تحديد عمليات تحقّق معقّدة وقوية من الأذونات في التوجيه @auth باستخدام فلاتر where وتعبيرات "لغة التعبير الشائعة" (CEL) التي يتم تقييمها على الخادم.

استخدِم التوجيه @auth لتنفيذ سيناريوهات الترخيص الشائعة

مستويات الوصول المضبوطة مسبقًا هي نقطة البداية للحصول على إذن.

مستوى الوصول USER هو المستوى الأساسي الأكثر فائدة للبدء.

سيستند الوصول الآمن تمامًا إلى المستوى USER بالإضافة إلى الفلاتر والعبارات التي تتحقّق من سمات المستخدم وسمات الموارد والأدوار وعمليات التحقّق الأخرى. المستويان USER_ANON وUSER_EMAIL_VERIFIED هما صيغتان مختلفتان من حالة USER.

يتيح لك بناء جملة التعبير تقييم البيانات باستخدام عنصر auth يمثّل بيانات المصادقة التي يتم تمريرها مع العمليات، بما في ذلك البيانات العادية في رموز المصادقة والبيانات المخصّصة في الرموز. للاطّلاع على قائمة الحقول المتاحة في عنصر auth، راجِع قسم المراجع.

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

يقدّم هذا الدليل الآن أمثلة على كيفية الاستفادة من USER وPUBLIC.

مثال توضيحي

تشير أمثلة أفضل الممارسات التالية إلى المخطط التالي لمنصة تدوين تتضمّن محتوًى محظورًا بموجب خطة دفع.

من المرجّح أن يقدّم هذا النوع من المنصات نموذجًا لـ Users وPosts.

type User @table(key: "uid") {
  uid: String!
  name: String
  birthday: Date
  createdAt: Timestamp! @default(expr: "request.time")
}

type Post @table {
  author: User!
  text: String!
  # "one of 'draft', 'public', or 'pro'"
  visibility: String! @default(value: "draft")
  # "the time at which the post should be considered published. defaults to
  # immediately"
  publishedAt: Timestamp! @default(expr: "request.time")
  createdAt: Timestamp! @default(expr: "request.time")
  updatedAt: Timestamp! @default(expr: "request.time")
}

المراجع التي يملكها المستخدم

تنصح Firebase بكتابة فلاتر وتعبيرات تختبر ملكية المستخدم لمورد، وفي الحالات التالية، ملكية Posts.

في الأمثلة التالية، تتم قراءة البيانات من رموز المصادقة المميزة ومقارنتها باستخدام التعبيرات. النمط المعتاد هو استخدام عبارات مثل where: {authorUid: {eq_expr: "auth.uid"}} لمقارنة authorUid مخزّنة مع auth.uid (رقم تعريف المستخدم) الذي تم تمريره في رمز المصادقة المميز.

إنشاء

تبدأ ممارسة التفويض هذه بإضافة auth.uid من رمز التفويض إلى كل Post جديد كحقل authorUid للسماح بالمقارنة في اختبارات التفويض اللاحقة.

# Create a new post as the current user
mutation CreatePost($text: String!, $visibility: String) @auth(level: USER) {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}
تعديل

عندما يحاول أحد العملاء تعديل Post، يمكنك اختبار auth.uid الذي تم تمريره مقابل authorUid المخزّن.

# Update one of the current user's posts
mutation UpdatePost($id: UUID!, $text: String, $visibility: String) @auth(level:USER) {
  post_update(
    # only update posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
    data: {
      text: $text
      visibility: $visibility
      # insert the current server time for updatedAt
      updatedAt_expr: "request.time"
    }
  )
}
حذف

يتم استخدام الأسلوب نفسه لتفويض عمليات الحذف.

# Delete one of the current user's posts
mutation DeletePost($id: UUID!) @auth(level: USER) {
  post_delete(
    # only delete posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
  )
}
# Common display information for a post
fragment DisplayPost on Post {
  id, text, createdAt, updatedAt
  author { uid, name }
}
قائمة
# List all posts belonging to the current user
query ListMyPosts @auth(level: USER) {
  posts(where: {
    userUid: {eq_expr: "auth.uid"}
  }) {
    # See the fragment above
    ...DisplayPost
    # also show visibility since it is user-controlled
    visibility
  }
}
جلب
# Get a post only if it belongs to the current user
query GetMyPost($id: UUID!) @auth(level: USER) {
  post(key: {id: $id},
    first: {where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}}
      }}, {
      # See the fragment above
      ...DisplayPost
      # also show visibility since it is user-controlled
      visibility
  }
}

فلترة البيانات

يتيح لك نظام التفويض في Data Connect كتابة فلاتر معقّدة مدمجة مع مستويات وصول مُعدّة مسبقًا، مثل PUBLIC، بالإضافة إلى استخدام البيانات من رموز المصادقة المميزة.

يتيح لك نظام منح الأذونات أيضًا استخدام التعبيرات فقط، بدون مستوى وصول أساسي، كما هو موضّح في بعض الأمثلة التالية.

الفلترة حسب سمات الموارد

في هذه الحالة، لا تستند المصادقة إلى رموز المصادقة المميزة لأنّ مستوى الأمان الأساسي تم ضبطه على PUBLIC. ولكن، يمكننا ضبط السجلات في قاعدة البيانات بشكل صريح على أنّها مناسبة للوصول العام، ولنفترض أنّ لدينا Post سجلاً في قاعدة البيانات مع ضبط visibility على "عام".

# List all posts marked as 'public' visibility
query ListPublicPosts @auth(level: PUBLIC) {
  posts(where: {
    # Test that visibility is "public"
    visibility: {eq: "public"}
    # Only display articles that are already published
    publishedAt: {lt_expr: "request.time"}
  }) {
    # see the fragment above
    ...DisplayPost
  }
}
الفلترة حسب مطالبات المستخدم

لنفترض هنا أنّك أعددت مطالبات مخصّصة للمستخدمين يتم تمريرها في رموز مميّزة للمصادقة من أجل تحديد المستخدمين في خطة "احترافية" لتطبيقك، ويتم تمييزها بحقل auth.token.plan في رمز المصادقة المميّز. يمكن اختبار التعبيرات في هذا الحقل.

# List all public or pro posts, only permitted if user has "pro" plan claim
query ProListPosts @auth(expr: "auth.token.plan == 'pro'") {
  posts(where: {
    # display both public posts and "pro" posts
    visibility: {in: ['public', 'pro']},
    # only display articles that are already published
    publishedAt: {lt_expr: "request.time"},
  }) {
    # see the fragment above
    ...DisplayPost
    # show visibility so pro users can see which posts are pro\
    visibility
  }
}
الفلترة حسب الترتيب والحدّ

أو قد تكون قد ضبطت visibility في سجلّات Post لتحديد أنّها محتوى متاح للمستخدمين "المحترفين"، ولكن لعرض معاينة أو إعلان تشويقي عن البيانات، يمكنك فرض قيود إضافية على عدد السجلّات التي يتم عرضها.

# Show 2 oldest Pro post as a preview
query ProTeaser @auth(level: USER) {
  posts(
    where: {
      # show only pro posts
      visibility: {eq: "pro"}
      # that have already been published more than 30 days ago
      publishedAt: {lt_time: {now: true, sub: {days: 30}}}
    },
    # order by publish time
    orderBy: [{publishedAt: DESC}],
    # only return two posts
    limit: 2
  ) {
    # See the fragment above
    ...DisplayPost
  }
}
الفلترة حسب الدور

إذا كان الادّعاء المخصّص يحدّد دور admin، يمكنك اختبار العمليات ومنح الإذن بها وفقًا لذلك.

# List all posts unconditionally iff the current user has an admin claim
query AdminListPosts @auth(expr: "auth.token.admin == true") {
  posts { ...DisplayPost }
}

أضِف التوجيهَين @check و@redact للبحث عن بيانات التفويض

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

باستخدام عمليات البحث عن بيانات التفويض، يمكنك طلب أدوار استنادًا إلى معرّف المستخدم واستخدام تعبيرات CEL لتحديد ما إذا كان التغيير مسموحًا به. على سبيل المثال، قد تحتاج إلى كتابة عملية تغيير UpdateMovieTitle تتيح لعميل معتمَد تعديل عناوين الأفلام.

في بقية هذه المناقشة، سنفترض أنّ قاعدة بيانات تطبيق مراجعات الأفلام تخزّن دور تفويض في جدول MoviePermission.

# MoviePermission
# Suppose a user has an authorization role with respect to records in the Movie table
type MoviePermission @table(key: ["movie", "user"]) {
  movie: Movie! # implies another field: movieId: UUID!
  user: User!
  role: String!
}

الاستخدام في عمليات التغيير

في مثال التنفيذ التالي، يتضمّن التغيير UpdateMovieTitle الحقل query لاسترداد البيانات من MoviePermission، كما يتضمّن التوجيهات التالية لضمان أنّ العملية آمنة وفعّالة:

  • توجيه @transaction لضمان إكمال جميع طلبات التفويض وعمليات التحقّق أو تعذّرها بشكل ذري.
  • توجيه @redact لحذف نتائج طلب البحث من الردّ، ما يعني أنّه يتم إجراء عملية التحقّق من الإذن على خادم Data Connect ولكن لا يتم عرض البيانات الحسّاسة للعميل.
  • زوج من توجيهات @check لتقييم منطق التفويض بشأن نتائج طلب البحث، مثل اختبار ما إذا كان معرّف المستخدم المحدّد لديه الدور المناسب لإجراء تعديلات.

mutation UpdateMovieTitle($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    # Step 1a: Use @check to test if the user has any role associated with the movie
    # Here the `this` binding refers the lookup result, i.e. a MoviePermission object or null
    # The `this != null` expression could be omitted since rejecting on null is default behavior
    ) @check(expr: "this != null", message: "You do not have access to this movie") {
      # Step 1b: Check if the user has the editor role for the movie
      # Next we execute another @check; now `this` refers to the contents of the `role` field
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

الاستخدام في طلبات البحث

عمليات البحث عن بيانات التفويض مفيدة أيضًا في حصر طلبات البحث استنادًا إلى الأدوار أو القيود الأخرى.

في المثال التالي، الذي يستخدم أيضًا مخطط MoviePermission، يتحقّق طلب البحث ممّا إذا كان مقدّم الطلب لديه دور "مشرف" مناسب لعرض المستخدمين الذين يمكنهم تعديل فيلم.

query GetMovieEditors($movieId: UUID!) @auth(level: PUBLIC) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

أنماط مضادة يجب تجنُّبها في التفويض

يتناول القسم السابق الأنماط التي يجب اتّباعها عند استخدام توجيه @auth.

عليك أيضًا أن تكون على دراية بأنماط التصميم السيئة المهمة التي يجب تجنُّبها.

تجنُّب تمرير معرّفات سمات المستخدمين ومَعلمات رمز المصادقة في وسيطات طلب البحث والتعديل

Firebase Authentication هي أداة فعّالة لعرض مسارات المصادقة والتقاط بيانات المصادقة بشكل آمن، مثل أرقام تعريف المستخدمين المسجّلين والعديد من الحقول المخزّنة في رموز المصادقة.

لا يُنصح بتمرير معرّفات المستخدمين وبيانات رمز المصادقة المميزة في وسيطات طلبات البحث والتعديل.

# Antipattern!
# This incorrectly allows any user to view any other user's posts
query AllMyPosts($userId: String!) @auth(level: USER) {
  posts(where: {authorUid: {eq: $userId}}) {
    id, text, createdAt
  }
}

تجنُّب استخدام مستوى الوصول USER بدون أي فلاتر

كما ذكرنا عدة مرات في الدليل، فإنّ مستويات الوصول الأساسية، مثل USER وUSER_ANON وUSER_EMAIL_VERIFIED، هي نقاط أساسية وبداية لعمليات التحقّق من الأذونات، ويجب تحسينها باستخدام الفلاتر والعبارات. إنّ استخدام هذه المستويات بدون فلتر أو تعبير مطابق يتحقّق من المستخدم الذي ينفّذ الطلب يعادل بشكل أساسي استخدام المستوى PUBLIC.

# Antipattern!
# This incorrectly allows any user to view all documents
query ListDocuments @auth(level: USER) {
  documents {
    id
    title
    text
  }
}

تجنُّب استخدام مستوى الوصول PUBLIC أو USER لإنشاء نماذج أولية

لتسريع عملية التطوير، قد يكون من المغري ضبط جميع العمليات على مستوى الوصول PUBLIC أو مستوى الوصول USER بدون إجراء تحسينات إضافية، وذلك من أجل تفويض جميع العمليات والسماح لك باختبار الرمز البرمجي بسرعة.

بعد الانتهاء من إنشاء النموذج الأوّلي الأولي بهذه الطريقة، ابدأ في التبديل من NO_ACCESS إلى نظام تفويض جاهز للإنتاج باستخدام المستويَين PUBLIC وUSER. ومع ذلك، لا تنشرها كـ PUBLIC أو USER بدون إضافة منطق إضافي كما هو موضّح في هذا الدليل.

# Antipattern!
# This incorrectly allows anyone to delete any post
mutation DeletePost($id: UUID!) @auth(level: PUBLIC) {
  post: post_delete(
    id: $id,
  )
}

تجنُّب منح الإذن استنادًا إلى عناوين بريد إلكتروني لم يتم التحقّق منها

يُعدّ منح إذن الوصول إلى المستخدمين في نطاق معيّن طريقة رائعة للحدّ من إمكانية الوصول. ومع ذلك، يمكن لأي مستخدم الادّعاء بملكية عنوان بريد إلكتروني أثناء تسجيل الدخول. احرِص على منح الإذن بالوصول إلى عناوين البريد الإلكتروني التي تم إثبات ملكيتها من خلال خدمة Firebase Authentication فقط.

# Antipattern!
# Anyone can claim an email address during sign-in
mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

الاطّلاع أيضًا على auth.token.email_verified

mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email_veri&&fied  auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

تدقيق التفويض باستخدام Firebase CLI

كما هو موضّح سابقًا، فإنّ مستويات الوصول المحدّدة مسبقًا، مثل PUBLIC وUSER، هي نقطة البداية للحصول على تفويض قوي، ويجب استخدامها مع عمليات تحقّق إضافية من التفويض تستند إلى الفلاتر والتعبيرات. ولا ينبغي استخدامها بمفردها بدون التفكير مليًا في حالة الاستخدام.

تساعدك Data Connect في تدقيق استراتيجية التفويض من خلال تحليل رمز الموصل عند النشر على الخادم باستخدام firebase deploy من واجهة سطر الأوامر Firebase. يمكنك استخدام عملية التدقيق هذه لمساعدتك في مراجعة قاعدة الرموز البرمجية.

عند نشر الموصلات، ستعرض واجهة سطر الأوامر تقييمات لرموز العمليات الحالية والمعدَّلة والجديدة في الموصل.

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

تظهر التحذيرات والإشعارات دائمًا في الحالات التالية:

  • PUBLIC

وتظهر التحذيرات والإشعارات على مستويات الوصول التالية عندما لا يتم تحسينها باستخدام الفلاتر التي تستخدم auth.uid:

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

إيقاف تحذيرات العمليات غير الآمنة باستخدام الوسيطة @auth(insecureReason:)

في كثير من الحالات، ستستنتج أنّ استخدام مستوىَي الوصول PUBLIC وUSER* هو الخيار الأنسب.

عندما يحتوي الموصل على العديد من العمليات، قد تحتاج إلى ناتج تدقيق أمني أكثر وضوحًا وملاءمةً يستبعد العمليات التي قد تؤدي عادةً إلى ظهور تحذير، ولكنك تعرف أنّ لديك مستوى الوصول الصحيح.

يمكنك إيقاف التحذيرات لهذه العمليات باستخدام @auth(insecureReason:). على سبيل المثال:

query listItem @auth(level: PUBLIC, insecureReason: "This operation is safe to expose to the public.")
  {
    items {
      id name
    }
  }

استخدام Firebase App Check لإثبات صحة التطبيق

تُعد المصادقة والتفويض من المكوّنات المهمة في Data Connect الأمان. تُعد المصادقة والترخيص مع ميزة "إثبات صحة التطبيق" حلاً أمنيًا قويًا للغاية.

من خلال خدمة المصادقة Firebase App Check، ستستخدم الأجهزة التي يشغّل تطبيقك عليها موفّر خدمة مصادقة للتطبيقات أو الأجهزة يثبت أنّ عمليات Data Connect صادرة من تطبيقك الأصلي وأنّ الطلبات صادرة من جهاز أصلي لم يتم التلاعب به. يتم إرفاق شهادة التصديق هذه بكل طلب يقدّمه تطبيقك إلى Data Connect.

للتعرّف على كيفية تفعيل App Check لـ Data Connect وتضمين حزمة تطوير البرامج (SDK) الخاصة بالعميل في تطبيقك، يمكنك الاطّلاع على App Check نظرة عامة.

مستويات المصادقة لتوجيه @auth(level)

يسرد الجدول التالي جميع مستويات الوصول العادية وما يعادلها من تعبيرات CEL. يتم إدراج مستويات المصادقة من الأوسع إلى الأضيق، ويشمل كل مستوى جميع المستخدمين الذين يستوفون المستويات التالية.

المستوى التعريف
PUBLIC يمكن لأي مستخدم تنفيذ العملية سواء كان قد أجرى المصادقة أم لا.

اعتبارات: يمكن لأي مستخدم قراءة البيانات أو تعديلها. تنصح Firebase بهذا المستوى من التفويض للبيانات التي يمكن تصفّحها بشكل علني، مثل بيانات المنتجات أو الوسائط. اطّلِع على أمثلة على أفضل الممارسات والبدائل.

لا يمكن استخدام فلاتر وتعابير @auth(expr: "true")

@auth معًا مع مستوى الوصول هذا. سيؤدي استخدام أي من هذه التعبيرات إلى ظهور رسالة الخطأ 400 Bad Request.
USER_ANON يتم تفويض أي مستخدم تم تحديد هويته، بما في ذلك المستخدمون الذين سجّلوا الدخول بشكل مجهول باستخدام Firebase Authentication، لتنفيذ طلب البحث أو التعديل.

ملاحظة: USER_ANON هي مجموعة شاملة من USER.

اعتبارات: يجب تصميم طلبات البحث وعمليات التعديل بعناية لهذا المستوى من التفويض. يسمح هذا المستوى للمستخدم بتسجيل الدخول بشكل مجهول (تسجيل الدخول التلقائي مرتبط فقط بجهاز المستخدم) باستخدام Authentication، ولا يجري بمفرده عمليات تحقّق أخرى، مثلاً، ما إذا كانت البيانات تخص المستخدم. اطّلِع على أمثلة على أفضل الممارسات والبدائل.

بما أنّ عمليات تسجيل الدخول بدون الكشف عن الهوية في Authentication تصدر uid، فإنّ مستوى USER_ANON يعادل
@auth(expr: "auth.uid != nil")
USER يُسمح لأي مستخدم سجّل الدخول باستخدام Firebase Authentication بإجراء طلب البحث أو التعديل، باستثناء المستخدمين الذين سجّلوا الدخول بدون الكشف عن هويتهم.

اعتبارات: يجب تصميم طلبات البحث وعمليات التعديل بعناية لهذا المستوى من التفويض. لا يتحقّق هذا المستوى إلا من أنّ المستخدم سجّل الدخول باستخدام Authentication، ولا يجري بمفرده عمليات تحقّق أخرى، مثلاً، ما إذا كانت البيانات تخص المستخدم. اطّلِع على أمثلة على أفضل الممارسات والبدائل.

تعادل @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED يُسمح لأي مستخدم سجّل الدخول باستخدام Firebase Authentication وعنوان بريد إلكتروني تم إثبات ملكيته بإجراء طلب البحث أو التعديل.

اعتبارات: بما أنّ عملية إثبات ملكية عنوان البريد الإلكتروني تتم باستخدام Authentication، فهي تستند إلى طريقة Authentication أكثر فعالية، وبالتالي يوفّر هذا المستوى أمانًا إضافيًا مقارنةً بالمستويَين USER أو USER_ANON. لا يتحقّق هذا المستوى إلا من أنّ المستخدم سجّل الدخول باستخدام Authentication مع عنوان بريد إلكتروني تم إثبات ملكيته، ولا يجري وحده عمليات تحقّق أخرى، مثلاً، ما إذا كانت البيانات تخص المستخدم. اطّلِع على أمثلة على أفضل الممارسات والبدائل.

تعادل @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS لا يمكن تنفيذ هذه العملية خارج سياق حزمة تطوير البرامج (SDK) الخاصة بالمشرف.

تعادل @auth(expr: "false")

مرجع CEL لـ @auth(expr)

كما هو موضّح في الأمثلة في أماكن أخرى من هذا الدليل، يمكنك ويجب استخدام التعبيرات المحدّدة في "لغة التعبير الشائعة" (CEL) للتحكّم في أذونات Data Connect باستخدام توجيهات @auth(expr:) و@check.

يتناول هذا القسم بناء جملة CEL ذي الصلة بإنشاء تعبيرات لهذه التوجيهات.

تتوفّر معلومات مرجعية كاملة حول CEL في مواصفات CEL.

اختبار المتغيّرات التي تم تمريرها في الاستعلامات وعمليات التعديل

تتيح لك بنية @auth(expr) الوصول إلى المتغيرات واختبارها من الاستعلامات وعمليات التعديل.

على سبيل المثال، يمكنك تضمين متغيّر عملية، مثل $status، باستخدام vars.status.

mutation Update($id: UUID!, $status: Any) @auth(expr: "has(vars.status)")

البيانات المتاحة للتعبيرات: الطلب والاستجابة و"هذا"

يمكنك استخدام البيانات في ما يلي:

  • التقييم باستخدام تعابير CEL في التوجيهَين @auth(expr:) و@check(expr:)
  • تعيين باستخدام تعبيرات الخادم، <field>_expr

يمكن لكلّ من تعبيرَي CEL @auth(expr:) و@check(expr:) تقييم ما يلي:

  • request.operationName
  • vars (الاسم المستعار لـ request.variables)
  • auth (الاسم المستعار لـ request.auth)

في عمليات التغيير، يمكنك الوصول إلى محتوى ما يلي وتعيينه:

  • response (للتحقّق من النتائج الجزئية في منطق الخطوات المتعدّدة)

بالإضافة إلى ذلك، يمكن لتعبيرات @check(expr:) تقييم ما يلي:

  • this (قيمة الحقل الحالي)
  • response (للتحقّق من النتائج الجزئية في منطق الخطوات المتعدّدة)

ربط request.operationName

يخزّن الربط request.operarationName نوع العملية، إما طلب بحث أو تعديل.

ربط vars (request.vars)

تسمح عملية الربط vars للتعبيرات بالوصول إلى جميع المتغيرات التي تم تمريرها في الاستعلام أو التعديل.

يمكنك استخدام vars.<variablename> في تعبير كاسم مستعار لـ request.variables.<variablename> المؤهَّل بالكامل:

# The following are equivalent
mutation StringType($v: String!) @auth(expr: "vars.v == 'hello'")
mutation StringType($v: String!) @auth(expr: "request.variables.v == 'hello'")

الربط auth (request.auth)

تحدّد Authentication المستخدمين الذين يطلبون الوصول إلى بياناتك وتوفّر هذه المعلومات كعنصر ربط يمكنك الاستناد إليه في عباراتك.

في الفلاتر والعبارات، يمكنك استخدام auth كاسم مستعار لـ request.auth.

يحتوي ربط المصادقة على المعلومات التالية:

  • uid: معرّف مستخدم فريد، يتمّ تعيينه للمستخدم الذي يقدّم الطلب.
  • token: خريطة للقيم التي يتم جمعها بواسطة Authentication

لمزيد من التفاصيل حول محتوى auth.token، يُرجى الاطّلاع على البيانات في رموز المصادقة.

عملية ربط response

يحتوي الربط response على البيانات التي يجمعها الخادم استجابةً لطلب بحث أو تغيير أثناء تجميع هذه البيانات.

أثناء تقدّم العملية، وعند اكتمال كل خطوة بنجاح، سيحتوي response على بيانات الاستجابة من الخطوات التي تم إكمالها بنجاح.

يتم تنظيم عملية الربط response وفقًا لشكل العملية المرتبطة بها، بما في ذلك الحقول المتداخلة (المتعددة) والاستعلامات المضمّنة (إن وُجدت).

يُرجى العِلم أنّه عند الوصول إلى بيانات الردود على طلبات البحث المضمّنة، يمكن أن تحتوي الحقول على أي نوع من البيانات، وذلك حسب البيانات المطلوبة في طلب البحث المضمّن. وعند الوصول إلى البيانات التي تعرضها حقول التعديل، مثل _insert و_delete، قد تحتوي على مفاتيح UUID وعدد عمليات الحذف والقيم الخالية (راجِع مرجع عمليات التعديل).

على سبيل المثال:

  • في عملية تغيير تتضمّن طلب بحث مضمّنًا، يحتوي الربط على بيانات البحث في response.query.<fieldName>.<fieldName>....، وفي هذه الحالة، response.query.todoList وresponse.query.todoList.priority.response
mutation CheckTodoPriority(
  $uniqueListName: String!
) {
  # This query is identified as `response.query`
  query @check(expr: "response.query.todoList.priority == 'high'", message: "This list is not for high priority items!") {
    # This field is identified as `response.query.todoList`
    todoList(where: { name: $uniqueListName }) {
      # This field is identified as `response.query.todoList.priority`
      priority
    }
  }
}
  • في عملية تعديل متعددة الخطوات، مثلاً مع حقول _insert متعددة، يحتوي ربط response على بيانات جزئية في response.<fieldName>.<fieldName>....، وفي هذه الحالة، response.todoList_insert.id.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()",
    name: $listName,
  })
  # Step 2:
  todo_insert(data: {
    listId_expr: "response.todoLis<t_insert.id" # -- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

عملية ربط this

يتم تقييم عملية الربط this إلى الحقل الذي تم ربط التوجيه @check به. في حالة أساسية، يمكنك تقييم نتائج طلب بحث ذات قيمة واحدة.

mutation UpdateMovieTitle (
  $movieId: UUID!,
  $newTitle: String!)
  @auth(level: USER)
  @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    ) {
      # Check if the user has the editor role for the movie. `this` is the string value of `role`.
      # If the parent moviePermission is null, the @check will also fail automatically.
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

إذا تكرّر الحقل المعروض عدة مرات لأنّ أي عنصر من العناصر الرئيسية هو قائمة، يتم اختبار كل تكرار باستخدام this المرتبط بكل قيمة.

بالنسبة إلى أي مسار معيّن، إذا كان أحد العناصر الرئيسية هو null أو []، لن يتم الوصول إلى الحقل وسيتم تخطّي تقييم CEL لهذا المسار. بعبارة أخرى، لا يتم التقييم إلا عندما تكون قيمة this هي null أو قيمة غير null، ولكن لا تكون undefined أبدًا.

عندما يكون الحقل نفسه عبارة عن قائمة أو عنصر، يتبع this البنية نفسها (بما في ذلك جميع العناصر الفرعية المحدّدة في حالة العناصر)، كما هو موضّح في المثال التالي.

mutation UpdateMovieTitle2($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query {
    moviePermissions( # Now we query for a list of all matching MoviePermissions.
      where: {movieId: {eq: $movieId}, userId: {eq_expr: "auth.uid"}}
    # This time we execute the @check on the list, so `this` is the list of objects.
    # We can use the `.exists` macro to check if there is at least one matching entry.
    ) @check(expr: "this.exists(p, p.role == 'editor')", message: "You must be an editor of this movie to update title") {
      role
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

بنية التعبير المعقّد

يمكنك كتابة عبارات أكثر تعقيدًا من خلال الجمع بينها وبين عاملَي التشغيل && و||.

mutation UpsertUser($username: String!) @auth(expr: "(auth != n&&ull)  (vars.username == ';joe')")

يوضّح القسم التالي جميع عوامل التشغيل المتاحة.

عوامل التشغيل وأولوية عوامل التشغيل

استخدِم الجدول التالي كمرجع للعوامل وأولوية تنفيذها.

لنفترض أنّ لدينا عبارتَين عشوائيتَين a وb، وحقل f، وفهرس i.

عامل التشغيل الوصف الترابطية
a[i] a() a.f الفهرسة والاتصال والوصول إلى الحقول من اليسار إلى اليمين
!a -a النفي الأحادي من اليمين إلى اليسار
a/b a%b a*b عوامل الضرب من اليسار إلى اليمين
a+b a-b عوامل الجمع من اليسار إلى اليمين
a>b a>=b a<b a<=b عوامل التشغيل العلائقية من اليسار إلى اليمين
a in b الوجود في القائمة أو الخريطة من اليسار إلى اليمين
type(a) == t مقارنة الأنواع، حيث يمكن أن يكون t قيمة منطقية أو عددًا صحيحًا أو عددًا عشريًا أو رقمًا أو سلسلة أو قائمة أو خريطة أو طابعًا زمنيًا أو مدة من اليسار إلى اليمين
a==b a!=b عوامل تشغيل المقارنة من اليسار إلى اليمين
a && b Conditional AND من اليسار إلى اليمين
a || b Conditional OR من اليسار إلى اليمين
a ? true_value : false_value التعبير الثلاثي من اليسار إلى اليمين

البيانات في رموز المصادقة

قد يحتوي الكائن auth.token على القيم التالية:

الحقل الوصف
email عنوان البريد الإلكتروني المرتبط بالحساب، إذا كان متوفّرًا
email_verified true إذا أثبت المستخدم أنّه يملك عنوان email. يتحقّق بعض مقدّمي الخدمات تلقائيًا من عناوين البريد الإلكتروني التي يملكونها.
phone_number رقم الهاتف المرتبط بالحساب، إذا كان متوفّرًا
name الاسم المعروض للمستخدم، إذا تم ضبطه
sub المعرّف الفريد (UID) للمستخدم على Firebase هذا المعرّف فريد ضمن المشروع.
firebase.identities قاموس بجميع الهويات المرتبطة بحساب هذا المستخدم. يمكن أن تكون مفاتيح القاموس أيًا مما يلي: email أو phone أو google.com أو facebook.com أو github.com أو twitter.com. قيم القاموس هي مصفوفات من المعرّفات الفريدة لكل خدمة توفير هوية مرتبطة بالحساب. على سبيل المثال، يحتوي auth.token.firebase.identities["google.com"][0] على معرّف مستخدم Google الأول المرتبط بالحساب.
firebase.sign_in_provider موفّر خدمة تسجيل الدخول المستخدَم للحصول على هذا الرمز المميّز. يمكن أن تكون إحدى السلاسل التالية: custom أو password أو phone أو anonymous أو google.com أو facebook.com أو github.com أو twitter.com.
firebase.tenant تمثّل هذه السمة رقم تعريف المستأجر المرتبط بالحساب، إذا كان متوفرًا. على سبيل المثال، tenant2-m6tyz

حقول إضافية في رموز JWT المميّزة الخاصة بمعرّف المستخدم

يمكنك أيضًا الوصول إلى حقول auth.token التالية:

مطالبات الرموز المميّزة المخصّصة
alg خوارزمية "RS256"
iss جهة الإصدار عنوان البريد الإلكتروني لحساب الخدمة في مشروعك
sub الموضوع عنوان البريد الإلكتروني لحساب الخدمة في مشروعك
aud الجمهور "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat وقت الإصدار الوقت الحالي، بالثواني منذ بدء حساب الفترة في نظام التشغيل UNIX
exp وقت انتهاء الصلاحية الوقت الذي تنتهي فيه صلاحية الرمز المميّز، بالثواني منذ بدء حساب الفترة في نظام التشغيل UNIX يمكن أن يكون بعد 3600 ثانية كحدّ أقصى من iat.
ملاحظة: يتحكّم هذا الإعداد فقط في وقت انتهاء صلاحية الرمز المميّز المخصّص نفسه. ولكن بعد تسجيل دخول المستخدم باستخدام signInWithCustomToken()، سيظل مسجّلاً الدخول إلى الجهاز إلى أن تصبح جلسته غير صالحة أو يسجّل المستخدم خروجه.
<claims> (اختياري) مطالبات مخصّصة اختيارية لتضمينها في الرمز المميز، ويمكن الوصول إليها من خلال auth.token (أو request.auth.token) في التعبيرات. على سبيل المثال، إذا أنشأت مطالبة مخصّصة adminClaim، يمكنك الوصول إليها باستخدام auth.token.adminClaim.

ما هي الخطوات التالية؟