Slack
This article explains how to design a graph-based authorization model for Slack. The article assumes you are familiar with our Key Concepts and Attribute Scopes.
There are four steps to design a model:
- (a) list the design requirements for the permissions system
- (b) define the types of objects that the authorization logic will consider
- (c) define the types of relationships between those types of objects. Since relationships are reflected by edges in the graph, we call this “defining the edge types”
- (d) attach these permissions to these relationships using attributes
In general, model design is an iterative process. While this article is presented linearly, you might expect to loop through these steps until you’ve hit on the cleanest model structure.
A. List Design Requirements
For simplicity, we’ll scope down to a five key permissions:
- Workspace permissions:
manage_space_members
- Channel permissions:
join_channel
view_messages
send_messages
manage_channel_members
Here are our design requirements:
- Workspace 1: Only workspace admins can invite users to a workspace
- W2: Only users who are invited to a workspace can join it
- Channel 1: Each channel exists in exactly one workspace
- C2: If a channel is public, only members of a channel's workspace can join that channel or view messages in that channel
- C3: If a channel is private, only invited members of a channel's workspace can join that channel
- C4: If a channel is private, only members of that channel can view messages in that channel
- C5: Only channel members can send messages in a channel
Since our permissions are channel specific, modeling these 5 permissions with RBAC would require creating and maintaining millions of roles. With UserClouds, we can do it with just 3 object types and 6 edge types.
B. Define the types of objects to consider
For Slack, we have 3 clear object types: User, Workspace and Channel. We could add Messages as a fourth object type here, but since all messages in a channel are treated symmetrically in our scope, this extra fidelity would be redundant.
We could also have modeled private and public channels as separate object types, with separate edge types linking them to the workspace. However, this would have multiplied our number of User-Channel edge types 2x. In general, reducing the number of object types in the system is an effective way to minimize model complexity.
C. Define the edge types
Now we define the relationships between our objects:
User-Workspace Relationships:
A user can be either an admin of a space, a member of the space or invited to the space. Each of these relationships will imply different permissions within the space, so we’ll define them as separate “edge types”.
Relationships: is_space_admin
, is_space_member
, is_space_invited
User-Channel Relationships:
Note here we won’t include the admin or invited roles: all members have the same permissions in the channel, so we don’t need that. And users don’t get invited to slack channels - they get added directly.
Relationships: is_channel_member
Channel-Workspace Relationships:
We’re interested in who has the right to add people to channels and who has the right to join channels. These look different according to whether a channel is public (any channel member can join), private (invite only), or a DM (no invites).
Relationships: is_public
, is_private
D. Add permissions to each of our relationships.
Space-Level Permissions: join-space
Only users who are invited to a space. So let’s add join_space
as a direct
permission to the is_space_invited
edge.
Space-Level Permissions: manage_space_members
That begs the question - who can invite someone to a space? Only soace admins. So let’s add manage_space_members as a direct permission to the is_space_admin edge type. We can use this to enable inviting and deleting.
Channel-Level Permissions: send_messages
& manage_channel_members
Only channel members can send messages. So we’ll add a send_messages: direct permission to the is_channel_member edge type.
Similarly only channel members can invite and remove users from the same spot, so we'll take the same approach with manage_channel_members.
Channel-Level Permissions: join_channel
& view_messages
Who can join a channel on Slack?
- If it is public, anyone in the workspace
- If it is private, only invited workspace participants can join
We’ll model this with two permissions.
- Firstly, we’ll attach a join-channel:direct permission to the is_public edge type. This means that any workspace has the right to join a public channel within it
- Then, we’ll add a join_channel:inherit permission to any edge indicating that a user belongs to a workspace, i.e. we’ll add it to is_space_admin, is_space_member, is_space_invited
This means that, if Alice is a member of Apple’s workspace, and #iphone is a public channel within Apple’s workspace, Alice will get the join_channel permission on #iphone.
Note we don’t need to add any permissions to the is_private edge, since members cannot join private channels - they can only be added to them.
Viewing messages follows the exact same rules: any workspace member can view the messages in a public channel in their workspace. However, only channel members can view messages in private channels.
Summary
We've finished our model design! Here's our list of edge types and their associated attributes:
- User-Workspace:
is_space_admin
manage_space_members:direct
join_channel:inherit
view_messages:inherit
- User-Workspace:
is_space_member
join_channel:inherit
view_messages:inherit
- User-Workspace:
is_space_invited
join_space:direct
- Workspace-Channel:
is_public
join_channel:direct
view_messages:direct
- User-Channel:
is_channel_member
,manage_channel_members:direct
view_messages:direct
send_messages:direct
We’ve designed our model and built the structure. Now we just need to populate our model and implement our permissions checks!
Updated 5 months ago