NEW - BETA User Permissions

LAST EDIT Feb 16 2021

Stream Chat ships with a configurable permission systems that allows you to control with fine grain what users are allowed to do on your chat application.

User permissions are organised as lists of permissions policies. Your chat application and channel types all have their own permission policies that you can adjust.

Application level permissionsCopied!

These permissions allow you to control access to functionality that is common to all channel types such as: user search and user management. By default Stream chat allows you any user to search users, for users to change their own data and forbids any user from changing the role for any user.

Channel types permissionsCopied!

Channel types allow you to support channels with different use cases within a single Chat application. Each channel type can have its own set of features as well as its own set of permissions.

Every time the user performs a request that is channel-specific (ie. send a message) the permission policies for that channel's type will be used.

Permission checkingCopied!

Before we dive into more detail on how a permission policy is structured, let's explain how permission checking works in general.

  1. Retrieve the list of policies for the channel type

  2. Iterate over the list of policies, order is based on priority (highest first)

    1. Check if the policy matches the current request

    2. If there is a match then the policy will decide if the request should continue or be rejected

    3. If there is not match, move to the next in list

Let's also introduce an example of how a list of permissions policies would look like:

  1. Admin users can perform any action

  2. Anonymous users are not allowed

  3. Users can modify their own messages

  4. Users can create channels

  5. Members of a channel can read and send messages

  6. Anything not matching the previous list should not be allowed

Let's now see how this would play out and introduce a couple of users: Tommaso and Thierry. Tommaso is an admin and Thierry a user. Thierry is also a member of the "Sailing" channel.

  1. Thierry posts a message on channel "soccer"

  2. Tommaso edits a message from Thierry

  3. Thierry creates a channel called "founders"

  4. An anonymous user opens the "Sailing" channel

Here's what the outcome of these requests would be:

  1. πŸ™…β€β™‚οΈ Only the last policy matches and it denies this action

  2. πŸ‘Œ Tommaso is an admin, 1st policy is a match

  3. πŸ‘Œ Thierry is a user, 4th policy is a match

  4. πŸ™…β€β™‚οΈ Anonymous users are discarded

Permission PolicyCopied!

Now that we have a better idea about how the permission system works, let's define more precisely the structure of a permission policy.

Name

Internal Name

Description

Example

Name

name

The name of the policy, this serves as a human readable placeholder to remember the policy is all about

"Channel Member Permissions"

Resources

resources

The list of resources this policy applies. This field cannot be empty but you can set it to ["*"] if you want to match any resource.

["ReadChannel", "SendMessage"]

Roles

roles

The list of roles this policy applies. Same as the resources field, this must not be empty and can be set to ["*"] to match any user role.

["channel_member"]

Owner

owner

This is an optional field that you can use to match actions performed on objects that the user owns. Ownership only applies to some resources like messages and channels. When set to true the policy will only match if the user is owner of the resource part for the request (when applicable).

true

Condition

condition

A condition allows you to express a more complex matching logic. This field can be left blank, in that case it will not have any impact. When present, the expression will be evaluated and must return either true, false or null. More information on this is available later.

-

Action

action

The action to perform if the policy matches on resource, role and ownership. This field can either be set to 0 or to 1 where 0 stands to Deny and 1 for Allow.

0

Priority

priority

The priority field defined the position of the policy in the list. Permission policies are evaluated in consistent order from the highest priority to the lowest.

99

Example listCopied!

Let's now write the example from before into a list of real policies.

Name

Resources

Roles

Owner

action

priority

Admin users can perform any action

["*"]

["admin"]

-

1 (Allow)

600

Anonymous users are not allowed

["*"]

["anonymous"]

-

0 (Deny)

500

Users can modify their own messages

["UpdateMessage"]

["user"]

true

1 (Allow)

400

Users can create channels

["CreateChannel"]

["user"]

-

1 (Allow)

300

Members of a channel can read and send messages

["ReadChannel", "CreateMessage"]

["channel_member"]

-

1 (Allow)

200

Anything not matching the previous list should not be allowed

["*"]

["*"]

-

0 (Deny)

100

Permission Policy ConditionsCopied!

Conditions allow you to extend the permission logic to support more advanced use-cases such as user groups, teams and more in general, to create permission policies that take into account the objects involved the API call.

Conditions are expressions that you can write using a syntax close to MongoDB's queries; the same syntax is also used to perform channel and user queries so that you don't need to learn yet another DSL.

Conditions are logical expressions that return true or false. If the expression returns true then the policy will be matched (assuming that the rest of policy matches as well) otherwise the policy will be ignored. Variables such as the current user and channel are available in scope and can be used in the condition.

ExamplesCopied!

  1. user.team and channel.team are the same

  2. user is a member or channel.public is true

ScopeCopied!

This the list of objects that are provided in scope. Any variable that is not defined in scope will be set to a special value called "undefined". An undefined variable is only equal to the undefined variable and will have any property return undefined, the same applies for any missing property for an object. (ie. user.missingField is undefined as well as user.missingField.yetAnother)

This are the variable in scope, any field (custom or not is available as well including nested ones)

  • $user the user performing the request

  • $channel the channel that's part of the request

  • $message the message data

  • $channelRole the role for the user in the channel

  • $otherUser the other user involved in the request

  • $event the event the user is sending

OperatorsCopied!

Expressions can be simple equality checks or chains of multiple checks.

  • $eq equality check

  • $neq dis-equality check

  • $not logical negation

  • $and logical and operator

  • $or logical or operator

  • $in membership check

ErrorsCopied!

Permission policies cannot produce errors, syntax is required to be valid before saving them and because of the way scope is managed (see undefined in the scope section) you can expect conditions will always have a consistent outcome and will never error.

Examples (TODO - not sure about this yet )Copied!

Team / Multi-tenant policyCopied!

1
2
3
{ 
  "$user.team":"$channel.team" 
}

Match a event type

This is an example on how you can create a policy that applies to events with type set to the custom value "join".

1
2
3
{ 
  "$event.type":"join" 
}

Open for members or public

This is a more complex example policy that

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{ 
  "$or":[ 
    { 
      "$and":[ 
        { 
          "$channelRole":"channel_member" 
        }, 
        { 
          "$channel.private":true 
        } 
      ] 
    }, 
    { 
      "$in":{ 
        "$channel.private":[ 
          "undefined", 
          false 
        ] 
      } 
    } 
  ] 
}