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

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

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

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

منح الإذن بالقراءة فقط لحقول محدّدة

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

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

/employees/{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() أمام قائمة الحقول المحظورة. ويتم تقييم هذه الطريقة على أنها true إذا أي من هذه الحقول، لذا فأنت ربما تريد إلغاء بهدف حظر حقول معينة.

على سبيل المثال، في المثال التالي، لا يُسمح للعملاء بإنشاء مستند يحتوي على حقل 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 وهو كائن يحتوي على جميع التغييرات بين اثنين خرائط Google.

من خلال الاتصال بـ affectedKeys() في هذه الخريطة، يمكنك وضع مجموعة من الحقول التي تم تغييرها في تعديل. بعد ذلك، يمكنك استخدام دوال مثل 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 وهي أنّها تسمح إجراء تغيير على مستند أو رفض التعديل بأكمله. لا يمكنك إنشاء قواعد أمان تقبل عمليات الكتابة على بعض الحقول في المستند مع رفض الآخرين في العملية نفسها.