التحكم في الوصول إلى حقول محددة

تعتمد هذه الصفحة على المفاهيم الواردة في هيكلة قواعد الأمان وشروط الكتابة لقواعد الأمان لشرح كيف يمكنك استخدام قواعد أمان Cloud Firestore لإنشاء قواعد تسمح للعملاء بتنفيذ عمليات على بعض الحقول في المستند دون غيرها.

قد تكون هناك أوقات تريد فيها التحكم في التغييرات التي يتم إجراؤها على مستند ليس على مستوى المستند ولكن على مستوى الحقل.

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

السماح بالوصول للقراءة لحقول محددة فقط

يتم تنفيذ عمليات القراءة في Cloud Firestore على مستوى المستند. إما أن تسترد المستند كاملاً، أو لا تسترد أي شيء. لا توجد طريقة لاسترداد وثيقة جزئية. من المستحيل استخدام قواعد الأمان وحدها لمنع المستخدمين من قراءة حقول معينة داخل المستند.

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

/الموظفين/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/employees/{emp_id}/private/finances

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

وبعد ذلك يمكنك إضافة قواعد أمان لها مستويات وصول مختلفة للمجموعتين. في هذا المثال، نستخدم مطالبات المصادقة المخصصة لنقول أن المستخدمين الذين يتمتعون role مطالبة المصادقة المخصصة المساوٍ لدور Finance هم وحدهم الذين يمكنهم عرض المعلومات المالية للموظف.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow any logged in user to view the public employee data
    match /employees/{emp_id} {
      allow read: if request.resource.auth != null
      // Allow only users with the custom auth claim of "Finance" to view
      // the employee's financial data
      match /private/finances {
        allow read: if request.resource.auth &&
          request.resource.auth.token.role == 'Finance'
      }
    }
  }
}

تقييد الحقول عند إنشاء المستند

Cloud Firestore ليس له مخطط، مما يعني أنه لا توجد قيود على مستوى قاعدة البيانات فيما يتعلق بالحقول التي يحتوي عليها المستند. على الرغم من أن هذه المرونة يمكن أن تجعل التطوير أسهل، إلا أنه ستكون هناك أوقات تريد فيها التأكد من أن العملاء يمكنهم فقط إنشاء المستندات التي تحتوي على حقول محددة، أو لا تحتوي على حقول أخرى.

يمكنك إنشاء هذه القواعد عن طريق فحص طريقة keys للكائن request.resource.data . هذه قائمة بجميع الحقول التي يحاول العميل كتابتها في هذا المستند الجديد. من خلال دمج هذه المجموعة من الحقول مع وظائف مثل hasOnly() أو hasAny() ، يمكنك إضافة منطق يقيد أنواع المستندات التي يمكن للمستخدم إضافتها إلى Cloud Firestore.

تتطلب حقول محددة في المستندات الجديدة

لنفترض أنك تريد التأكد من أن جميع المستندات التي تم إنشاؤها في مجموعة restaurant تحتوي على حقل name location city على الأقل. يمكنك القيام بذلك عن طريق استدعاء hasAll() ‎ في قائمة المفاتيح في المستند الجديد.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document contains a name
    // location, and city field
    match /restaurant/{restId} {
      allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
    }
  }
}

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

منع حقول محددة في المستندات الجديدة

وبالمثل، يمكنك منع العملاء من إنشاء مستندات تحتوي على حقول محددة باستخدام hasAny() مقابل قائمة الحقول المحظورة. يتم تقييم هذه الطريقة على أنها صحيحة إذا كان المستند يحتوي على أي من هذه الحقول، لذلك ربما تريد إلغاء النتيجة لمنع حقول معينة.

على سبيل المثال، في المثال التالي، لا يُسمح للعملاء بإنشاء مستند يحتوي على حقل average_score أو rating_count حيث ستتم إضافة هذه الحقول عن طريق استدعاء الخادم في وقت لاحق.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document does *not*
    // contain an average_score or rating_count field.
    match /restaurant/{restId} {
      allow create: if (!request.resource.data.keys().hasAny(
        ['average_score', 'rating_count']));
    }
  }
}

