নির্দিষ্ট ক্ষেত্রগুলিতে অ্যাক্সেস নিয়ন্ত্রণ করুন

এই পৃষ্ঠাটি 'সিকিউরিটি রুলস গঠন' এবং 'সিকিউরিটি রুলসের জন্য শর্ত লেখা'-এর ধারণাগুলোর উপর ভিত্তি করে ব্যাখ্যা করে যে, কীভাবে আপনি Cloud Firestore Security Rules ব্যবহার করে এমন নিয়ম তৈরি করতে পারেন যা ক্লায়েন্টদের একটি ডকুমেন্টের কিছু ফিল্ডে অপারেশন সম্পাদন করার অনুমতি দেয়, কিন্তু অন্যগুলোতে নয়।

এমন সময় আসতে পারে যখন আপনি কোনো ডকুমেন্টের পরিবর্তনগুলো ডকুমেন্ট লেভেলে নয়, বরং ফিল্ড লেভেলে নিয়ন্ত্রণ করতে চাইবেন।

উদাহরণস্বরূপ, আপনি হয়তো কোনো ক্লায়েন্টকে একটি ডকুমেন্ট তৈরি বা পরিবর্তন করার অনুমতি দিতে চাইতে পারেন, কিন্তু সেই ডকুমেন্টের নির্দিষ্ট কিছু ফিল্ড সম্পাদনা করার অনুমতি দিতে চান না। অথবা আপনি হয়তো এটা নিশ্চিত করতে চাইতে পারেন যে, কোনো ক্লায়েন্ট যে ডকুমেন্টই তৈরি করুক না কেন, তাতে যেন সর্বদা একটি নির্দিষ্ট সেট ফিল্ড থাকে। এই নির্দেশিকায় আলোচনা করা হয়েছে কীভাবে আপনি Cloud Firestore Security Rules ব্যবহার করে এই ধরনের কিছু কাজ সম্পন্ন করতে পারেন।

শুধুমাত্র নির্দিষ্ট ফিল্ডের জন্য পঠন অ্যাক্সেসের অনুমতি দেওয়া

Cloud Firestore রিড অপারেশন ডকুমেন্ট লেভেলে সম্পন্ন করা হয়। আপনি হয় সম্পূর্ণ ডকুমেন্টটি পুনরুদ্ধার করতে পারবেন, অথবা কিছুই পাবেন না। আংশিক ডকুমেন্ট পুনরুদ্ধার করার কোনো উপায় নেই। শুধুমাত্র সিকিউরিটি রুল ব্যবহার করে ব্যবহারকারীদের কোনো ডকুমেন্টের নির্দিষ্ট ফিল্ড পড়া থেকে বিরত রাখা অসম্ভব।

যদি কোনো ডকুমেন্টের মধ্যে এমন কিছু ফিল্ড থাকে যা আপনি কিছু ব্যবহারকারীর কাছ থেকে গোপন রাখতে চান, তাহলে সেগুলোকে একটি আলাদা ডকুমেন্টে রাখাই সবচেয়ে ভালো উপায়। উদাহরণস্বরূপ, আপনি একটি private সাবকালেকশনে এইরকম একটি ডকুমেন্ট তৈরি করার কথা ভাবতে পারেন:

/কর্মচারী/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/কর্মচারী/{emp_id}/ব্যক্তিগত/অর্থায়ন

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

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

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

ডকুমেন্ট তৈরির ক্ষেত্রে সীমাবদ্ধতা আরোপ করা

Cloud Firestore স্কিমাবিহীন, যার অর্থ হলো একটি ডকুমেন্টে কী কী ফিল্ড থাকবে, তার ওপর ডাটাবেস পর্যায়ে কোনো বিধিনিষেধ নেই। যদিও এই নমনীয়তা ডেভেলপমেন্টকে সহজ করে তুলতে পারে, এমন সময়ও আসবে যখন আপনি নিশ্চিত করতে চাইবেন যে ক্লায়েন্টরা যেন শুধুমাত্র নির্দিষ্ট ফিল্ডযুক্ত ডকুমেন্ট তৈরি করতে পারে, অথবা অন্য কোনো ফিল্ড যেন না থাকে।

আপনি request.resource.data অবজেক্টের keys মেথডটি পরীক্ষা করে এই নিয়মগুলো তৈরি করতে পারেন। এটি সেই সমস্ত ফিল্ডের একটি তালিকা যা ক্লায়েন্ট এই নতুন ডকুমেন্টটিতে লিখতে চেষ্টা করছে। hasOnly() বা hasAny() এর মতো ফাংশনগুলোর সাথে এই ফিল্ডের সেটটিকে একত্রিত করে, আপনি এমন লজিক যোগ করতে পারেন যা একজন ব্যবহারকারী Cloud Firestore এ যে ধরনের ডকুমেন্ট যোগ করতে পারবে তা সীমাবদ্ধ করে দেয়।

