多租户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中,如何使用lambda表达式按月计算排名?   java如何更改createTempFile中的默认名称?   java无法找到或加载主类net。Fabrimc。德夫朗。主织物   java如何将应用程序绑定到端口161?   使用Spring Boot运行基于Flyway Java的回调   java如何将源代码库添加到IntelliJ(例如来自Github)?   用于MacOSX上应用程序打包的DMG后台java自定义下拉图标   Java泛型和返回类型   java Docker volume mapping+windows=难以置信的慢?   java gwt gxt文本字段。强制无效   试图使用Map<Class,Function<T,R>>替换Java8中的instanceOf,无法放入映射   java Spring SpEL错误   java如何格式化输出值?   java异步HTTP请求,排队请求   带有Kafka、ClassCastException的java Avro模式?   io我们是否需要在java中为Windows和linux提供单独的文件路径   哈姆克雷斯特爪哇。util。IllegalFormatFlagsException:标志=“”   java组合框所选项目   java如何停止程序以等待用户操作?