إنشاء قائمة مسموح بها من الحقول للمستندات الجديدة

بدلاً من منع حقول معينة في المستندات الجديدة، قد ترغب في إنشاء قائمة فقط بتلك الحقول المسموح بها صراحةً في المستندات الجديدة. ثم يمكنك استخدام الدالة hasOnly() للتأكد من أن أي مستندات جديدة تم إنشاؤها تحتوي على هذه الحقول فقط (أو مجموعة فرعية من هذه الحقول) وليس غيرها.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document doesn't contain
    // any fields besides the ones listed below.
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasOnly(
        ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

الجمع بين الحقول المطلوبة والاختيارية

يمكنك دمج عمليات hasAll و hasOnly معًا في قواعد الأمان الخاصة بك للمطالبة ببعض الحقول والسماح للآخرين. على سبيل المثال، يتطلب هذا المثال أن تحتوي جميع المستندات الجديدة على حقول name location city ، كما يسمح بشكل اختياري address hours العمل cuisine .

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document has a name,
    // location, and city field, and optionally address, hours, or cuisine field
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
       (request.resource.data.keys().hasOnly(
           ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

في سيناريو العالم الحقيقي، قد ترغب في نقل هذا المنطق إلى وظيفة مساعدة لتجنب تكرار التعليمات البرمجية الخاصة بك ولدمج الحقول الاختيارية والمطلوبة بسهولة أكبر في قائمة واحدة، كما يلي:

service cloud.firestore {
  match /databases/{database}/documents {
    function verifyFields(required, optional) {
      let allAllowedFields = required.concat(optional);
      return request.resource.data.keys().hasAll(required) &&
        request.resource.data.keys().hasOnly(allAllowedFields);
    }
    match /restaurant/{restId} {
      allow create: if verifyFields(['name', 'location', 'city'],
        ['address', 'hours', 'cuisine']);
    }
  }
}

تقييد الحقول عند التحديث

من الممارسات الأمنية الشائعة السماح للعملاء فقط بتحرير بعض الحقول دون غيرها. لا يمكنك تحقيق ذلك فقط من خلال النظر إلى قائمة request.resource.data.keys() الموضحة في القسم السابق، نظرًا لأن هذه القائمة تمثل المستند الكامل كما ستعتني بالتحديث، وبالتالي ستتضمن الحقول التي لم يقم بها العميل يتغير.

ومع ذلك، إذا كنت ستستخدم الدالة diff() ، فيمكنك مقارنة request.resource.data مع كائن resource.data ، الذي يمثل المستند الموجود في قاعدة البيانات قبل التحديث. يؤدي هذا إلى إنشاء كائن mapDiff ، وهو كائن يحتوي على كافة التغييرات بين خريطتين مختلفتين.

من خلال استدعاء الأسلوب affectedKeys() على MapDiff، يمكنك التوصل إلى مجموعة من الحقول التي تم تغييرها في التحرير. ثم يمكنك استخدام وظائف مثل hasOnly() أو hasAny() للتأكد من أن هذه المجموعة تحتوي (أو لا تحتوي) على عناصر معينة.

منع تغيير بعض الحقول

باستخدام الأسلوب hasAny() في المجموعة التي تم إنشاؤها بواسطة affectedKeys() ثم إلغاء النتيجة، يمكنك رفض أي طلب عميل يحاول تغيير الحقول التي لا تريد تغييرها.

على سبيل المثال، قد ترغب في السماح للعملاء بتحديث المعلومات حول أحد المطاعم ولكن دون تغيير متوسط ​​درجاتهم أو عدد التقييمات.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Allow the client to update a document only if that document doesn't
      // change the average_score or rating_count fields
      allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
        .hasAny(['average_score', 'rating_count']));
    }
  }
}

السماح بتغيير حقول معينة فقط

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

