Django的URL自主发现

最近部门的领导们要对程序进行安全测试,让我们提供所有的URL,一个一个写太麻烦,因此总结为以下博文:

1, 在django的项目同名的app中有个urls文件,是属于项目的根路由(也就是我本次做的入手点)

2,ROOT_URLCONF = 'XXX.urls'

在django的settings文件中会存在(这个是告诉Django根级路由配置的位置

3, Django既然在settings文件中以字符串的形式来配置,那必然可以改变,或者我们也可以导入这些路由!

问题是,咋办?

道理都懂,问题是咋实现?

4,Django的路由分发会有include,有可能会有namespace,有可能有name

    from django.conf import settings
    from django.utils.module_loading import import_string  # 帮助我们以字符串的形式导入模块
    port = import_string(settings.ROOT_URLCONF)
    print(port)
    print(dir(port))
<module 'blog.urls' from 'C:\Users\lenovo\Desktop\blog\blog\urls.py'>
# 能够使用的方法如下
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'admin', 'include', 'path', 're_path', 'serve', 'settings', 'urlpatterns', 'views']

那既然有URL的方法

port = import_string(settings.ROOT_URLCONF)
    for k in port.urlpatterns:
        print(k)

#
>>>
<URLResolver <URLPattern list> (admin:admin) 'admin/'>
<URLResolver <module 'cnblong.urls' from 'C:\Users\lenovo\Desktop\blog\cnblong\urls.py'> (cnblong:test) 'cnblog/'>
<URLPattern 'login/' [name='login']>
<URLPattern 'logout/' [name='logout']>
<URLPattern '^$'>
<URLPattern 'register/' [name='register']>
<URLPattern 'get_valid_img/'>
<URLPattern 'media/(?P<path>.*)$'>

可以看到的是其中的类型有URLResolver, URLPattern

其中路由分发就是URLResolver

不是路由分发就是URLPattern

def test(request):
    from collections import OrderedDict  # 用于保存路由
    from django.urls import URLPattern, URLResolver
    from django.conf import settings
    from django.utils.module_loading import import_string  # 帮助我们以字符串的形式导入模块
    port = import_string(settings.ROOT_URLCONF)
    for k in port.urlpatterns:
        if isinstance(k, URLResolver):  # 路由分发
            pass
        elif isinstance(k, URLPattern):  # 非路由分发
            if k.name:
                print('1111', k, k.pattern)
            else:
                print('2222', k, k.pattern)
1111 <URLPattern 'login/' [name='login']> login/
1111 <URLPattern 'logout/' [name='logout']> logout/
2222 <URLPattern '^$'> ^$
1111 <URLPattern 'register/' [name='register']> register/
2222 <URLPattern 'get_valid_img/'> get_valid_img/
2222 <URLPattern 'media/(?P<path>.*)$'> media/(?P<path>.*)$

和非路由分发类似的

 if isinstance(k, URLResolver):  # 路由分发
     print('00000', k, k.namespace)

>>>>
00000 <URLResolver <URLPattern list> (admin:admin) 'admin/'> admin
00000 <URLResolver <module 'cnblong.urls' from 'C:\Users\lenovo\Desktop\blog\cnblong\urls.py'> (cnblong:test) 'cnblog/'> test

但是现在问题出现了,路由分发下有多个URL,所以我们得拼接URL,类似于有namespace的URL+分发的URL,那就需要重复调用这个方法进行递归操作

所以,我们的单独写一个函数了,方便拼接

from collections import OrderedDict
from django.conf import settings
from django.utils.module_loading import import_string
from django.urls.resolvers import URLResolver, URLPattern


def check_url_exclude(url):
    '''自定制,过滤一下。 以 xxx 为前缀的 url'''
    exclude_url = [
        "/admin/.*",
        "/login/",
    ]
    for regex in exclude_url:
        if re.match(regex, url):
            return True


def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict):
    '''
    递归获取,所有的url
    :param pre_namespace:  namespace前缀,用于拼接name (namespace:name)
    :param pre_url:  url的前缀, 用于拼接url
    :param urlpatterns:  路由关系列表
    :param url_ordered_dict:  用于保存递归中获取的所有的路由
    :return:
    '''
    for item in urlpatterns:
        if isinstance(item, URLPattern):  # 表示一个 非路由分发。将路由添加到字典中
            if not item.name:  # 判断这个url 有没有,name别名
                continue

       name = item.name
            if pre_namespace:  # 判断当前这个url是不是有namespace前缀。也就是:是否是某一个命名空间中的 name别名
                name = "%s:%s" % (pre_namespace, item.name)
            url = (pre_url + str(item.pattern)).replace("^", "").replace("$", "")
            if check_url_exclude(url):  # 在这里进行自定制的过滤。 过露出我不想要的 哪些url
                continue
            url_ordered_dict[name] = {"name": name, "url": url}

        elif isinstance(item, URLResolver):  # 表示这是一个路由分发。 这里就需要递归了
        namespace = pre_namespace
            if pre_namespace:  # 如果有前缀
                if item.namespace:  # 自己有没有namespace
                    namespace = "%s:%s" % (pre_namespace, item.namespace)# 把之前的pre_namespace 和当前的 item.namespace 拼接。 传给下一次的递归函数。继续进行拼接
            else:
                if item.namespace:
                    namespace = item.namespace
            recursion_urls(namespace, pre_url + str(item.pattern), item.url_patterns, url_ordered_dict)
        # 进入下一次循环之前,pre_url + str(item.pattern) 要拼接上这一次循环的 url。
        # item.url_patterns这一次是 URLResolver 对象的 url_patterns。 中间要加一个 _ 烦得很。 第一次是通过导入拿到的 模块对象。
        # 但是 递归中的不是 模块对象。是一个URLResolver对象。 所以要加一个 _ 。下划线

def get_all_url_dict():
    '''
    获取项目中,所有的url 保存到字典(前提是,每个url必须有name别名)
    :return:
    '''
    url_ordered_dict = OrderedDict()
    md = import_string(settings.ROOT_URLCONF)

    recursion_urls(None, "/", md.urlpatterns, url_ordered_dict)
    # 递归的去获取所有的路由。
    # 第一次循环时,肯定是从 根路由开始, 所以没有前缀 传一个None.
    # "/" 也是因为,第一次循环时。 所有的url 都没有前导 的 "/" 手动的加上。
    # md.urlpatterns 要循环的这个列表。
    # url_ordered_dict 保存所有url 的字典。
    return url_ordered_dict


def multi_permissions(request):
    '''
    批量操作权限
    :param request:
    :return:
    '''
    # 获取项目中,所有的URL
    all_url_dict = get_all_url_dict()
    print(all_url_dict)
    return HttpResponse("OK")
View Code
原文地址:https://www.cnblogs.com/zhoulixiansen/p/11530519.html