多租户django应用程序中的范围查询

django-scopes的Python项目详细描述


django范围

Build StatuscodecovPyPI

动机

我们中的许多人使用django构建多租户应用程序 获取对应用程序中一小部分数据的访问权限,而 同时拥有some全局功能,使每个 客户不可行。而django在保护我们不构建sql方面做得很好 注入漏洞和类似错误,django无法保护我们免受逻辑攻击 错误和多租户最危险的安全问题之一 应用程序是我们在租户之间泄漏数据。

很容易忘记一个.filter调用,很难捕捉到这些错误 在手动和自动测试中,因为您通常没有很多客户机 在你的开发设置中。离开radical, database-dependent ideas 除此之外,生态系统中没有很多方法可以防止这些错误 除了严格的代码审查之外。

我们想把这个模块作为一个灵活的防线。这是注定的 对你的日常工作影响很小,但如果你建立了一个 错误的查询。

安装

除了简单的

pip install django-scopes

兼容性

这个库是针对python 3.5-3.7django 2.1-2.2进行测试的。

用法

假设我们有一个多租户的blog应用程序,由三个模型组成SitePost,和Comment

fromdjango.dbimportmodelsclassSite(models.Model):name=models.CharField()classPost(models.Model):site=models.ForeignKey(Site,)title=models.CharField()classComment(models.Model):post=models.ForeignKey(Post,)text=models.CharField()

在这种情况下,我们的应用程序可能会充满如下语句 Post.objects.filter(site=current_site)Comment.objects.filter(post__site=current_site), 或者当涉及更灵活的权限处理时更复杂。使用django scopes,我们 使用自定义的基于权限的筛选器来编写这些查询,但是 我们添加了一个自定义模型管理器,该模型管理器了解作为 租户范围:

fromdjango_scopesimportScopedManagerclassPost(models.Model):site=models.ForeignKey(Site,)title=models.CharField()objects=ScopedManager(site='site')classComment(models.Model):post=models.ForeignKey(Post,)text=models.CharField()objects=ScopedManager(site='post__site')

关键字参数site定义了scope维度的名称,而字符串 'site''post__site'告诉我们如何查找此作用域维度的值 在ORM查询中。

通过将多个关键字参数传递给 ScopedManager,例如ScopedManager(site='post__site', user='author')如果 与你的用例相关。

现在,有了这个自定义管理器,所有查询一开始都被禁止:

>>> Comment.objects.all()
ScopeError: A scope on dimension "site" needs to be active for this query.

唯一有效的方法是Comment.objects.none(),这对django很有用。 通用视图定义。

在上下文中激活作用域

现在,您可以使用我们的上下文管理器专门允许查询特定的博客站点, 例如:

fromdjango_scopesimportscopewithscope(site=current_site):Comment.objects.all()

这将自动向所有查询添加一个.filter(post__site=current_site)。 同样,我们建议您仍然显式地编写它们,但是很高兴知道 保护。

当然,您仍然可以显式地输入非作用域上下文来访问 系统:

withscope(site=None):Comment.objects.all()

这也可以正确地嵌套在以前定义的范围内。您还可以激活多个 值一次:

withscope(site=[site1,site2]):Comment.objects.all()

把那些with语句放在任何地方听起来都很麻烦?可能一点也不:你可能 已经有了一个中间件,它可以为每个请求确定站点(或者通常是租户) 基于url或登录用户,您可以很容易地使用它来自动包装 它围绕着所有特定于租户的视图。

函数可以使用

fromdjango_scopesimportscopes_disabledwithscopes_disabled():# OR@scopes_disabled()deffun():

自定义管理器类

如果已经在使用自定义管理器类,则可以使用_manager_class将其传递给ScopedManager 关键字如下: 来自django.db导入模型

fromdjango.dbimportmodelsclassSiteManager(models.Manager):defget_queryset(self):returnsuper().get_queryset().exclude(name__startswith='test')classSite(models.Model):name=models.CharField()objects=ScopedManager(site='site',_manager_class=SiteManager)

注意事项

我们希望在默认情况下强制作用域以保持安全,不幸的是 破坏django测试运行器以及pytest django。现在,我们还没有找到 一个比monkeypatch更好的解决方案:

fromdjango.testimportutilsfromdjango_scopesimportscopes_disabledutils.setup_databases=scopes_disabled()(utils.setup_databases)

当使用模型表单时,django将自动按语法在foreign上生成选择字段 键和多对多字段。这在这里不起作用,所以我们提供helper字段 使用 改为空的queryset:

fromdjango.formsimportModelFormfromdjango_scopes.formsimportSafeModelChoiceFieldclassPostMethodForm(ModelForm):classMeta:model=Commentfield_classes={'post':SafeModelChoiceField,}

我们注意到django-filter在生成筛选器集时也运行一些查询。 目前,我们最好的解决方法是:

fromdjango_scopesimportscopes_disabledwithscopes_disabled():classCommentFilter(FilterSet):

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
java使用字符而不是字符串的意义   部分匹配长度字符串相似性的java正则表达式   java获取构造函数错误(错误:“(”或“[”)   java@Value注释无法正常工作SpringBoot   java hasKey在响应中没有看到字段   java JavaFX,没有JFXButtons和所有   在Log4j2中,是否可以根据键从记录器中筛选出某些键值对?   如何使用Java中Kafka的消息,从特定偏移量开始   java在单击后存储动态按钮的值   java Android编码:ViewRootImpl$CalledFromErrorThreadException。[Noob]   通过FileChooser保存pdf格式会提示在java中出现第二个对话框   用于空文件的java Spring集成文件标记   java我们应该如何编写get方法,以便私有字段不会超出其预期范围?   java Eclipse产品问题,Juno RequireHandle:org。日食e4。果心服务