پُرسمان ایمن داده‌ها

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

برای کمک هوش مصنوعی، پیام‌واره زیر را در عامل کدنویسی هوش مصنوعی خود (مانند Gemini در Android Studio،‏ Antigravity،‏ Claude Code،‏ Codex، و Cursor) کپی کنید. می‌توانید پیام‌واره را سفارشی‌سازی کنید و با نماینده‌تان تکرار کنید. برای اطلاعات دقیق نصب، به مهارت‌های عامل Firebase مراجعه کنید.

Help me use Firebase in my app.
1. Make sure that **Firebase agent skills** are installed and updated. For Claude Code, Codex, or Cursor agents, help me install the Firebase plugin. For other agents, run `npx skills add firebase/agent-skills`. Always try to use **Firebase agent skills** for Firebase-related tasks.
2. Help me write, validate, and deploy Firebase Security Rules to protect my app's Firestore data.

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

هنگام نوشتن پُرسمان برای بازیابی اسناد، به‌خاطر داشته باشید که قوانین امنیتی فیلتر نیستند—پُرسمان‌ها همه یا هیچ هستند. برای صرفه‌جویی در زمان و منابع، «قوانین» پرسمان را دربرابر مجموعه نتایج بالقوه آن ارزیابی می‌کند نه مقادیر فیلد واقعی برای همه اسناد شما. اگر پُرسمانی بتواند احتمالاً اسنادی را برگرداند که کارخواه اجازه خواندن آن‌ها را ندارد، کل درخواست ناموفق می‌شود.

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

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

ایمن‌سازی و پرس‌وجوی اسناد براساس 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 برای تنظیم فیلد author روی شناسه کاربری کاربری که سند را ایجاد کرده است استفاده می‌کند. «اصالت‌سنجی Firebase» همچنین متغیر 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 سند باشد. دلیل این رفتار این است که وقتی «قوانین» قوانین امنیتی شما را اعمال می‌کند، پُرسمان را دربرابر مجموعه نتایج بالقوه آن ارزیابی می‌کند، نه دربرابر ویژگی‌های واقعی اسناد در پایگاه داده شما. اگر پرسمانی احتمالاً شامل اسنادی باشد که قوانین امنیتی شما را نقض می‌کند، پرسمان ناموفق خواهد بود.

درمقابل، پُرسمان زیر موفق می‌شود، زیرا شامل همان محدودیت در فیلد 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()

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

‫OR پُرسمان

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

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 را اعمال می‌کند اما برای پُرسمان‌ها. همچنین حد پُرسمان را بررسی می‌کند، سپس هر پُرسمان بدون حد یا با حد بیشتر از ۱۰ را رد می‌کند.
  • مجموعه قوانین یک تابع 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. بااستفاده از 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
}

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

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;
    }
  }
}

با این قوانین، کلاینت‌های وب، Apple، و Android می‌توانند پُرسمان‌های زیر را انجام دهند:

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

    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
    }
  }
}

مراحل بعدی