নিরাপদে তথ্য অনুসন্ধান

এই পৃষ্ঠাটি ‘সিকিউরিটি রুলস গঠন’ এবং ‘সিকিউরিটি রুলসের জন্য শর্ত লেখা’ অধ্যায়ের ধারণাগুলোর উপর ভিত্তি করে ব্যাখ্যা করে যে Cloud Firestore Security Rules কীভাবে কোয়েরির সাথে কাজ করে। এটি আরও বিশদভাবে আলোচনা করে যে, সিকিউরিটি রুলস আপনার লেখা কোয়েরিগুলোকে কীভাবে প্রভাবিত করে এবং আপনার কোয়েরিগুলো যেন সিকিউরিটি রুলসের মতোই একই সীমাবদ্ধতা ব্যবহার করে, তা কীভাবে নিশ্চিত করবেন তা বর্ণনা করে। এই পৃষ্ঠাটি আরও বর্ণনা করে যে, limit এবং orderBy মতো কোয়েরি প্রপার্টির উপর ভিত্তি করে কোয়েরিকে অনুমতি দেওয়া বা না দেওয়ার জন্য কীভাবে সিকিউরিটি রুলস লিখতে হয়।

নিয়মগুলো ফিল্টার নয়

ডকুমেন্ট পুনরুদ্ধার করার জন্য কোয়েরি লেখার সময় মনে রাখবেন যে, সিকিউরিটি রুলগুলো ফিল্টার নয়—কোয়েরি হয় সম্পূর্ণ ডকুমেন্ট গ্রহণ করে অথবা কিছুই করে না। আপনার সময় এবং রিসোর্স বাঁচাতে, Cloud Firestore আপনার সমস্ত ডকুমেন্টের প্রকৃত ফিল্ড ভ্যালুর পরিবর্তে একটি কোয়েরিকে তার সম্ভাব্য ফলাফলের সেটের ভিত্তিতে মূল্যায়ন করে। যদি কোনো কোয়েরির মাধ্যমে এমন ডকুমেন্ট ফেরত আসার সম্ভাবনা থাকে যা পড়ার অনুমতি ক্লায়েন্টের নেই, তাহলে সম্পূর্ণ অনুরোধটি ব্যর্থ হয়ে যায়।

কোয়েরি এবং নিরাপত্তা নিয়মাবলী

নীচের উদাহরণগুলি যেমন দেখায়, আপনাকে অবশ্যই আপনার নিরাপত্তা নিয়মের সীমাবদ্ধতা মেনে আপনার কোয়েরিগুলি লিখতে হবে।

auth.uid এর ভিত্তিতে নথি সুরক্ষিত করুন এবং অনুসন্ধান করুন।

নিম্নলিখিত উদাহরণটি দেখায় কিভাবে একটি নিরাপত্তা নিয়ম দ্বারা সুরক্ষিত নথি পুনরুদ্ধার করার জন্য একটি কোয়েরি লিখতে হয়। এমন একটি ডেটাবেস বিবেচনা করুন যেখানে story নথিগুলির একটি সংগ্রহ রয়েছে:

/গল্প/{গল্পের আইডি}

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

title এবং content ফিল্ড ছাড়াও, প্রতিটি ডকুমেন্ট অ্যাক্সেস কন্ট্রোলের জন্য author এবং published ফিল্ড সংরক্ষণ করে। এই উদাহরণগুলোতে ধরে নেওয়া হয়েছে যে, অ্যাপটি ডকুমেন্টটি তৈরি করা ব্যবহারকারীর UID-কে author ফিল্ডে সেট করার জন্য ফায়ারবেস অথেন্টিকেশন ব্যবহার করে। ফায়ারবেস অথেন্টিকেশন সিকিউরিটি রুলসের মধ্যে থাকা request.auth ভ্যারিয়েবলটিও পূরণ করে।

নিম্নলিখিত নিরাপত্তা নিয়মটি প্রতিটি story লেখকের জন্য পঠন ও লিখন অ্যাক্সেস সীমাবদ্ধ করতে request.auth এবং resource.data ভেরিয়েবল ব্যবহার করে:

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 কোয়েরির ক্ষেত্রে। এটি কোয়েরি লিমিটও পরীক্ষা করে এবং তারপর লিমিট ছাড়া বা ১০-এর বেশি লিমিটযুক্ত যেকোনো কোয়েরি প্রত্যাখ্যান করে।
  • কোডের পুনরাবৃত্তি এড়ানোর জন্য নিয়মাবলীতে একটি 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;
    }
  }
}

এই নিয়মগুলো ব্যবহার করে ওয়েব, অ্যাপল এবং অ্যান্ড্রয়েড ক্লায়েন্টরা নিম্নলিখিত কোয়েরিগুলো করতে পারে:

  • যে কেউ একটি ফোরামে প্রকাশিত পোস্ট পুনরুদ্ধার করতে পারে:

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

পরবর্তী পদক্ষেপ