Django 频道自定义权限系统
Django channels custom permissions system
所以我有一个系统,用户可以通过模型成员成为称为盒子的模型的一部分。
成员模型有自己的一组角色,这些角色又有自己的权限。
我有特定的方法来确定一个成员在一个盒子里有哪一组权限。
所以现在我有一个名为 'box_{box_id}' 的 websocket 组,成员可以连接到该组。框相关模型创建等出站事件发送到此组。
但是,有些成员不应该监听基于他们拥有的权限发送的某些事件。
这是将发送到表示事件的组的示例消息
{'event':事件类型,
'data':事件数据}
所以现在,例如,如果用户在框中没有 READ_UPLOADS 权限,则他无法收听类型为 UPLOAD_CREATE 的事件
如何使用 Django 通道实施此类检查?
编辑
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)
这是我目前的消费者。我在消费者之外使用 fire_event
类型。但是,每次我需要获得权限时,我都需要访问数据库。因此,我已经实现了这个权限缓存系统来缓解同样的问题。是否应该更改?
您可以在向客户端发送数据的方法中检查这些权限。由于它们都属于同一个频道组,因此您不能在发送到组的级别进行过滤,至少据我所知是这样。所以你可以这样做:
def receive(self, event):
# update box
...
# notify the members
self.channel_layer.group_send(
f'box_{self.box.id}',
{'type': 'notify_box_update', 'event': EVENT TYPE, 'data': EVENT DATA},
)
def notify_box_update(event):
if has_permission(self.user, event['event'], self.box):
self.send(event)
此处,通知事件通过 channel_layer 发送到组,但只有具有适当权限的用户才能将其发送给下游的用户。您可以在代码中的某处实施 has_permission
方法,以检查给定用户、框和事件类型的权限。
所以我有一个系统,用户可以通过模型成员成为称为盒子的模型的一部分。
成员模型有自己的一组角色,这些角色又有自己的权限。
我有特定的方法来确定一个成员在一个盒子里有哪一组权限。
所以现在我有一个名为 'box_{box_id}' 的 websocket 组,成员可以连接到该组。框相关模型创建等出站事件发送到此组。
但是,有些成员不应该监听基于他们拥有的权限发送的某些事件。
这是将发送到表示事件的组的示例消息 {'event':事件类型, 'data':事件数据}
所以现在,例如,如果用户在框中没有 READ_UPLOADS 权限,则他无法收听类型为 UPLOAD_CREATE 的事件
如何使用 Django 通道实施此类检查?
编辑
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)
这是我目前的消费者。我在消费者之外使用 fire_event
类型。但是,每次我需要获得权限时,我都需要访问数据库。因此,我已经实现了这个权限缓存系统来缓解同样的问题。是否应该更改?
您可以在向客户端发送数据的方法中检查这些权限。由于它们都属于同一个频道组,因此您不能在发送到组的级别进行过滤,至少据我所知是这样。所以你可以这样做:
def receive(self, event):
# update box
...
# notify the members
self.channel_layer.group_send(
f'box_{self.box.id}',
{'type': 'notify_box_update', 'event': EVENT TYPE, 'data': EVENT DATA},
)
def notify_box_update(event):
if has_permission(self.user, event['event'], self.box):
self.send(event)
此处,通知事件通过 channel_layer 发送到组,但只有具有适当权限的用户才能将其发送给下游的用户。您可以在代码中的某处实施 has_permission
方法,以检查给定用户、框和事件类型的权限。