Python Django复杂函数自定义(sql函数)

Python Django复杂函数自定义(sql函数),python,django,django-queryset,django-annotate,Python,Django,Django Queryset,Django Annotate,在为找到解决方案的过程中,我创建了一个自定义django Func: from django.db.models import Func class Position(Func): function = 'POSITION' template = "%(function)s(LOWER('%(substring)s') in LOWER(%(expressions)s))" template_sqlite = "instr(lower(%(expressions)s),

在为找到解决方案的过程中,我创建了一个自定义django Func:

from django.db.models import Func

class Position(Func):
    function = 'POSITION'
    template = "%(function)s(LOWER('%(substring)s') in LOWER(%(expressions)s))"
    template_sqlite = "instr(lower(%(expressions)s), lower('%(substring)s'))"

    def __init__(self, expression, substring):
        super(Position, self).__init__(expression, substring=substring)

    def as_sqlite(self, compiler, connection):
        return self.as_sql(compiler, connection, template=self.template_sqlite)
其工作原理如下:

class A(models.Model):
    title = models.CharField(max_length=30)

data = ['Port 2', 'port 1', 'A port', 'Bport', 'Endport']
for title in data:
    A.objects.create(title=title)

search = 'port'
qs = A.objects.filter(
        title__icontains=search
    ).annotate(
        pos=Position('title', search)
    ).order_by('pos').values_list('title', flat=True)
# result is
# ['Port 2', 'port 1', 'Bport', 'A port', 'Endport'] 
pos=Func(Lower(F('title')), Lower(Value(search)), function='INSTR')
但正如@hynekcer所评论的:

在myapp_郊区的“”)中,它通过
”很容易崩溃;丢弃…
应用程序的名称应为“myapp且自动提交已启用”

主要问题是额外的数据(
子字符串
)在没有sqlescape的情况下进入模板,这使得应用程序容易受到SQL注入攻击

我找不到Django的保护方法



我创建了一个可以测试任何解决方案的测试环境。

通常,让您容易受到SQL注入攻击的是。
单引号对之间包含的所有内容都将按其应有的方式进行处理,但未配对的单引号可能会结束字符串,并允许条目的其余部分充当可执行代码。
这正是@hynekcer的例子

Django提供了防止上述情况的方法:

该值将添加到SQL参数列表中,并正确引用

因此,如果您确保通过
Value
方法传递每个用户输入,您将很好:

from django.db.models import Value

search = user_input
qs = A.objects.filter(title__icontains=search)
              .annotate(pos=Position('title', Value(search)))
              .order_by('pos').values_list('title', flat=True)
编辑:

如评论中所述,在上述设置中,这似乎没有达到预期效果。但如果调用如下所示,则它会起作用:

pos=Func(F('title'), Value(search), function='INSTR')

作为一个旁注:为什么首先要弄乱模板

您可以从任何数据库语言(例如:SQLite、PostgreSQL、MySQL等)中找到要使用的函数,并显式使用它:

class Position(Func):
    function = 'POSITION' # MySQL default in your example

    def as_sqlite(self, compiler, connection):
        return self.as_sql(compiler, connection, function='INSTR')

    def as_postgresql(self, compiler, connection):
        return self.as_sql(compiler, connection, function='STRPOS')

    ...
编辑:

您可以在
Func
调用中使用其他函数(如
LOWER
函数),如下所示:

class A(models.Model):
    title = models.CharField(max_length=30)

data = ['Port 2', 'port 1', 'A port', 'Bport', 'Endport']
for title in data:
    A.objects.create(title=title)

search = 'port'
qs = A.objects.filter(
        title__icontains=search
    ).annotate(
        pos=Position('title', search)
    ).order_by('pos').values_list('title', flat=True)
# result is
# ['Port 2', 'port 1', 'Bport', 'A port', 'Endport'] 
pos=Func(Lower(F('title')), Lower(Value(search)), function='INSTR')

基于John Moutafis的思想,最终的功能是(在
\uuuuu init\uuuu
方法中,我们使用
获得安全结果。)

TL;DR: Django文档中的所有示例都可以轻松地使用一个参数安全地实现其他类似的SQL函数。 所有内置Django和
Func()
的后代在设计上也是安全的。超出此限制的应用程序需要注释


是Django查询表达式中最通用的部分。它允许以某种方式将几乎任何函数或运算符实现到Django ORM中。它像一把瑞士军刀,非常通用,但必须比使用专用工具更注意不要割伤自己(就像带光学屏障的电动切割机)。如果一把“升级的”“安全的”袖珍小刀无法放入口袋,那么用铁锤锻造自己的工具就更安全了


安全说明

  • 首先应该阅读带有示例的简短文档(我在此推荐Django 2.0的开发文档,其中最近添加了更多安全信息,包括与您的示例完全相关的信息)

  • *表达式中的所有位置参数
    都是由Django编译的,也就是说,
    值(字符串)
    被移动到参数中,数据库驱动程序在其中正确转义它们

  • 其他字符串被解释为字段名
    F(name)
    ,然后以right
    table\u name作为前缀。
    alias dot,最后添加到该表的联接,并通过
    quote\u name()
    函数处理名称
  • 问题是1.11中的文档仍然很简单,诱人的参数
    **extra
    **extra\u context
    的文档记录得很模糊。它们只能用于将从不“编译”的简单参数"并且永远不要使用SQL
    参数
    。数字或带有安全字符的简单字符串(不带撇号、反斜杠或百分比)是好的。它不能是字段名,因为它不会明确,也不会连接。对于以前检查过的数字和固定字符串(如“ASC”/“DESC”)、时区名称和其他值(如e从下拉列表中删除。仍然存在一个弱点。必须在服务器端检查下拉列表值。还必须验证数字是否为数字,而不是像
    '2'
    那样的数字字符串,因为所有数据库函数都默认接受省略的数字字符串而不是数字。如果传递了错误的“数字”
    '0)从my_app.my_表中;流氓sql;——'然后注射结束。注意,在这种情况下,rogue字符串不包含任何非常禁止的字符。必须特别检查用户提供的数字,或者必须通过位置表达式传递值
  • 可以安全地指定Func类的
    function
    name和
    arg\u joiner
    string属性,或者指定Func()调用的相同
    function
    arg\u joiner
    参数。
    模板
    参数不应在括号内替换的参数表达式周围包含撇号:
    %(表达式)s
    ,因为数据库驱动程序会在必要时添加撇号,但额外的撇号可能会导致它通常无法正常工作,但有时它可能被忽视,这将导致
与安全无关的注释

  • 许多带有一个参数的简单内置函数看起来并不尽可能简单,因为它们是从Func的多用途后代派生的。例如,
    Length
    是一个也可以用作查找的函数

    查找转换将相同的函数应用于查找的左侧和右侧

    # I'm searching people with usernames longer than mine 
    qs = User.objects.filter(username__length__gt=my_username)
    
  • 可以在
    Func.as_sql(…,function=…,template=…,arg_joiner=…)
    中指定的相同关键字参数可以在
    Func.\uuuu init_uuuuuuuuu()
    中指定,如果在自定义as_sql()中未被覆盖,或者可以将它们设置为的属性