检查字符串是否是任何 URL 模式的前缀

Checking if a string is a prefix of any URL pattern

无论是否不可取,我的 Django 站点在根目录下为每个用户分配一个页面,例如 /rgov

我使用字符集白名单,因此应该 阻止创建 index.html 或一些恶意的 。我的 URL 配置也是最后路由用户页面,所以应该不可能通过注册相应的名称来劫持 /admin 或其他任何东西。

但是,我想阻止用户注册 admin,因为他们的页面会被破坏。

(Similar question,没有一个理想的解决方案,下面会介绍。)


这是我的尝试:

def is_reserved(username):
  r = urls.resolvers.get_resolver('mysite.systemurls')
  hit = False
  for path in ('/{}', '/{}/'):
    try:
      r.resolve(path.format(username))
      hit = True
      break
    except urls.exceptions.Resolver404:
      continue
  return hit

此处,mysite.systemurls 模块定义了除用户页面之外的每个 URL 模式。

这确实阻止了选择用户名 admin,因为有为 /admin/ 定义的路由。但它不阻止api,因为虽然有/api/foo/bar,但没有/api/.

的路由

有没有办法测试是否存在后缀为/api/的路由(例如)?由于 URL 模式是正则表达式,可能不是那么容易,但理论上应该是可以的。

这是我不雅的解决方案,抱歉,造成了不便。我使用 Django 系统检查框架实施了检查。该检查收集应用程序使用的所有 URL 模式,然后从每个模式中提取第一个路径组件。然后它确保 none 这些第一个路径组件

如果您的 URL 模式中有一些 re_path 打破了所做的假设,这将不起作用。

import re

from django import urls
from django.core.checks import register, Error, Tags, Warning

from . import usernames


@register(Tags.urls, Tags.security)
def check_scary_available_usernames(app_configs=None, **kwargs):
  '''
  Checks that there are no URL patterns /x/y where /x itself is not a pattern.
  In this case, /x might be available for user registration, which would be bad.
  '''
  errors, prefixes = [], set()
  r = urls.resolvers.get_resolver('mysite.systemurls')
  descend_into_resolver(r, [], errors, prefixes)

  # Check to make sure none of these usernames is taken or available
  for prefix in prefixes:
    if not prefix:
      continue
    if not usernames.is_reserved(prefix):
      errors.append(Warning(
        'There is no restriction on registering the forbidden username {}, '
        'which would conflict with a URL in use by the system.'.format(prefix)
      ))
    if usernames.user_exists(prefix):
      errors.append(Warning(
        'A user has the forbidden username {}, which conflicts with a URL in '
        'use by the system.'.format(prefix)
      ))
  return errors


def descend_into_resolver(resolver, chain, errors, prefixes):
  for up in resolver.url_patterns:
    regex = up.pattern.regex.pattern
    if isinstance(up, urls.resolvers.URLResolver):
      descend_into_resolver(up, chain + [regex], errors, prefixes)
    elif isinstance(up, urls.resolvers.URLPattern):
      collect_pattern_prefix(chain + [regex], errors, prefixes)
    else:
      errors.append(Warning(
        'Resolver has unexpected URL pattern: {}'.format(repr(up))
      ))


def collect_pattern_prefix(patterns, errors, prefixes):
  # Remember, we are matching against a regular expression pattern! We are not
  # taking a robust approach; if it fails, this could report spurious warnings.
  uberpattern = r''
  for i, pattern in enumerate(patterns):
    if i != 0:
      pattern = pattern.lstrip('^')
    if i != len(patterns) - 1:
      pattern = pattern.rstrip('$')
    uberpattern += pattern
  uberpattern = uberpattern.replace('\/', '/')

  m = re.match(r'^\^?([^/$]*)', uberpattern)
  if m is None:
    errors.append(Warning(
      'Could not determine first component of URL pattern '
      '{}'.format(uberpattern)
    ))
  else:
    prefixes.add(m.group(1))