How to check resource access rights via team membership in firebase security rules?

Viewed 42

A user should be allowed to access a resource if they are in a team that is allowed to access the resource.

How can I do this in security rules ?

I have collections:

teams, with a .members field in each

resources, with a teamsThatCanAccess for each

If i wrote this in js, itd be something like:

canUserAccess = (userId, resource) => {
  teams = resource.teamsThatCanAccess
  hasAccess = false
  teams.forEach((team) => {
    if (userId in team.members) {
      hasAccess = true
    }
  }
  return hasAccess
}

However, as I understand it, security rules dont like loops.

--EDIT--

To illustrate further, the database I'm building will look like something like this:

teams = [
  { name: "teamA", org: "org1", members: ["uid1", "uid2", "uid3"] },
  { name: "teamB", org: "org1", members: ["uid1", "uid2"] },
  { name: "teamC", org: "org1", members: ["uid3", "uid4", "uid5"] },
  { name: "teamD", org: "org2", members: ["uid201", "uid202"] },
]

resources = [
  {
    id: "projectId1",
    name: "project 1",
    org: "org1",
    teamsThatCanAccess: ["teamA", "teamB"],
  },
  {
    id: "projectId2",
    name: "project 2",
    org: "org1",
    teamsThatCanAccess: ["teamA", "teamB", "teamC"],
  },
  {
    id: "projectId3",
    name: "project 3",
    org: "org1",
    teamsThatCanAccess: ["teamC"],
  },
  {
    id: "projectId4",
    name: "project 201",
    org: "org2",
    teamsThatCanAccess: ["teamD"],
  },
]

projectFiles = [
  { content: "document text", project: "projectId1" },
  { content: "document text 2", project: "projectId1" },
  { content: "document text 3", project: "projectId2" },
]
1 Answers

Based on what you described, you have a structure that looks like this:

// document at /teams/someTeamId
{
  "members": [
    "uid1",
    "uid2",
    "uid3"
  ],
  /* ... */
}

// document at /resources/someResourceId
{
  "teamsThatCanAccess": [
    "someTeamId",
    "otherTeamId"
  ],
  /* ... */
}

To secure the data, you will need to introduce a new collection of documents, called something like teamsByUser:

// document at /teamsByUser/uid1
{
  "memberOf": [
    "someTeamId",
    "otherTeamId"
  ]
}

By introducing this array, you can now use the rules.List#hasAny method to find if there is any overlap between the memberOf array in /teamsByUser/{userId} and the teamsThatCanAccess array in /resources/{resourceId}.

This will then allow you to configure your rules as

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /resources/{resourceId} {
      allow read: if resource.data.size() == 0 // is empty?
                  || get(/databases/$(database)/documents/teamsByUser/$(request.auth.uid)).data.memberOf.hasAny(resource.data.teamsThatCanAccess); // accessing user is a member of an allowed team
    }

    // don't forget to add rules to prevent users joining arbitrary teams from clients
  }
}
Related