למד את תחביר הליבה של שפת חוקי האבטחה של מסד נתונים בזמן אמת

כללי אבטחת מסד הנתונים של Firebase בזמן אמת מאפשרים לך לשלוט בגישה לנתונים המאוחסנים במסד הנתונים שלך. תחביר הכללים הגמיש מאפשר לך ליצור כללים התואמים לכל דבר, מכל הכתיבה למסד הנתונים שלך ועד פעולות על צמתים בודדים.

כללי אבטחת מסד נתונים בזמן אמת הם תצורה הצהרתית עבור מסד הנתונים שלך. המשמעות היא שהכללים מוגדרים בנפרד מהלוגיקה של המוצר. יש לכך מספר יתרונות: לקוחות אינם אחראים לאכיפת האבטחה, הטמעות באגי לא יפגעו בנתונים שלכם, ואולי הכי חשוב, אין צורך בשופט ביניים, כמו שרת, כדי להגן על הנתונים מהעולם.

נושא זה מתאר את התחביר והמבנה הבסיסיים של כללי אבטחת מסד נתונים בזמן אמת המשמשים ליצירת ערכות כללים שלמות.

מבנה כללי האבטחה שלך

כללי אבטחת מסד נתונים בזמן אמת מורכבים מביטויים דמויי JavaScript הכלולים במסמך JSON. מבנה הכללים שלך צריך להיות בהתאם למבנה הנתונים ששמרת במסד הנתונים שלך.

כללים בסיסיים מזהים קבוצה של צמתים שיש לאבטח, את שיטות הגישה (למשל, קריאה, כתיבה) המעורבות, ותנאים שבהם הגישה מותרת או נדחתה. בדוגמאות הבאות, התנאים שלנו יהיו הצהרות true false פשוטות, אך בנושא הבא נסקור דרכים דינמיות יותר לביטוי תנאים.

כך, למשל, אם אנו מנסים לאבטח child_node תחת parent_node , התחביר הכללי שיש לעקוב אחריו הוא:

{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": <condition>,
        ".write": <condition>,
        ".validate": <condition>,
      }
    }
  }
}

בואו ליישם את הדפוס הזה. לדוגמה, נניח שאתה עוקב אחר רשימת הודעות ויש לך נתונים שנראים כך:

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

הכללים שלך צריכים להיות מובנים בצורה דומה. להלן אוסף של כללים לאבטחת קריאה בלבד שעשויים להיות הגיוניים עבור מבנה הנתונים הזה. דוגמה זו ממחישה כיצד אנו מציינים צמתים של מסד נתונים שעליהם חלים כללים ואת התנאים להערכת כללים באותם צמתים.

{
  "rules": {
    // For requests to access the 'messages' node...
    "messages": {
      // ...and the individual wildcarded 'message' nodes beneath
      // (we'll cover wildcarding variables more a bit later)....
      "$message": {

        // For each message, allow a read operation if <condition>. In this
        // case, we specify our condition as "true", so read access is always granted.
        ".read": "true",

        // For read-only behavior, we specify that for write operations, our
        // condition is false.
        ".write": "false"
      }
    }
  }
}

כללים בסיסיים פעולות

ישנם שלושה סוגים של כללים לאכיפת אבטחה המבוססים על סוג הפעולה המתבצעת בנתונים: .write , .read ו- .validate . להלן סיכום מהיר של מטרותיהם:

סוגי כללים
.לקרוא מתאר אם ומתי מותר לקרוא נתונים על ידי משתמשים.
.לִכתוֹב מתאר אם ומתי מותר לכתוב נתונים.
.לְאַמֵת מגדיר כיצד ייראה ערך בפורמט נכון, האם יש לו תכונות צאצא וסוג הנתונים.

משתני לכידת תווים כלליים

כל הצהרות הכללים מצביעות על צמתים. הצהרה יכולה להצביע על צומת ספציפי או להשתמש במשתני לכידת תווים כלליים $ כדי להצביע על קבוצות של צמתים ברמה של ההיררכיה. השתמש במשתני לכידה אלה כדי לאחסן את הערך של מפתחות צומת לשימוש בתוך הצהרות כללים עוקבות. טכניקה זו מאפשרת לך לכתוב תנאי כללים מורכבים יותר, משהו שנעסוק בפירוט רב יותר בנושא הבא.

{
  "rules": {
    "rooms": {
      // this rule applies to any child of /rooms/, the key for each room id
      // is stored inside $room_id variable for reference
      "$room_id": {
        "topic": {
          // the room's topic can be changed if the room id has "public" in it
          ".write": "$room_id.contains('public')"
        }
      }
    }
  }
}

ניתן להשתמש במשתני $ הדינמיים גם במקביל לשמות נתיבים קבועים. בדוגמה זו, אנו משתמשים במשתנה $other כדי להכריז על כלל .validate המבטיח widget אין ילדים מלבד title color . כל כתיבה שתגרום ליצירת ילדים נוספים תיכשל.