নতুন নথিতে নির্দিষ্ট ক্ষেত্রগুলির প্রয়োজন

ধরা যাক, আপনি নিশ্চিত করতে চান যে একটি 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() মেথড ব্যবহার করে আপনি ক্লায়েন্টদের নির্দিষ্ট ফিল্ডযুক্ত ডকুমেন্ট তৈরি করা থেকে বিরত রাখতে পারেন। কোনো ডকুমেন্টে এই ফিল্ডগুলোর কোনোটি থাকলে এই মেথডটির ফলাফল true হয়, তাই নির্দিষ্ট কিছু ফিল্ড নিষিদ্ধ করার জন্য আপনি সম্ভবত এর ফলাফলকে negate করতে চাইবেন।

উদাহরণস্বরূপ, নিম্নলিখিত উদাহরণে, ক্লায়েন্টদের এমন কোনো ডকুমেন্ট তৈরি করার অনুমতি নেই যাতে 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 অবজেক্ট তৈরি হয়, যা দুটি ভিন্ন ম্যাপের মধ্যেকার সমস্ত পরিবর্তন ধারণ করে।

এই mapDiff-এর উপর affectedKeys() মেথডটি কল করার মাধ্যমে, আপনি সম্পাদনার সময় পরিবর্তিত হওয়া ফিল্ডগুলোর একটি সেট তৈরি করতে পারেন। এরপর, এই সেটে নির্দিষ্ট কিছু আইটেম আছে কি নেই, তা নিশ্চিত করার জন্য আপনি hasOnly() বা hasAny() এর মতো ফাংশন ব্যবহার করতে পারেন।

কিছু ক্ষেত্র পরিবর্তন করা থেকে বিরত রাখা

affectedKeys() দ্বারা তৈরি সেটের উপর hasAny() মেথড ব্যবহার করে এবং তারপর ফলাফলটিকে নেগেট করার মাধ্যমে, আপনি এমন যেকোনো ক্লায়েন্ট রিকোয়েস্ট প্রত্যাখ্যান করতে পারেন যা আপনার অনাকাঙ্ক্ষিত ফিল্ডগুলো পরিবর্তন করার চেষ্টা করে।

উদাহরণস্বরূপ, আপনি হয়তো গ্রাহকদের কোনো রেস্তোরাঁ সম্পর্কে তথ্য হালনাগাদ করার অনুমতি দিতে চান, কিন্তু তাদের গড় স্কোর বা পর্যালোচনার সংখ্যা পরিবর্তন করার অনুমতি দিতে চান না।

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() তালিকায় সেই ফিল্ডটি যোগ করছেন।

ফিল্ডের প্রকার প্রয়োগ করা

Cloud Firestore স্কিমাবিহীন হওয়ার আরেকটি প্রভাব হলো, নির্দিষ্ট ফিল্ডে কোন ধরনের ডেটা সংরক্ষণ করা যাবে, সে বিষয়ে ডাটাবেস পর্যায়ে কোনো নিয়ন্ত্রণ নেই। তবে, সিকিউরিটি রুলসে ' 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
      }
    }
  }
}

ঐচ্ছিক ক্ষেত্রগুলির জন্য প্রকার প্রয়োগ করা

এটা মনে রাখা গুরুত্বপূর্ণ যে, কোনো ডকুমেন্টে foo ফিল্ডটি বিদ্যমান না থাকলে request.resource.data.foo কল করলে একটি এরর দেখা দেয়, এবং এর ফলে ওই কলটি করা যেকোনো সিকিউরিটি রুল রিকোয়েস্টটি প্রত্যাখ্যান করবে। আপনি request.resource.data এর উপর ` get মেথড ব্যবহার করে এই পরিস্থিতি সামাল দিতে পারেন। ` 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 সম্পর্কে শেষ কথাটি হলো, এগুলি হয় ক্লায়েন্টকে কোনো ডকুমেন্টে পরিবর্তন করার অনুমতি দেয়, অথবা সম্পূর্ণ সম্পাদনাটিই প্রত্যাখ্যান করে। আপনি এমন সিকিউরিটি রুলস তৈরি করতে পারবেন না যা একই অপারেশনে আপনার ডকুমেন্টের কিছু ফিল্ডে লেখার অনুমতি দেয়, কিন্তু অন্যগুলোতে দেয় না।