在Django Rest框架中添加临时guest (Ephemeral user)



我需要以下内容:

普通用户创建访客。这个客户端必须能够从两个视图中POST和GET。

我已经创建了以下来宾模型:

class Guest(models.Model):
reservation = models.ForeignKey(Reservation, related_name='guests', on_delete=models.CASCADE)
keys        = models.ManyToManyField(Key)
email       = models.EmailField(blank=False)
phone       = models.DecimalField(decimal_places=0, max_digits=10)
name        = models.CharField(max_length=64)
created     = models.DateTimeField(default=timezone.now)
state       = models.IntegerField(default=1,validators=[MaxValueValidator(1),MinValueValidator(0)])
class Meta:
ordering = ['created']

此客人"用户">是一个短寿命的(过几天就删除了)。我希望避免为每个来宾创建一个User实例。

我的方法是允许在ViewA和ViewB上匿名用户,然后,我会检查请求['guest']以获得客人的ID,从数据库检索它并比较请求['headers']令牌值。

关于DRF的权限(伪代码)

类IsGuestActive (permissions.BasePermission):

def has_permission(self, request, view):
if request.user.is_anonymous() 
guest_id =  request['guest']
token    =  request['headers'].token
return (Guest.objects.get(pk=guest_id, token=token) is not None)

我是Django的新手,我的主要问题是:

  1. 我的方法可行吗?
  2. 我应该创建一个用户与外键Guest和使用组代替?

我非常感谢任何关于创建我需要的临时用户的最佳方法的建议。


编辑(1)

关于安全性,使用管道胶带类型的解决方案确实是危险的,因为没有中间件负责对客户机进行身份验证(使系统容易在无意中授予权限)。但是,分析权限类中的头文件的方法并不是不可行的。来自官方文档:

class BlocklistPermission(permissions.BasePermission):
"""
Global permission check for blocked IPs.
"""
def has_permission(self, request, view):
ip_addr = request.META['REMOTE_ADDR']
blocked = Blocklist.objects.filter(ip_addr=ip_addr).exists()
return not blocked

我们可以在这里看到,META用于验证访问。我需要进一步调查。

我找到了一个解决方案,我认为是可行的,创建一个临时控制访问视图,而不必创建一个自定义用户:

exclaier:考虑到使用该系统的来宾的短暂性,这种方法似乎有点安全。它没有解决控制会话或检索访问的问题。

用例:您需要来宾才能访问Views,但是来宾需要特殊授权。此客人将是短暂的。

middleware.py创建一个中间件,在其中定义request.guest = None

class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request.guest = None

models.py现在,我们将把客户机定义为主键作为令牌值:

class Guest(models.Model):
reservation = models.ForeignKey(Reservation, related_name='guests', on_delete=models.CASCADE)
token       = models.CharField(max_length=40, primary_key=True)
...
class Meta:
ordering = ['created']
verbose_name = 'Guest'
verbose_name_plural = 'Guests'
def save(self, *args, **kwargs):
if not self.token:
self.token = self.generate_key()
return super().save(*args, **kwargs)
def generate_key(self):
return binascii.hexlify(os.urandom(20)).decode()
def __str__(self):
return self.token

在我的例子中,我给来宾发送了一个带有令牌作为参数的链接。

由于您不想将令牌存储在本地存储或任何其他不是httpOnly cookie的地方以避免XSS攻击,因此定义一个视图来响应电子邮件上的链接的第一次点击:

class SetGuest(APIView):
authentication_classes  = []
permission_classes      = [permissions.AllowAny]
def get(self, request, token, format=None):
response                = HttpResponse("", status=308)
response['Location']    = "http://localhost/index.html?redirect&action=confirm&status=success"
response.set_cookie(key='guest_token', value=token, path='/', samesite='Lax', max_age=3600*24*7, secure=False, httponly=True)
return response

这个视图将重定向到一个页面,并设置CSRF和guest_token cookie。

定义一个装饰器decorators.py:

def validate_guest(view_func):
def wrapper(self, request, *args, **kwargs):
try: 
token           = request.COOKIES.get('guest_token')
request.guest   = Guest.objects.get(token=token)
except Exception as e:
logger.debug(str(e))            
return redirect('/api/')
return view_func(self, request, *args, **kwargs)
return wrapper

现在,为了保护视图,它只适用于带有令牌的客户机:

class GuestGetKeys(APIView):
authentication_classes  = []
permission_classes      = [IsGuest]
dec_list                = [ensure_csrf_cookie]
@validate_guest
def get(self, request, format=None):
return Response({'status': 'valid', 'success': 'Guest is logged'}, status=status.HTTP_200_OK)

不使用装饰器也可以达到同样的效果,但要使用permissions.py

我不喜欢这样做的原因是,通过装饰器,我可以以更干净的方式控制重定向。

为什么使用中间件定义新的dict值?我尝试直接在Decorator和permissions类上修改请求对象,但它根本不起作用。

这种方法有一些缺点,比如会话撤销问题,没有其他方法来启动"会话"。除了访问发送给来宾的链接之外,令牌是纯文本的,数据库泄露将允许任何人作为来宾访问。会话不会过期,等等。

最新更新