پرس و جوی ایمن داده ها

این صفحه بر پایه مفاهیم ساختار قوانین امنیتی و شرایط نوشتن برای قوانین امنیتی است تا نحوه تعامل Cloud Firestore Security Rules با پرس و جوها را توضیح دهد. نگاهی دقیق‌تر به نحوه تأثیر قوانین امنیتی بر جستارهایی که می‌توانید بنویسید می‌اندازد و نحوه اطمینان از اینکه درخواست‌های شما از محدودیت‌های مشابه قوانین امنیتی شما استفاده می‌کنند، توضیح می‌دهد. این صفحه همچنین نحوه نوشتن قوانین امنیتی برای اجازه یا رد درخواست‌ها بر اساس ویژگی‌های پرس و جو مانند limit و orderBy را توضیح می‌دهد.

قوانین فیلتر نیستند

هنگام نوشتن پرس و جو برای بازیابی اسناد، به خاطر داشته باشید که قوانین امنیتی فیلتر نیستند - پرس و جوها همه یا هیچ هستند. برای صرفه جویی در زمان و منابع شما، Cloud Firestore یک پرس و جو را در برابر مجموعه نتایج احتمالی آن به جای مقادیر واقعی فیلد برای همه اسناد شما ارزیابی می کند. اگر یک پرس و جو به طور بالقوه بتواند اسنادی را که مشتری اجازه خواندن آنها را ندارد بازگرداند، کل درخواست با شکست مواجه می شود.

سوالات و قوانین امنیتی

همانطور که مثال‌های زیر نشان می‌دهند، باید درخواست‌های خود را بنویسید تا با محدودیت‌های قوانین امنیتی شما مطابقت داشته باشد.

اسناد ایمن و پرس و جو بر اساس auth.uid

مثال زیر نحوه نوشتن یک پرس و جو برای بازیابی اسناد محافظت شده توسط یک قانون امنیتی را نشان می دهد. پایگاه داده ای را در نظر بگیرید که حاوی مجموعه ای از اسناد story است:

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

علاوه بر فیلدهای title و content ، هر سند author و فیلدهای published را برای کنترل دسترسی ذخیره می کند. این مثال‌ها فرض می‌کنند که برنامه از Firebase Authentication برای تنظیم فیلد author روی UID کاربری که سند را ایجاد کرده است استفاده می‌کند. Firebase Authentication همچنین متغیر request.auth را در قوانین امنیتی پر می کند.

قانون امنیتی زیر از متغیرهای request.auth و resource.data برای محدود کردن دسترسی خواندن و نوشتن هر story برای نویسنده آن استفاده می‌کند:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

فرض کنید که برنامه شما دارای صفحه ای است که لیستی از اسناد story را به کاربر نشان می دهد. ممکن است انتظار داشته باشید که می توانید از عبارت زیر برای پر کردن این صفحه استفاده کنید. با این حال، این پرس و جو با شکست مواجه خواهد شد، زیرا شامل محدودیت های مشابه قوانین امنیتی شما نمی شود:

نامعتبر : محدودیت های پرس و جو با محدودیت های قوانین امنیتی مطابقت ندارند

// This query will fail
db.collection("stories").get()

پرس و جو با شکست مواجه می شود حتی اگر کاربر فعلی در واقع نویسنده هر سند story باشد. دلیل این رفتار این است که وقتی Cloud Firestore قوانین امنیتی شما را اعمال می کند، پرس و جو را بر اساس مجموعه نتایج بالقوه خود ارزیابی می کند، نه با ویژگی های واقعی اسناد در پایگاه داده شما. اگر یک پرس و جو به طور بالقوه شامل اسنادی باشد که قوانین امنیتی شما را نقض می کنند، پرس و جو با شکست مواجه می شود.

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

معتبر : محدودیت های پرس و جو با محدودیت های قوانین امنیتی مطابقت دارند

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

اسناد را بر اساس یک فیلد ایمن و پرس و جو کنید

برای نشان دادن بیشتر تعامل بین پرسش‌ها و قوانین، قوانین امنیتی زیر دسترسی خواندن را برای مجموعه stories گسترش می‌دهد تا به هر کاربری اجازه دهد اسناد story را در جایی که فیلد published روی true تنظیم شده است بخواند.

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

پرس و جو برای صفحات منتشر شده باید شامل محدودیت های مشابه قوانین امنیتی باشد:

db.collection("stories").where("published", "==", true).get()

محدودیت query .where("published", "==", true) تضمین می کند که resource.data.published برای هر نتیجه ای true است. بنابراین، این پرس و جو قوانین امنیتی را برآورده می کند و اجازه خواندن داده ها را دارد.

OR پرس و جو

هنگام ارزیابی یک پرس و جوی منطقی OR ( or ، in ، یا array-contains-any ) در برابر مجموعه قوانین، Cloud Firestore هر مقدار مقایسه را جداگانه ارزیابی می کند. هر مقدار مقایسه باید قیود قوانین امنیتی را برآورده کند. به عنوان مثال، برای قانون زیر:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

نامعتبر : پرس و جو تضمین نمی کند که x > 5 برای همه اسناد احتمالی باشد

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

معتبر : پرس و جو تضمین می کند که x > 5 برای همه اسناد بالقوه

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [6, 42, 99, 105, 200])
    )

ارزیابی محدودیت ها در پرس و جوها

قوانین امنیتی شما همچنین می توانند پرس و جوها را بر اساس محدودیت های آنها بپذیرند یا رد کنند. متغیر request.query حاوی ویژگی های limit ، offset و orderBy یک پرس و جو است. به عنوان مثال، قوانین امنیتی شما می توانند هر درخواستی را که حداکثر تعداد اسناد بازیابی شده را به محدوده خاصی محدود نمی کند، رد کند:

