تأمين الوصول إلى البيانات للمستخدمين والمجموعات

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

الحل: التحكم في الوصول على أساس الدور

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

لنفترض أنك تقوم بإنشاء تطبيق كتابة تعاوني يمكن للمستخدمين من خلاله إنشاء "قصص" و"تعليقات" بمتطلبات الأمان التالية:

  • كل قصة لها مالك واحد ويمكن مشاركتها مع "الكتاب" و"المعلقين" و"القراء".
  • يمكن للقراء رؤية القصص والتعليقات فقط. لا يمكنهم تعديل أي شيء.
  • يتمتع المعلقون بحق الوصول الكامل للقراء، ويمكنهم أيضًا إضافة تعليقات إلى القصة.
  • يتمتع الكتّاب بحق الوصول الكامل للمعلقين، ويمكنهم أيضًا تعديل محتوى القصة.
  • يمكن للمالكين تعديل أي جزء من القصة بالإضافة إلى التحكم في وصول المستخدمين الآخرين.

هيكل البيانات

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

لتتبع أدوار الوصول، أضف حقل roles وهو عبارة عن خريطة لمعرفات المستخدمين للأدوار:

/قصص/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time ...",
  roles: {
    alice: "owner",
    bob: "reader",
    david: "writer",
    jane: "commenter"
    // ...
  }
}

تحتوي التعليقات على حقلين فقط، معرف مستخدم المؤلف وبعض المحتوى:

/قصص/{storyid}/comments/{commentid}

{
  user: "alice",
  content: "I think this is a great story!"
}

قواعد

الآن بعد أن تم تسجيل أدوار المستخدمين في قاعدة البيانات، فأنت بحاجة إلى كتابة قواعد الأمان للتحقق من صحتها. تفترض هذه القواعد أن التطبيق يستخدم Firebase Auth بحيث يكون المتغير request.auth.uid هو معرف المستخدم.

الخطوة 1 : ابدأ بملف القواعد الأساسية، والذي يتضمن قواعد فارغة للقصص والتعليقات:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
         // TODO: Story rules go here...

         match /comments/{comment} {
            // TODO: Comment rules go here...
         }
     }
   }
}

الخطوة 2 : أضف قاعدة write بسيطة تمنح المالكين التحكم الكامل في القصص. تساعد الوظائف المحددة في تحديد أدوار المستخدم وما إذا كانت المستندات الجديدة صالحة:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          // Read from the "roles" map in the resource (rsc).
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          // Determine if the user is one of an array of roles
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          // Valid if story does not exist and the new story has the correct owner.
          return resource == null && isOneOfRoles(request.resource, ['owner']);
        }

        // Owners can read, write, and delete stories
        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

         match /comments/{comment} {
            // ...
         }
     }
   }
}

الخطوة 3 : اكتب القواعد التي تسمح للمستخدم من أي دور بقراءة القصص والتعليقات. يؤدي استخدام الوظائف المحددة في الخطوة السابقة إلى إبقاء القواعد موجزة وسهلة القراءة:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

        // Any role can read stories.
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          // Any role can read comments.
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
        }
     }
   }
}

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

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner'])
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);

          // Owners, writers, and commenters can create comments. The
          // user id in the comment document must match the requesting
          // user's id.
          //
          // Note: we have to use get() here to retrieve the story
          // document so that we can check the user's role.
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

الخطوة 5 : امنح الكتاب القدرة على تحرير محتوى القصة، ولكن ليس لتحرير أدوار القصة أو تغيير أي خصائص أخرى للمستند. يتطلب ذلك تقسيم قاعدة write القصص إلى قواعد منفصلة create update delete نظرًا لأن الكتّاب يمكنهم فقط تحديث القصص:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return request.resource.data.roles[request.auth.uid] == 'owner';
        }

        function onlyContentChanged() {
          // Ensure that title and roles are unchanged and that no new
          // fields are added to the document.
          return request.resource.data.title == resource.data.title
            && request.resource.data.roles == resource.data.roles
            && request.resource.data.keys() == resource.data.keys();
        }

        // Split writing into creation, deletion, and updating. Only an
        // owner can create or delete a story but a writer can update
        // story content.
        allow create: if isValidNewStory();
        allow delete: if isOneOfRoles(resource, ['owner']);
        allow update: if isOneOfRoles(resource, ['owner'])
                      || (isOneOfRoles(resource, ['writer']) && onlyContentChanged());
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

محددات

يوضح الحل الموضح أعلاه تأمين بيانات المستخدم باستخدام قواعد الأمان، ولكن يجب أن تكون على دراية بالقيود التالية:

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