DEV Community

Cover image for Modeling Facebook Groups Access Management
Ege Aytin for Permify

Posted on • Edited on • Originally published at permify.co

Modeling Facebook Groups Access Management

Introduction

We frequently receive inquiries about how to model different types of permissions or policies using Permify Schema Language.

For those who may not be familiar, the schema language is an authorization language that allows you to define arbitrary relations between users and objects to model your authorization logic. And its offered by Permify, an open-source relationship-based authorization service. Just to let you know, I'm one of the maintainers and co-founders of Permify :)

We already have documentation, examples, analogies, and other resources that demonstrate how our Schema Language works and its different aspects. Yet, when it comes to complex cases, there may be multiple approaches that can achieve the same results. So it's quite normal for users to seek our take and best practices for such scenarios.

Therefore we have started to search and implement "real life" examples that held with Permify to our resources, we believe this approach could be the engine to drive more adoption to our schema language.

So in this tutorial article, we’ll examine Facebook Groups authorization model and implement it using Permify.

Facebook Groups And Its Access Control

Facebook Groups is a feature that allows Facebook users to create and join communities centered around various interests, topics, or organizations.

Users can create their groups or search for existing groups to join. When creating a group, users define its purpose, privacy settings, and other details. There are couple of different user types of the group such as member, admin, and moderator with different permissions and abilities.

Each group has its feed where members can post text updates, photos, videos, and other content relevant to the group's topic. Members can engage with posts by liking, commenting, and sharing them. Groups provide additional features like events, polls, file sharing, group chat, and collaborative documents, enabling members to organize events, gather opinions, share files, and communicate within the group.

At some point all of these entity's group, posts, comments, likes, events have a natural linkage between them. As an example comments, likes belongs to a parent post that belongs to a specific group. This relationship-based data structure gives the opportunity to create a robust access control structure with utilizing from ReBAC (relational based access control).

Implementation with Permify

Defining Entities & Relations Between Resources

The very first step to building an authorization model with Permify schema language is creating your entities.

So according to our Facebook Groups example, we have several entities:

User entity represents a user on Facebook.

Group entity represents the Facebook group, and it has several relations including member, admin, and moderator to represent the members, admins, and moderators of the group. Additionally, there are relations to represent the posts and comments in the group.

Post entity represents a post in the Facebook group, and it has relations to represent the owner of the post and the group that the post belongs to.

Comment entity represents a comment on a post in the Facebook group, and it has relations to represent the owner of the comment, the post that the comment belongs to, and the comment itself.

Like entity represents a like on a post in the Facebook group, and it has relations to represent the owner of the like and the post that the like belongs to.

Poll entity represents a poll in the Facebook group, and it has relations to represent the owner of the poll and the group that the poll belongs to.

File entity represents a file in the Facebook group, and it has relations to represent the owner of the file and the group that the file belongs to.

Event entity represents an event in the Facebook group, and it has relations to represent the owner of the event and the group that the event belongs to.

Let's put those entities and their relations to our schema language,

// Represents a user
entity user {}

// Represents a Facebook group
entity group {

    // Relation to represent the members of the group
    relation member @user
    // Relation to represent the admins of the group
    relation admin @user
    // Relation to represent the moderators of the group
    relation moderator @user

}

// Represents a post in a Facebook group
entity post {

    // Relation to represent the owner of the post
    relation owner @user
    // Relation to represent the group that the post belongs to
    relation group @group

}

// Represents a comment on a post in a Facebook group
entity comment {

    // Relation to represent the owner of the comment
    relation owner @user

    // Relation to represent the post that the comment belongs to
    relation post @post
}

// Represents a comment like on a post in a Facebook group
entity like {

    // Relation to represent the owner of the like
    relation owner @user

    // Relation to represent the post that the like belongs to
    relation post @post

}

// Definition of poll entity
entity poll {

     // Relation to represent the owner of the poll
    relation owner @user

    // Relation to represent the group that the poll belongs to
    relation group @group

}

// Definition of file entity
entity file {

    // Relation to represent the owner of the file
    relation owner @user

    // Relation to represent the group that the file belongs to
    relation group @group

}

// Definition of event entity
entity event {

    // Relation to represent the owner of the event
    relation owner @user
    // Relation to represent the group that the event belongs to
    relation group @group

}
Enter fullscreen mode Exit fullscreen mode