allow list: if request.query.limit <= 10;

مجموعه قواعد زیر نحوه نوشتن قوانین امنیتی را نشان می‌دهد که محدودیت‌های اعمال‌شده روی پرس‌و‌جوها را ارزیابی می‌کنند. این مثال مجموعه قوانین stories قبلی را با تغییرات زیر گسترش می دهد:

  • مجموعه قوانین قانون خواندن را به قواعد get و list جدا می کند.
  • قانون get بازیابی اسناد منفرد را به اسناد عمومی یا اسنادی که کاربر نوشته است محدود می کند.
  • قانون list همان محدودیت‌هایی را اعمال می‌کند که get اما برای پرس و جوها. همچنین محدودیت پرس و جو را بررسی می کند، سپس هر درخواستی را بدون محدودیت یا با محدودیت بیشتر از 10 رد می کند.
  • مجموعه قوانین یک تابع authorOrPublished() را برای جلوگیری از تکرار کد تعریف می کند.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

پرس و جوهای گروه مجموعه و قوانین امنیتی

به‌طور پیش‌فرض، پرس‌و‌جوها در یک مجموعه واحد قرار می‌گیرند و نتایج را فقط از آن مجموعه بازیابی می‌کنند. با پرس و جوهای گروه مجموعه ، می توانید نتایج یک گروه مجموعه شامل همه مجموعه ها با شناسه یکسان را بازیابی کنید. این بخش نحوه ایمن سازی پرسش های گروه مجموعه خود را با استفاده از قوانین امنیتی شرح می دهد.

اسناد ایمن و پرس و جو بر اساس گروه های مجموعه

در قوانین امنیتی خود، باید صراحتاً با نوشتن یک قانون برای گروه مجموعه، درخواست های گروه مجموعه را مجاز کنید:

  1. مطمئن شوید rules_version = '2'; اولین خط از مجموعه قوانین شما است. جستارهای گروه مجموعه به رفتار عام بازگشتی جدید {name=**} قوانین امنیتی نسخه 2 نیاز دارند.
  2. با استفاده از match /{path=**}/ [COLLECTION_ID] /{doc} یک قانون برای گروه مجموعه خود بنویسید.

به عنوان مثال، یک انجمن را در نظر بگیرید که در اسناد forum سازماندهی شده است که حاوی زیر مجموعه posts است:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

در این برنامه، ما پست ها را توسط صاحبان آنها قابل ویرایش و توسط کاربران احراز هویت شده قابل خواندن می کنیم:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

هر کاربر تایید شده می تواند پست های هر انجمن را بازیابی کند:

db.collection("forums/technology/posts").get()

اما اگر بخواهید به کاربر فعلی پست‌هایش را در همه انجمن‌ها نشان دهید، چه؟ می‌توانید از یک پرس‌وجو گروه مجموعه برای بازیابی نتایج از همه مجموعه‌های posts استفاده کنید:

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

در قوانین امنیتی خود، باید با نوشتن یک قانون خواندن یا فهرست برای گروه مجموعه posts این درخواست را مجاز کنید:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

با این حال، توجه داشته باشید که این قوانین بدون در نظر گرفتن سلسله مراتب، برای همه مجموعه‌های دارای posts شناسه اعمال می‌شود. برای مثال، این قوانین برای همه مجموعه‌های posts زیر اعمال می‌شود:

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

پرس و جوهای گروهی مجموعه ایمن بر اساس یک فیلد

مانند جستارهای تک مجموعه ای، پرس و جوهای گروه مجموعه نیز باید محدودیت های تعیین شده توسط قوانین امنیتی شما را برآورده کنند. به عنوان مثال، می‌توانیم یک فیلد published را به هر پست انجمن اضافه کنیم، همانطور که در مثال stories بالا انجام دادیم:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

سپس می توانیم قوانینی را برای گروه مجموعه posts بر اساس وضعیت published و author پست بنویسیم:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

با استفاده از این قوانین، مشتریان وب، اپل و اندروید می توانند پرس و جوهای زیر را انجام دهند:

  • هر کسی می تواند پست های منتشر شده در یک انجمن را بازیابی کند:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • هر کسی می تواند پست های منتشر شده یک نویسنده را در همه انجمن ها بازیابی کند:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • نویسندگان می توانند تمام پست های منتشر شده و منتشر نشده خود را در همه انجمن ها بازیابی کنند:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

اسناد ایمن و پرس و جو بر اساس گروه مجموعه و مسیر سند

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

برنامه‌ای را در نظر بگیرید که تراکنش‌های هر کاربر را در میان چندین صرافی سهام و ارز دیجیتال پیگیری می‌کند:

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

به فیلد user توجه کنید. حتی اگر می دانیم که کدام کاربر صاحب یک سند transaction از مسیر سند است، این اطلاعات را در هر سند transaction کپی می کنیم زیرا به ما امکان می دهد دو کار را انجام دهیم:

  • جستارهای گروه مجموعه را بنویسید که محدود به اسنادی است که یک /users/{userid} خاص را در مسیر سند خود دارند. به عنوان مثال:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • این محدودیت را برای همه درخواست‌های گروه مجموعه transactions اعمال کنید تا یک کاربر نتواند اسناد transaction کاربر دیگر را بازیابی کند.

ما این محدودیت را در قوانین امنیتی خود اعمال می‌کنیم و اعتبارسنجی داده‌ها را برای فیلد user درج می‌کنیم:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

مراحل بعدی