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

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

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

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

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

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

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

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

/داستان‌ها/{storyid}

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

علاوه بر فیلدهای title و content ، هر سند فیلدهای author و published را برای استفاده در کنترل دسترسی ذخیره می‌کند. این مثال‌ها فرض می‌کنند که برنامه از Firebase Authentication برای تنظیم فیلد author با شناسه کاربری که سند را ایجاد کرده است، استفاده می‌کند. 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()

قید پرس‌وجوی .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'; اولین خط از مجموعه قوانین شما باشد. کوئری‌های گروه مجموعه نیاز به رفتار جدید wildcard بازگشتی {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}

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

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

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

با این قوانین، کاربران وب، اپل و اندروید می‌توانند سوالات زیر را بپرسند:

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

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

مراحل بعدی