Permission model with users and user groups in DynamoDB

Viewed 1328

I have a "relational" model, with the following types:

  • Users
  • User Groups
  • Items

Users can be part of multiple groups and groups can have multiple users.

Items have users and groups that are allowed to read or perform operations on them.

The most common query will be, that a user queries an item by its id. For this, it must be checked that the user has the needed permissions to query the item. In other words it or one of its groups must have permissions on the item.

What I came up with so far

A solution would be, that the items have the following properties:

  • Users

    • Primary Key: user-id
    • No sort key
    • Properties: list of groups
  • Items:

    • Primary Key: item-id
    • Sort key:
      • 'data': Data for this item
      • <user/group-id>: Permission for this user or group

So to check permissions, one would have to

  • Get the groups of a user
  • Get the permissions by querying the item-id with the user id and all of the group ids as sort keys (using BatchGetItems).
  • Check that the user or any of its groups have the needed permissions.

The Question

I feel like this can result in huge queries, especially when the number of groups is high.

Is there a way to realize this permission model (or a similar permission model) without so expensive permission checks?

1 Answers

Short answer—keep your permissions separate from your data.

Long answer—you should use a lightweight permissions table that stores the relationships between groups and users and items. You should not use any permission-related sort key on your items, and unless you have a specific reason to allow items with the same Id (such as storing a versioned history of the items), you should probably not use any sort key at all for your items.

The permissions table has two attributes, which I’ll call entity (the hash key) and relationship (the sort key). (You could use whatever names you want, though.) This table also has a GSI where relationship is the hash key and entity is the sort key. Because the rows of this table are small, a query is very inexpensive. You could potentially read >100 rows and consume only 1 RCU.

The entity attribute is simply a userId or groupId. The relationship attribute is a composite of the relationship type and another ID. (You can use whatever you want for IDs, but I'll be using numbers for all of my examples.)

Here is some sample data:

entity      | relationship
===========================================
user-0001   | member-of:group-1000
user-0001   | member-of:group-3000
user-0002   | can-access:item-1111
user-0002   | member-of:group-2000
group-1000  | can-access:item-1111
group-2000  | can-access:item-2222

To find out if a user has permission to access a given item, you need two queries. Query the permissions table for the userId and query the GSI for the “can-access” itemId relationship. Then, compare the two query results to see if there are any groups in common (or if the user has permission to access the item directly).

For example, if you wanted to see if user-0001 can access item-1111, you would query for user-0001 and get back [member-of:group-1000, member-of:group-3000]. Then you would query the GSI for can-access:item-1111 and you would get back [user-0002, group-1000]. Comparing the two results, you would see that user-0001 can access item-1111 by way of group-1000.

An additional benefit of this model is that it's flexible enough to handle other permission use cases. Here is an example of what that data might look like:

entity      | relationship
===========================================
user-0001   | admin-of:group-1000
user-0001   | member-of:group-1000
user-0002   | member-of:group-2000
user-0002   | owner-of:item-1111
group-1000  | can-write:item-1111
group-1000  | can-read:item-1111
group-2000  | can-read:item-1111

In this example, we also have users who can administer groups, we've separated out the read and write permissions for items, and we can define a user or group as the owner of an item (which maybe means that they can modify who has permission to read or write the item).


One caveat is that this assumes groups cannot be nested. If groups can be nested, then you will need more queries to traverse the hierarchy of nested groups, and you might be better off using AWS Neptune or another database for storing your permissions.

Related