شرایط نوشتن برای قوانین امنیتی Cloud Firestore

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

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

احراز هویت

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

یک الگوی رایج دیگر این است که مطمئن شوید کاربران فقط می‌توانند داده‌های خودشان را بخوانند و بنویسند:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

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

اعتبارسنجی داده‌ها

بسیاری از برنامه‌ها اطلاعات کنترل دسترسی را به عنوان فیلدهایی روی اسناد در پایگاه داده ذخیره می‌کنند. Cloud Firestore Security Rules می‌توانند به صورت پویا بر اساس داده‌های سند، دسترسی را مجاز یا غیرمجاز کنند:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

متغیر resource به سند درخواستی اشاره دارد و resource.data نقشه‌ای از تمام فیلدها و مقادیر ذخیره شده در سند است. برای اطلاعات بیشتر در مورد متغیر resource ، به مستندات مرجع مراجعه کنید.

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

دسترسی به اسناد دیگر

با استفاده از توابع get() و exists() ، قوانین امنیتی شما می‌توانند درخواست‌های ورودی را با سایر اسناد موجود در پایگاه داده ارزیابی کنند. توابع get() و exists() هر دو انتظار مسیرهای سند کاملاً مشخص شده را دارند. هنگام استفاده از متغیرها برای ساخت مسیر برای get() و exists() ، باید با استفاده از سینتکس $(variable) به طور صریح از متغیرها escape کنید.

در مثال زیر، متغیر database توسط دستور match match /databases/{database}/documents دریافت شده و برای تشکیل مسیر استفاده می‌شود:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid));

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }
  }
}

برای نوشتن‌ها، می‌توانید از تابع getAfter() برای دسترسی به وضعیت یک سند پس از تکمیل یک تراکنش یا نوشتن دسته‌ای اما قبل از ثبت نهایی تراکنش یا دسته‌ای استفاده کنید. مانند get() ، تابع getAfter() یک مسیر سند کاملاً مشخص شده را می‌گیرد. می‌توانید از getAfter() برای تعریف مجموعه‌هایی از نوشتن‌ها که باید به صورت یک تراکنش یا دسته‌ای با هم انجام شوند، استفاده کنید.

دسترسی به محدودیت‌های تماس

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

  • ۱۰ برای درخواست‌های تک سندی و درخواست‌های استعلام.
  • ۲۰ برای خواندن‌های چند سندی، تراکنش‌ها و نوشتن‌های دسته‌ای. محدودیت قبلی ۱۰ برای هر عملیات نیز اعمال می‌شود.

    برای مثال، تصور کنید که یک درخواست نوشتن دسته‌ای با ۳ عملیات نوشتن ایجاد می‌کنید و قوانین امنیتی شما از ۲ فراخوانی دسترسی به سند برای اعتبارسنجی هر نوشتن استفاده می‌کنند. در این حالت، هر نوشتن از ۲ فراخوانی از ۱۰ فراخوانی دسترسی خود استفاده می‌کند و درخواست نوشتن دسته‌ای از ۶ فراخوانی از ۲۰ فراخوانی دسترسی خود استفاده می‌کند.

تجاوز از هر یک از این محدودیت‌ها منجر به خطای عدم اجازه دسترسی می‌شود. برخی از فراخوانی‌های دسترسی به سند ممکن است ذخیره شوند و فراخوانی‌های ذخیره شده در حافظه پنهان، جزو محدودیت‌ها محسوب نمی‌شوند.

برای توضیح دقیق‌تر در مورد چگونگی تأثیر این محدودیت‌ها بر تراکنش‌ها و نوشتن‌های دسته‌ای، به راهنمای ایمن‌سازی عملیات اتمی مراجعه کنید.

دسترسی به تماس‌ها و قیمت‌گذاری

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

توابع سفارشی

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

  • توابع می‌توانند فقط شامل یک دستور return باشند. آن‌ها نمی‌توانند هیچ منطق اضافی داشته باشند. برای مثال، آن‌ها نمی‌توانند حلقه‌ها را اجرا کنند یا سرویس‌های خارجی را فراخوانی کنند.
  • توابع می‌توانند به طور خودکار به توابع و متغیرها از محدوده‌ای که در آن تعریف شده‌اند دسترسی داشته باشند. برای مثال، تابعی که در محدوده service cloud.firestore تعریف شده است، به متغیر resource و توابع داخلی مانند get() و exists() دسترسی دارد.
  • توابع می‌توانند توابع دیگر را فراخوانی کنند اما نمی‌توانند به صورت بازگشتی عمل کنند. عمق کل پشته فراخوانی به 10 محدود شده است.
  • در نسخه v2 قوانین، توابع می‌توانند متغیرها را با استفاده از کلمه کلیدی let تعریف کنند. توابع می‌توانند تا 10 اتصال let داشته باشند، اما باید با یک دستور return خاتمه یابند.

یک تابع با کلمه کلیدی function تعریف می‌شود و هیچ یا چند آرگومان می‌گیرد. برای مثال، ممکن است بخواهید دو نوع شرط استفاده شده در مثال‌های بالا را در یک تابع واحد ترکیب کنید:

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

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

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

وقتی داده‌های خود را ایمن کردید و شروع به نوشتن کوئری‌ها کردید، به خاطر داشته باشید که قوانین امنیتی فیلتر نیستند. شما نمی‌توانید برای همه اسناد موجود در یک مجموعه، کوئری بنویسید و انتظار داشته باشید که Cloud Firestore فقط اسنادی را که کلاینت فعلی اجازه دسترسی به آنها را دارد، برگرداند.

برای مثال، قانون امنیتی زیر را در نظر بگیرید:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

رد شده : این قانون، پرس‌وجوی زیر را رد می‌کند زیرا مجموعه نتایج می‌تواند شامل اسنادی باشد که visibility public نیست:

وب
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

مجاز : این قانون به پرس‌وجوی زیر اجازه می‌دهد زیرا عبارت where("visibility", "==", "public") تضمین می‌کند که مجموعه نتایج، شرط قانون را برآورده می‌کند:

وب
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

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

مراحل بعدی