على سبيل المثال، بدلاً من عدم السماح بحقل average_score و rating_count ، يمكنك إنشاء قواعد أمان تسمح للعملاء بتغيير name location city address hours cuisine فقط.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
    // Allow a client to update only these 6 fields in a document
      allow update: if (request.resource.data.diff(resource.data).affectedKeys()
        .hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

هذا يعني أنه، في بعض التكرارات المستقبلية لتطبيقك، إذا كانت مستندات المطعم تتضمن حقل telephone ، فإن محاولات تحرير هذا الحقل ستفشل حتى تعود وتضيف هذا الحقل إلى قائمة hasOnly() في قواعد الأمان الخاصة بك.

فرض أنواع الحقول

هناك تأثير آخر لكون Cloud Firestore بدون مخطط وهو أنه لا يوجد أي تطبيق على مستوى قاعدة البيانات لأنواع البيانات التي يمكن تخزينها في حقول محددة. هذا شيء يمكنك تنفيذه في قواعد الأمان باستخدام عامل التشغيل is .

على سبيل المثال، تفرض قاعدة الأمان التالية أن يكون حقل score المراجعة عددًا صحيحًا، وأن تكون headline content author_name عبارة عن سلاسل، ويكون review_date بمثابة طابع زمني.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if (request.resource.data.score is int &&
          request.resource.data.headline is string &&
          request.resource.data.content is string &&
          request.resource.data.author_name is string &&
          request.resource.data.review_date is timestamp
        );
      }
    }
  }
}

أنواع البيانات الصالحة لمشغل is bool ، و bytes ، و float ، و int ، و list ، latlng ، number ، path ، map ، string ، والطابع timestamp . يدعم is التشغيل أيضًا أنواع بيانات constraint ، duration ، set ، و map_diff ، ولكن بما أن هذه البيانات يتم إنشاؤها بواسطة لغة قواعد الأمان نفسها ولا يتم إنشاؤها بواسطة العملاء، فنادرًا ما تستخدمها في معظم التطبيقات العملية.

لا تحتوي أنواع بيانات list map على دعم للأسماء العامة أو وسيطات الكتابة. بمعنى آخر، يمكنك استخدام قواعد الأمان لفرض احتواء حقل معين على قائمة أو خريطة، لكن لا يمكنك فرض احتواء الحقل على قائمة بجميع الأعداد الصحيحة أو جميع السلاسل.

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

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

service cloud.firestore {
  match /databases/{database}/documents {
  match /orders/{orderId} {
    allow create: if request.resource.data.tags is list &&
      request.resource.data.tags[0] is string &&
      request.resource.data.product is map &&
      request.resource.data.product.name is string &&
      request.resource.data.product.quantity is int
      }
    }
  }
}

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

service cloud.firestore {
  match /databases/{database}/documents {

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp;
  }

   match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
        allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
      }
    }
  }
}

فرض أنواع للحقول الاختيارية

من المهم أن تتذكر أن استدعاء request.resource.data.foo في مستند لا يوجد فيه foo يؤدي إلى حدوث خطأ، وبالتالي فإن أي قاعدة أمنية تقوم بهذا الاستدعاء سترفض الطلب. يمكنك التعامل مع هذا الموقف باستخدام طريقة get على request.resource.data . يتيح لك الأسلوب get توفير وسيطة افتراضية للحقل الذي تسترده من الخريطة إذا كان هذا الحقل غير موجود.

على سبيل المثال، إذا كانت مستندات المراجعة تحتوي أيضًا على حقل photo_url اختياري وحقل tags اختياري تريد التحقق من كونه سلاسل وقوائم على التوالي، فيمكنك إنجاز ذلك عن طريق إعادة كتابة وظيفة reviewFieldsAreValidTypes إلى شيء مثل ما يلي:

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp &&
          docData.get('photo_url', '') is string &&
          docData.get('tags', []) is list;
  }

يؤدي هذا إلى رفض المستندات التي تحتوي على tags ، ولكنها ليست قائمة، بينما لا يزال يسمح بالمستندات التي لا تحتوي على حقل tags (أو photo_url ).

لا يُسمح أبدًا بالكتابة الجزئية

ملاحظة أخيرة حول قواعد أمان Cloud Firestore هي أنها إما تسمح للعميل بإجراء تغيير على المستند، أو ترفض التعديل بالكامل. لا يمكنك إنشاء قواعد أمان تقبل الكتابة في بعض الحقول في مستندك بينما ترفض أخرى في نفس العملية.