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

כללי האבטחה של מסד נתונים בזמן אמת של 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 כלל ".read": false תחת /foo/bar/ אין שום השפעה כאן מכיוון שלא ניתן לבטל גישה על ידי נתיב ילד.

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

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

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

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

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

מבלי להבין שכללים מוערכים באופן אטומי, זה אולי נראה כאילו אחזור של /records/ path יחזיר 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
});
מטרה-ג
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
}];
מָהִיר
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
})
ג'אווה
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/ path. עם זאת, שים לב שהכלל עבור rec1 מעולם לא הוערך מכיוון שהוא לא היה בדרך שביקשנו. כדי להשיג rec1 , נצטרך לגשת אליו ישירות:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
מטרה-ג
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
מָהִיר
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
ג'אווה
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: