最近部门的领导们要对程序进行安全测试,让我们提供所有的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")