在Firebase数据库中编写规则

时间:2019-12-02 13:18:50

标签: java android firebase firebase-realtime-database firebase-security

我正在用android构建可管理用户和班次的应用。可以为每个用户分配多个班次,每个班次可以有多个用户。每个班次都关联一个日期。

这是我在firebase中构建数据库的方式:

 {
  "shift-assign" : {
    "key1" : {
      "111" : true,
      "222" : true,
      "888" : true
    },
    "key2" : {
      "111" : true,
      "222" : true
    }
  },
  "shifts" : {
    "key1" : {
      "date" : "20191202",
      "endTime" : "02:00",
      "numOfEmps" : 4,
      "startTime" : "21:00",
      "wage" : 30
    },
    "key2" : {
      "date" : "20191202",
      "endTime" : "12:00",
      "numOfEmps" : 3,
      "startTime" : "08:00",
      "wage" : 10
    },
    "key3" : {
      "date" : "20191203",
      "endTime" : "06:00",
      "numOfEmps" : 3,
      "startTime" : "00:00",
      "wage" : 30
    }
  },
  "user-assign" : {
    "111" : {
      "key1" : true,
      "key2" : true
    },
    "222" : {
      "key1" : true,
      "key2" : true
    },
    "888" : {
      "key1" : true
    }
  },
  "users" : {
    "111" : {
      "active" : true,
      "email" : "111",
      "firstName" : "aaa",
      "id" : "111",
      "lastName" : "aaa",
      "password" : "111",
      "phone" : "1111111"
    },
    "222" : {
      "active" : true,
      "email" : "222",
      "firstName" : "bbb",
      "id" : "222",
      "lastName" : "bbb",
      "password" : "222",
      "phone" : "4444444"
    },
    "888" : {
      "active" : false,
      "email" : "888",
      "firstName" : "ccc",
      "id" : "888",
      "lastName" : "ccc",
      "password" : "888",
      "phone" : "5555555"
    },
    "99999" : {
      "active" : true,
      "firstName" : "Admin",
      "id" : "99999",
      "password" : "123"
    }
  }
}

当用户尝试将自己分配给班次时,我想先检查一些约束,然后再将其实际分配给班次。

我尝试使用事务,但是代码混乱了,因为我需要引用事务中的不同节点。

我的代码现在看起来像这样:

  private void AssignUserToShift() {
        // Constraint No. 0
        // user cannot be assigned to shifts whose date < today
        if(dates[0].compareTo(LocalDateTime.now().toLocalDate().format(dtf)) < 0){
            Toast.makeText(getApplicationContext(), "Cannot assign to prior dates", Toast.LENGTH_SHORT).show();
            return;
        }
        Map<String, Object> pathsToUpdate = new HashMap<>();
        pathsToUpdate.put("shift-assign/" + shiftKey + "/" + userId, true);
        pathsToUpdate.put("user-assign/" + userId + "/" + shiftKey, true);
        mDatabase.updateChildren(pathsToUpdate, new DatabaseReference.CompletionListener() {
            @Override
            public void onComplete(@Nullable DatabaseError databaseError, @NonNull DatabaseReference databaseReference) {
                if(databaseError == null) {
                    // add to adapter's list in GUI
                    AddUserToList();
                }else{
                    Toast.makeText(getApplicationContext(), "Could not update database entries", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

相反的方法:

private void RemoveUserFromShift() {
    mUser user = userList.stream()
            .filter(u -> u.getId().equals(userId))
            .findAny()
            .orElse(null);
    if(user == null){
        Toast.makeText(UsersAssignedActivity.this, "You are not assigned to this shift.", Toast.LENGTH_SHORT).show();
        return;
    }

    AlertDialog.Builder builder = new AlertDialog.Builder(UsersAssignedActivity.this, R.style.AlertDialogCustom);
    builder.setMessage("Do you wish to remove yourself from this shift?");
    builder.setCancelable(false);
    builder.setPositiveButton("Yes",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int i) {
                    Map<String, Object> pathsToDelete = new HashMap<>();
                    pathsToDelete.put("shift-assign/" + shiftKey + "/" + userId, null);
                    pathsToDelete.put("user-assign/" + userId + "/" + shiftKey, null);
                    mDatabase.updateChildren(pathsToDelete, new DatabaseReference.CompletionListener() {
                        @Override
                        public void onComplete(@Nullable DatabaseError databaseError, @NonNull DatabaseReference databaseReference) {
                            if(databaseError == null){
                                // remove from adapter's list in GUI
                                RemoveUserFromList();
                            }else{
                                Toast.makeText(UsersAssignedActivity.this, "Could not delete database entries", Toast.LENGTH_SHORT).show();
                            }
                        }
                    });
                }
            });
    builder.setNegativeButton("No",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int i) {
                    dialog.cancel();
                }
            });
    AlertDialog alertDialog = builder.create();
    alertDialog.show();
}

所以我想我会改用规则并检查约束服务器端。

我要编写满足以下条件的规则-

  1. 用户将无法分配给已满的班次。 (其numOfEmps等于shift-assign/shiftKey下的子节点数)

  2. 用户在某个日期不能分配超过一半的班次(如果与同一日期相关联的有4个班次,并且用户已经被分配到其中一个班次,则他们只能允许在该日期将自己分配给另外一个班次。)

    1. 用户连续不能工作超过4天。 (如果用户尝试在某个日期将自己分配给某个班次,并且在过去4天中已经将该用户分配给一个班次,则他们将无法为其分配自己的班次)

      < / li>
    2. 最后,如果尚未将用户分配给该班次。我猜这可以通过事务在客户端进行检查。

如何编写这些规则?

1 个答案:

答案 0 :(得分:0)

您的问题中有一个非常广泛的用例。我将向您展示如何从第一个要求开始:

  

用户将无法分配到已满的班次。 (其numOfEmps等于shift-assign / shiftKey下的子节点数)

安全规则无法对数据执行任何汇总。由于此要求要求您比较两个值,因此两个值必须存储在数据库中规则内已知路径下。

您已经拥有numOfEmps,但是您还需要存储已经注册进行轮班的人员数量。例如,您可以将注册人数添加到轮班中,然后在每次添加/删除操作时对其进行更新:

"shifts" : {
  "key1" : {
    "date" : "20191202",
    "endTime" : "02:00",
    "numOfEmps" : 4,
    "numSignups" : 3,
    "startTime" : "21:00",
    "wage" : 30
  },

现在您可以通过以下方式检查安全规则:

{
  "rules": {
    "shift-assigns": {
      "$shiftid": {
        "$uid": {
          ".validate": "root.child('/shifts/$shiftid/numSignups').val() < root.child('/shifts/$shiftid/numOfEmps').val()"
        }
      }
    }
  }
}

当然,您还需要确保用户:

  • 只能使用$uid === auth.uid之类的内容将自己添加到班次中/从班次中删除。
  • 只能添加一次班次,但是您的数据模型已经确保了这一点。 ?
  • 写操作还会更新新的numSignups。尽管有一些涉及,但在安全规则中这是可能的。有关详细说明,请在这里查看我的答案:Is the way the Firebase database quickstart handles counts secure?

完成此操作后,您可以一个接一个地开始其他要求,直到满足所有要求为止。

总而言之,对于所有这些用例,您的安全规则将变得非常复杂。虽然您的要求似乎都可以实现,但是您将需要花费大量的时间来学习如何处理安全规则的局限性,以及如何在其中实现用例。

另一种方法是在Cloud Function中实现相同的逻辑,然后从应用程序代码中调用该逻辑。对于大多数开发人员而言,Cloud Functions更加自然,并且也是有效的选择。