‍Defining Permissions

We have several actions attached to the entities, which are limited by certain permissions. Let’s form permission for editing post.

To edit post X in Facebook Groups, users either be an owner/creator of that post or user should have an admin role in the group that post X belongs to.

Permify Schema language supports and, or not operators to achieve permission intersection and exclusion. The keywords action or permission can be used with those operators to form rules for your authorization logic.

So if we specify the edit post action as edit_post, then post entity should be refactored as follows:

// Represents a post in a Facebook group
entity post {

    // Relation to represent the owner of the post
    relation owner @user

    // Relation to represent the group that the post belongs to
    relation group @group

    action edit_post = owner or group.admin

}
Enter fullscreen mode Exit fullscreen mode

As you can see from the group.admin syntax, you can access entities relations with classic dot notation.

Let’s define more advanced permissions. For example, we can define a permission “view_comment" in the comment entity to control access for performing viewing comment action.

In Facebook groups, users are granted to view a comment if:

User is a member of the group which the comment's post belongs.

User is owner/creator of the comment.

Ownership is a basic ReBAC model, the tricky part of this permission is the nested hierarchical structure of first condition: user is a member of the group which the comment's post belongs.

To add this permission to our schema we need to update both the post and the comment entity as follows:

// Represents a post in a Facebook group
entity post {

    ..
    ..

    // Relation to represent the group that the post belongs to
    relation group @group

    // Permissions for the post entity

    ..
    ..
    permission group_member = group.member
}

// Represents a comment on a post in a Facebook group
entity comment {

    // Relation to represent the owner of the comment
    relation owner @user

    // Relation to represent the post that the comment belongs to
    relation post @post
    relation comment @comment

    ..
    ..

    // Permissions 
    action view_comment = owner or post.group_member

    ..
    ..
}
Enter fullscreen mode Exit fullscreen mode

The post.group_member refers to the members of the group to which the post belongs. We defined it as action in post entity as,

// Represents a post in a Facebook group
entity post {

    ..
    ..

    // Relation to represent the group that the post belongs to
    relation group @group

    // Permissions for the post entity

    ..
    ..
    permission group_member = group.member
}

// Represents a comment on a post in a Facebook group
entity comment {

    // Relation to represent the owner of the comment
    relation owner @user

    // Relation to represent the post that the comment belongs to
    relation post @post
    relation comment @comment

    ..
    ..

    // Permissions 
    action view_comment = owner or post.group_member

    ..
    ..
}
Enter fullscreen mode Exit fullscreen mode

Permissions can be inherited as relations in other entities.

This allows to form nested hierarchical relationships between entities.

Turning to our example, a comment belongs to a post that is part of a group. Since there is a 'member' relation defined for the group entity, we can use the 'group_member' permission to inherit the member relation from the group in the post and then use it in the comment as follows:

action view_comment = owner or post.group_member
Enter fullscreen mode Exit fullscreen mode

Complete Schema

For the sake of simplicity of this post I won’t break down and go depth for each permission.

So let’s add the remaining permissions and complete our schema.

// Represents a user
entity user {}

// Represents a Facebook group
entity group {

    // Relation to represent the members of the group
    relation member @user
    // Relation to represent the admins of the group
    relation admin @user
    // Relation to represent the moderators of the group
    relation moderator @user

    // Permissions for the group entity
    action create = member
    action join = member
    action leave = member
    action invite_to_group = admin
    action remove_from_group = admin or moderator
    action edit_settings = admin or moderator
    action post_to_group = member
    action comment_on_post = member
    action view_group_insights = admin or moderator
}

// Represents a post in a Facebook group
entity post {

    // Relation to represent the owner of the post
    relation owner @user
    // Relation to represent the group that the post belongs to
    relation group @group

    // Permissions for the post entity
    action view_post = owner or group.member
    action edit_post = owner or group.admin
    action delete_post = owner or group.admin

    permission group_member = group.member
}

// Represents a comment on a post in a Facebook group
entity comment {

    // Relation to represent the owner of the comment
    relation owner @user

    // Relation to represent the post that the comment belongs to
    relation post @post

    // Permissions for the comment entity
    action view_comment = owner or post.group_member
    action edit_comment = owner
    action delete_comment = owner
}

