کنترل دسترسی به فیلدهای خاص

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

ممکن است مواقعی وجود داشته باشد که بخواهید تغییرات سند را نه در سطح سند بلکه در سطح فیلد کنترل کنید.

برای مثال، ممکن است بخواهید به مشتری اجازه دهید سندی را ایجاد کند یا تغییر دهد، اما به او اجازه ندهید فیلدهای خاصی را در آن سند ویرایش کند. یا ممکن است بخواهید اجرا کنید که هر سندی که کارخواه همیشه ایجاد می‌کند حاوی مجموعه خاصی از فیلدها باشد. این راهنما نحوه انجام برخی‌از این کارها را بااستفاده از Cloud Firestore Security Rules پوشش می‌دهد.

برای کمک هوش مصنوعی، پیام‌واره زیر را در عامل کدنویسی هوش مصنوعی خود (مانند 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.

اجازه دادن به دسترسی خواندن فقط برای فیلدهای خاص

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

اگر فیلدهای خاصی در سند وجود دارد که می‌خواهید از دید برخی کاربران پنهان بماند، بهترین راه این است که آن‌ها را در سند جداگانه‌ای قرار دهید. برای مثال، ممکن است بخواهید سندی را در زیرمجموعه 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'
      }
    }
  }
}

محدود کردن فیلدها در ایجاد سند

Rules بدون طرح‌واره است، یعنی هیچ محدودیتی در سطح پایگاه داده برای فیلدهایی که سند حاوی آن است وجود ندارد. درحالی‌که این انعطاف‌پذیری می‌تواند توسعه را آسان‌تر کند، مواقعی وجود دارد که می‌خواهید مطمئن شوید که مشتریان فقط می‌توانند اسنادی ایجاد کنند که حاوی فیلدهای خاصی باشد، یا حاوی فیلدهای دیگر نباشد.

می‌توانید این قوانین را با بررسی روش keys از شیء request.resource.data ایجاد کنید. این فهرستی از همه فیلدهایی است که کارخواه تلاش می‌کند در این سند جدید بنویسد. با ترکیب این مجموعه فیلدها با توابعی مثل hasOnly() یا hasAny()، می‌توانید منطقی اضافه کنید که انواع اسنادی را که کاربر می‌تواند به «قوانین» اضافه کند محدود کند.

الزامی کردن فیلدهای خاص در اسناد جدید

فرض کنید می‌خواهید مطمئن شوید که همه اسناد ایجادشده در مجموعه 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() دربرابر فهرستی از فیلدهای ممنوعه، از ایجاد اسناد حاوی فیلدهای خاص توسط کلاینت‌ها جلوگیری کنید. اگر سندی حاوی هرکدام از این فیلدها باشد، این روش به‌عنوان درست ارزیابی می‌شود، بنابراین احتمالاً می‌خواهید نتیجه را منفی کنید تا فیلدهای خاصی را ممنوع کنید.

برای مثال، در نمونه زیر، سرویس‌گیرندگان مجاز نیستند سندی ایجاد کنند که حاوی فیلد 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 ایجاد می‌شود که شیئی حاوی همه تغییرات بین دو نقشه متفاوت است.

با فراخوانی روش affectedKeys() در این mapDiff، می‌توانید مجموعه‌ای از فیلدها را که در ویرایش تغییر کرده‌اند ارائه دهید. سپس می‌توانید از توابعی مثل 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() در قوانین امنیتی‌تان اضافه کنید.

اجرای انواع فیلد

یکی دیگر از تأثیرات بی‌طرح‌واره بودن Rules این است که هیچ‌گونه اجرایی در سطح پایگاه داده برای اینکه چه نوع داده‌هایی را می‌توان در فیلدهای خاص ذخیره کرد وجود ندارد. این چیزی است که می‌توانید در قوانین امنیتی اعمال کنید، اما با اپراتور 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 Security Rules این است که یا به مشتری اجازه می‌دهند تغییری در سند ایجاد کند یا کل ویرایش را رد می‌کنند. نمی‌توانید قانون امنیتی‌ای ایجاد کنید که نوشتن در برخی‌از فیلدهای سندتان را بپذیرد و درعین‌حال نوشتن در فیلدهای دیگر را در همان عملیات رد کند.