So I have a system where users can be part of models called boxes through the model member.
Member models have their own set of roles which in turn have their own permissions.
I have specific methods which determine which set of permissions a member has in a box.
So now I have a websocket group named 'box_{box_id}' to which members can connect. Outbound events such as box related model creation are sent to this group.
However, some members should not listen to certain events sent based on the permissions they have.
This is a sample message that would be sent to the group which denotes an event {'event': EVENT TYPE, 'data': EVENT DATA}
So now, for example, an user cannot listen to the event with type UPLOAD_CREATE if he doesnt have READ_UPLOADS permissions in the box
How can I implement such checks using django channels?
EDIT
class LocalEventsConsumer(AsyncWebsocketConsumer):
"""
An ASGI consumer for box-specific (local) event sending.
Any valid member for the given box can connect to this consumer.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.box_id = self.scope['url_route']['kwargs']['box_id']
self.events_group_name = 'box_%s_events' % self.box_id
self.overwrites_cache = {}
self.permissions_cache = set()
# need to update cache on role and overwrite updates
async def connect(self):
try:
# we cache the member object on connection
# to help check permissions later on during
# firing of events
member_kwargs = {
'user': self.scope['user'],
'box__id': self.box_id,
}
self.member = api_models.Member.objects.get(**member_kwargs)
self.permissions_cache = self.member.base_permissions
except ObjectDoesNotExist:
# we reject the connection if the
# box-id passed in the url was invalid
# or the user isn't a member of the box yet
await self.close()
await self.channel_layer.group_add(self.events_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.events_group_name, self.channel_name)
async def fire_event(self, event: dict):
member_permissions = self.get_event_permissions(event)
required_permissions = event.pop('listener_permissions', set())
if required_permissions in member_permissions:
await self.send(event)
def get_event_permissions(self, event):
# handle permission caching throughout
# the life of the user's connection
overwrite_channel = event['data'].get('channel', None)
overwrite_cache = self.overwrites_cache.get(overwrite_channel.id, None)
if not overwrite_channel:
# calculate overwrites if the event data at hand
# has a channel attribute. We would need to calculate
# overwrites only when channel-specific events are
# triggered, like UPLOAD_CREATE and OVERWRITE_DELETE
return self.permissions_cache
if not overwrite_cache:
overwrite_cache = self.member.permissions.get_overwrites(overwrite_channel)
self.overwrites_cache[overwrite_channel.id] = overwrite_cache
return overwrite_cache
@receiver(post_delete, sender=api_models.MemberRole)
@receiver(post_save, sender=api_models.MemberRole)
def update_permissions_cache(self, instance=None, **kwargs):
if instance.member == self.member:
self.permissions_cache = self.member.base_permissions
@receiver(post_delete, sender=api_models.Overwrite)
@receiver(post_save, sender=api_models.Overwrite)
def update_overwrites_cache(self, instance=None, **kwargs):
overwrite_cache = self.overwrites_cache.get(instance.channel, None)
if instance.role in self.member.roles.all() and overwrite_cache:
self.overwrites_cache[instance.channel] = self.member.permissions.get_overwrites(instance.channel)
this is my current consumer. I use the fire_event type outside the consumer. However, everytime I need to get the permissions, I need to make a trip to the database. Therefore, I've implemented this permission caching system to mitigate the same. Should the same be altered?