// Represents a comment like on a post in a Facebook group
entity like {

    // Relation to represent the owner of the like
    relation owner @user

    // Relation to represent the post that the like belongs to
    relation post @post

    // Permissions for the like entity
    action like_post = owner or post.group_member
    action unlike_post = owner or post.group_member
}

// Definition of poll entity
entity poll {

     // Relation to represent the owner of the poll
    relation owner @user

    // Relation to represent the group that the poll belongs to
    relation group @group

    // Permissions for the poll entity
    action create_poll = owner or group.admin
    action view_poll = owner or group.member
    action edit_poll = owner or group.admin
    action delete_poll = owner or group.admin
}

// Definition of file entity
entity file {

    // Relation to represent the owner of the file
    relation owner @user

    // Relation to represent the group that the file belongs to
    relation group @group

    // Permissions for the file entity
    action upload_file = owner or group.member
    action view_file = owner or group.member
    action delete_file = owner or group.admin
}

// Definition of event entity
entity event {

    // Relation to represent the owner of the event
    relation owner @user
    // Relation to represent the group that the event belongs to
    relation group @group

    // Permissions for the event entity
    action create_event = owner or group.admin
    action view_event = owner or group.member
    action edit_event = owner or group.admin
    action delete_event = owner or group.admin
    action RSVP_to_event = owner or group.member

}
Enter fullscreen mode Exit fullscreen mode

Creating Sample Authorization Data

To test the authorization model that was created above, we need to have related data. Each data should represent relationships between entities, objects, and users and builds up a collection of access control lists (ACLs).

In Permify, we use a specific form to represent object-to-object and object-to-subject relations. It’s called relational tuples.

the simplest form of relational tuple structured as entity # relation @ user. Each relational tuple represents an action that a specific user or user set can do on a resource and takes the form of user U has relation R to object O, where user U could be a simple user or a user set such as group X members.

Here are some relational tuples according to our Facebook Groups example,

//group relationships
group:1#member@user:1
group:1#admin@user:2
group:2#moderator@user:3
group:2#member@user:4
group:1#member@user:5

//post relationships
post:1#owner@user:1
post:1#group@group:1
post:2#owner@user:4
post:2#group@group:1

//comment relationships
comment:1#owner@user:2
comment:1#post@post:1
comment:2#owner@user:5
comment:2#post@post:2

//like relationships
like:1#owner@user:3
like:1#post@post:1
like:2#owner@user:4
like:2#post@post:2

//poll relationships
poll:1#owner@user:2
poll:1#group@group:1
poll:2#owner@user:5
poll:2#group@group:1

//like relationships
file:1#owner@user:1
file:1#group@group:1

//event relationships
event:1#owner@user:3
event:1#group@group:1
Enter fullscreen mode Exit fullscreen mode

Test & Validation

Finally, we are able to check some permissions and test our authorization logic.

Let's start with the view action in the event entity,

can user 4 view event:1 ?

entity event {

   // Relation to represent the owner of the event
   relation owner @user
   // Relation to represent the group that the event belongs to

   relation group @group

   // Permissions for the event entity

   ..
   ..

   action view = owner or group.member
}
Enter fullscreen mode Exit fullscreen mode

‍According to what we have defined for the 'view' action, users who are either the owner of event:1 or a member of the group that belongs to event:1 can view the event.
 
According to the relation tuples we created, user:4 is not the owner of the event. 

