دسترسی ایمن به داده ها برای کاربران و گروه ها

بسیاری از برنامه های مشترک به کاربران امکان خواندن و نوشتن داده های مختلف را بر اساس مجموعه ای از مجوزها می دهند. به عنوان مثال، در یک برنامه ویرایش اسناد، کاربران ممکن است بخواهند به چند کاربر اجازه دهند اسناد خود را بخوانند و بنویسند در حالی که دسترسی ناخواسته را مسدود می کنند.

راه حل: کنترل دسترسی مبتنی بر نقش

می توانید از مدل داده Cloud Firestore و همچنین قوانین امنیتی سفارشی برای پیاده سازی کنترل دسترسی مبتنی بر نقش در برنامه خود استفاده کنید.

فرض کنید در حال ساختن یک برنامه نوشتن مشترک هستید که در آن کاربران می توانند "داستان" و "نظرات" را با الزامات امنیتی زیر ایجاد کنند:

  • هر داستان یک صاحب دارد و می تواند با «نویسندگان»، «نظر دهندگان» و «خوانندگان» به اشتراک گذاشته شود.
  • خوانندگان فقط می توانند داستان ها و نظرات را ببینند. آنها نمی توانند چیزی را ویرایش کنند.
  • کامنت‌گذاران تمام دسترسی خوانندگان را دارند و همچنین می‌توانند به یک داستان نظر اضافه کنند.
  • نویسندگان تمام دسترسی نظر دهندگان را دارند و همچنین می توانند محتوای داستان را ویرایش کنند.
  • مالکان می توانند هر قسمت از یک داستان را ویرایش کنند و همچنین دسترسی کاربران دیگر را کنترل کنند.

ساختار داده ها

فرض کنید برنامه شما دارای یک مجموعه stories است که در آن هر سند یک داستان را نشان می دهد. هر داستان همچنین دارای یک زیر مجموعه comments است که در آن هر سند یک نظر در مورد آن داستان است.

برای پیگیری نقش‌های دسترسی، یک فیلد roles که نقشه شناسه‌های کاربر است به نقش‌ها اضافه کنید:

/stories/{storyid}

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

نظرات فقط شامل دو قسمت است، شناسه کاربری نویسنده و برخی محتوا:

/stories/{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;
        }
     }
   }
}

محدودیت ها

راه حل نشان داده شده در بالا نشان دهنده ایمن سازی داده های کاربر با استفاده از قوانین امنیتی است، اما باید از محدودیت های زیر آگاه باشید:

  • دانه بندی : در مثال بالا، چندین نقش (نویسنده و مالک) دسترسی نوشتن به یک سند دارند اما با محدودیت های متفاوت. مدیریت این امر با اسناد پیچیده تر می تواند دشوار باشد و ممکن است بهتر باشد اسناد واحد را به چندین سند تقسیم کنید که هر کدام متعلق به یک نقش هستند.
  • گروه‌های بزرگ : اگر نیاز به اشتراک‌گذاری با گروه‌های بسیار بزرگ یا پیچیده دارید، سیستمی را در نظر بگیرید که در آن نقش‌ها در مجموعه خودشان ذخیره می‌شوند نه به‌عنوان یک فیلد در سند هدف.