{
  "rules": {
    "widget": {
      // a widget can have a title or color attribute
      "title": { ".validate": true },
      "color": { ".validate": true },

      // but no other child paths are allowed
      // in this case, $other means any key excluding "title" and "color"
      "$other": { ".validate": false }
    }
  }
}

קריאה וכתיבה של כללים אשד

חוקי .read .write פועלים מלמעלה למטה, כאשר כללים רדודים יותר עוקפים כללים עמוקים יותר. אם כלל מעניק הרשאות קריאה או כתיבה בנתיב מסוים, הוא גם מעניק גישה לכל הצמתים הצאצאים תחתיו. שקול את המבנה הבא:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          /* ignored, since read was allowed already */
          ".read": false
        }
     }
  }
}

מבנה אבטחה זה מאפשר לקרוא את /bar/ בכל פעם ש- /foo/ מכיל baz ילד עם הערך true . הכלל ".read": false תחת /foo/bar/ אינו משפיע כאן, מכיוון שלא ניתן לבטל את הגישה על ידי נתיב צאצא.

למרות שזה אולי לא נראה אינטואיטיבי באופן מיידי, זהו חלק רב עוצמה משפת הכללים ומאפשר ליישם הרשאות גישה מורכבות מאוד במינימום מאמץ. זה יומחש כאשר ניכנס לאבטחה מבוססת משתמשים בהמשך מדריך זה.

שים לב שכללי .validate אינם משתפכים. יש לעמוד בכל כללי האימות בכל רמות ההיררכיה על מנת שתתאפשר כתיבה.

כללים אינם מסננים

כללים מיושמים בצורה אטומית. כלומר פעולת קריאה או כתיבה נכשלת מיד אם אין כלל במיקום זה או במיקום אב המעניק גישה. גם אם כל נתיב ילד מושפע נגיש, הקריאה במיקום האב תיכשל לחלוטין. שקול את המבנה הזה:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

בלי להבין שהכללים מוערכים בצורה אטומית, זה אולי נראה כאילו שליפת הנתיב /records/ תחזיר rec1 אבל לא rec2 . התוצאה בפועל, לעומת זאת, היא שגיאה:

JavaScript
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
Objective-C
הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  // success block is not called
} withCancelBlock:^(NSError * _Nonnull error) {
  // cancel block triggered with PERMISSION_DENIED
}];
מָהִיר
הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
var ref = FIRDatabase.database().reference()
ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // success block is not called
}, withCancelBlock: { error in
    // cancel block triggered with PERMISSION_DENIED
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // success method is not called
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback triggered with PERMISSION_DENIED
  });
});
מנוחה
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

מכיוון שפעולת הקריאה ב- /records/ היא אטומית, ואין כלל קריאה המעניק גישה לכל הנתונים תחת /records/ , הדבר יביא שגיאה PERMISSION_DENIED . אם אנו מעריכים כלל זה בסימולטור האבטחה במסוף Firebase שלנו, נוכל לראות שפעולת הקריאה נדחתה מכיוון שאף כלל קריאה לא איפשר גישה לנתיב /records/ . עם זאת, שים לב שהכלל עבור rec1 מעולם לא הוערך מכיוון שהוא לא היה בנתיב שביקשנו. כדי להביא rec1 , נצטרך לגשת אליו ישירות:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objective-C
הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
מָהִיר
הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records/rec1");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // SUCCESS!
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback is not called
  }
});
מנוחה
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

הצהרות חופפות

ייתכן שיותר מכלל אחד יחול על צומת. במקרה שבו ביטויי כללים מרובים מזהים צומת, שיטת הגישה נדחית אם אחד מהתנאים הוא false :

{
  "rules": {
    "messages": {
      // A rule expression that applies to all nodes in the 'messages' node
      "$message": {
        ".read": "true",
        ".write": "true"
      },
      // A second rule expression applying specifically to the 'message1` node
      "message1": {
        ".read": "false",
        ".write": "false"
      }
    }
  }
}

בדוגמה שלמעלה, קריאות לצומת message1 יידחו מכיוון שהכללים השניים תמיד false , למרות שהכלל הראשון תמיד true .

הצעדים הבאים

אתה יכול להעמיק את ההבנה שלך בכללי אבטחת מסדי נתונים של Firebase בזמן אמת:

  • למד את המושג העיקרי הבא של שפת הכללים, תנאים דינמיים , המאפשרים לכללים שלך לבדוק הרשאות משתמש, להשוות נתונים קיימים ונכנסים, לאמת נתונים נכנסים, לבדוק את מבנה השאילתות המגיעות מהלקוח ועוד.

  • סקור מקרי שימוש אופייניים באבטחה ואת הגדרות כללי האבטחה של Firebase המטפלים בהם .