Furthermore, when we check whether user:4 is a member of the only group (group:1) that event:1 is part of (event:1#group@group:1), we see that there is no member relation for user:4 in that group. 

Therefore, the user:4 view event:1 check request should yield a 'false' response.

can user:5 view comment:1 ?

// Represents a post in a Facebook group
entity post {

    ..
    ..

    // Relation to represent the group that the post belongs to
    relation group @group

    // Permissions for the post entity

    ..
    ..
    permission group_member = group.member
}

// Represents a comment on a post in a Facebook group
entity comment {

    // Relation to represent the owner of the comment
    relation owner @user

    // Relation to represent the post that the comment belongs to
    relation post @post
    relation comment @comment

    ..
    ..

    // Permissions 
    action view_comment = owner or post.group_member

    ..
    ..
}
Enter fullscreen mode Exit fullscreen mode

According to the relation tuples we created, user:5 is not the owner of the comment.

But member of the group:1 and that grant user:5 (group:1#member@user:5) access to perform view the comment:1.

In particular, comment:1 is part of the post:1 (comment:1#post@post:1) and post:1 is part of the group:1 (post:1#group@group:1). And from the action definition on above model group:1 members can view the comment:1.

Therefore, the user:5 view_comment comment:1 check request should yield a 'true' response.

Let's test these two access checks in our locally by using permify validator. We'll use the below schema for the validation yaml file.

schema: >-
    entity user {}

    entity group {

        // Relation to represent the members of the group
        relation member @user
        // Relation to represent the admins of the group
        relation admin @user
        // Relation to represent the moderators of the group
        relation moderator @user

        // Permissions for the group entity
        action create = member
        action join = member
        action leave = member
        action invite_to_group = admin
        action remove_from_group = admin or moderator
        action edit_settings = admin or moderator
        action post_to_group = member
        action comment_on_post = member
        action view_group_insights = admin or moderator
    }

    entity post {

        // Relation to represent the owner of the post
        relation owner @user
        // Relation to represent the group that the post belongs to
        relation group @group

        // Permissions for the post entity
        action view_post = owner or group.member
        action edit_post = owner or group.admin
        action delete_post = owner or group.admin

        permission group_member = group.member
    }

    entity comment {

        // Relation to represent the owner of the comment
        relation owner @user

        // Relation to represent the post that the comment belongs to
        relation post @post

        // Permissions for the comment entity
        action view_comment = owner or post.group_member
        action edit_comment = owner
        action delete_comment = owner
    }

    entity like {

        // Relation to represent the owner of the like
        relation owner @user

        // Relation to represent the post that the like belongs to
        relation post @post

        // Permissions for the like entity
        action like_post = owner or post.group_member
        action unlike_post = owner or post.group_member
    }

    entity poll {

        // Relation to represent the owner of the poll
        relation owner @user

        // Relation to represent the group that the poll belongs to
        relation group @group

        // Permissions for the poll entity
        action create_poll = owner or group.admin
        action view_poll = owner or group.member
        action edit_poll = owner or group.admin
        action delete_poll = owner or group.admin
    }

    entity file {

        // Relation to represent the owner of the file
        relation owner @user

        // Relation to represent the group that the file belongs to
        relation group @group

        // Permissions for the file entity
        action upload_file = owner or group.member
        action view_file = owner or group.member
        action delete_file = owner or group.admin
    }

    entity event {

        // Relation to represent the owner of the event
        relation owner @user
        // Relation to represent the group that the event belongs to
        relation group @group

        // Permissions for the event entity
        action create_event = owner or group.admin
        action view_event = owner or group.member
        action edit_event = owner or group.admin
        action delete_event = owner or group.admin
        action RSVP_to_event = owner or group.member
    }

relationships:
    - group:1#member@user:1
    - group:1#admin@user:2
    - group:2#moderator@user:3
    - group:2#member@user:4
    - group:1#member@user:5
    - post:1#owner@user:1
    - post:1#group@group:1
    - post:2#owner@user:4
    - post:2#group@group:1
    - comment:1#owner@user:2
    - comment:1#post@post:1
    - comment:2#owner@user:5
    - comment:2#post@post:2
    - like:1#owner@user:3
    - like:1#post@post:1
    - like:2#owner@user:4
    - like:2#post@post:2
    - poll:1#owner@user:2
    - poll:1#group@group:1
    - poll:2#owner@user:5
    - poll:2#group@group:1
    - file:1#owner@user:1
    - file:1#group@group:1
    - event:1#owner@user:3
    - event:1#group@group:1

scenarios:
  - name: "scenario 1"
    description: "test description"
    checks:
      - entity: "event:1"
        subject: "user:4"
        assertions:
          RSVP_to_event : false
      - entity: "comment:1"
        subject: "user:5"
        assertions:
          view_comment : true
Enter fullscreen mode Exit fullscreen mode

To use permify validate function clone the Permify from GitHub and open up a new file and copy the schema yaml file content inside. Then, build and run Permify instance using the command make run.

Image description

Then run permify validate {path of your schema validation file} to start the test process. The validation result according to our example schema validation file:

Image description

Conclusion‍

This is the end of the demonstration and implementation of the authorization structure for Facebook groups using Permify. You can find this example on our playground to examine it on your browser.

If you are interested in learning more about our solution or believe that it may be beneficial to your organization, please don't hesitate to join our community on Discord. We welcome the opportunity to chat and share our insights.

Top comments (0)