CMDB权限系统
权限系统
业务场景: CMDB --
用户名密码
生成环境的机器:
root 运维
user 运维
readonly 运维/开发/测试/项目主管
灰度环境的机器:
root 运维
user 运维/测试
readonly 运维/开发/测试/项目主管
测试环境的机器:
root 运维
user 运维/测试
readonly 运维/开发/测试/项目主管
主机
生成环境的机器: (CRUD/增删改查)
运维 CRUD
开发 R
测试 R
项目主管 CR
灰度环境的机器:
运维 CRUD
开发 R
测试 R
项目主管 CR
测试环境的机器:
运维 CRUD
开发 R
测试 R
项目主管 CR
分析:
权限:
人: -> 组(岗位)-> 按钮(url+路径)-> 路径是重点 /list.html -> 组(url组) -> 菜单
路径: list.html -> 查看类 R /host/list.html /user/list.html /readolny/list.html
路径: add.html -> 增加类 C
路径: delete.html -> 删除类 D
路径: update.html -> 编辑类 U
图解:
![](https://upload-images.jianshu.io/upload_images/14972130-66f37baa7d157ab4.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp)
表设计
简单的 5张 表
class UserInfo(models.Model): # 登录用户表
name = models.CharField(max_length=32, blank=True, null=True, verbose_name='登录用户名')
passwd = models.CharField(max_length=128, blank=True, null=True, verbose_name='登录密码')
department = models.CharField(max_length=32, blank=True, null=True, verbose_name='所属部门')
# ForeignKey # 一个岗位 对 多个用户 一对多, foreignkey 的 放在 对多的表里 # 用 related_name 反向查找方便
pos = models.ForeignKey(to='Position', blank=True, null=True, verbose_name='权限', related_name='userpos')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = '主机来源表'
class Position(models.Model): # 岗位,职位 表
name = models.CharField(max_length=32,blank=True, null=True, verbose_name='职位名')
# ManyToMany # 多个职位 对应 多个权限 # 用 related_name 反向查找方便
auth = models.ManyToManyField(to='Auth', blank=True, null=True, verbose_name='权限', related_name='posauth')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = '职位表'
class Auth(models.Model): # 权限表
url = models.CharField(max_length=128, blank=True, null=True, verbose_name='路径')
name = models.CharField(max_length=64, blank=True, null=True, verbose_name='显示标识')
# ForeignKey # 一个组 对应多个 权限 # 用 related_name 反向查找方便
group = models.ForeignKey(to='AuthGroup', blank=True, null=True, verbose_name='组', related_name='authgroup')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = '权限表'
class AuthGroup(models.Model): # 组表
name = models.CharField(max_length=16, blank=True, null=True, verbose_name='组名')
# ForeignKey # 一个组 对应 多个菜单
ti = models.ForeignKey(to='Menu', blank=True, null=True, verbose_name='菜单', related_name='groupmenu')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = '组表'
class Menu(models.Model): # 菜单表
title = models.CharField(max_length=32, blank=True, null=True, verbose_name='菜单名')
def __str__(self):
return self.title
class Meta:
verbose_name_plural = '菜单栏'
创建数据库,并迁移python manage.py makemigrations
python manage.py migrate
修改 admin.py 将其显示在后台界面
from django.contrib import admin
# Register your models here.
from hc.models import UserInfo
from hc.models import Position
from hc.models import Auth
from hc.models import AuthGroup
from hc.models import Menu
admin.site.register(UserInfo)
admin.site.register(Position)
admin.site.register(Auth)
admin.site.register(AuthGroup)
admin.site.register(Menu)
图解
![](https://upload-images.jianshu.io/upload_images/14972130-5cc74541e3c9455f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp)
权限表设计
登录django admin后台进行
添加数据
添加表关系
url
/user/list/
/user/add/
/user/update/(d+)/
/user/delete/(d+)/
/menu/list/
/menu/add/
/menu/update/(d+)/
/menu/delete/(d+)/
/position/list/
/position/add/
/position/update/(d+)/
/position/delete/(d+)/
/auth/list/
/auth/add/
/auth/update/(d+)/
/auth/delete/(d+)/
/authgroup/list/
/authgroup/add/
/authgroup/update/(d+)/
/authgroup/delete/(d+)/
/host/list/
/host/add/
/host/update/(d+)/
/host/delete/(d+)/
登录后数据需求分析
通过ORM 取得其数据
列如:
老板 /auth/list/ 查询权限 权限表 权限
老板 /auth/add/ 添加权限 权限表 权限
老板 /auth/update/(d+)/ 编辑权限 权限表 权限
老板 /auth/delete/(d+)/ 删除权限 权限表 权限
老板 /host/list/ 查询主机 主机 主机
老板 /host/add/ 添加主机 主机 主机
老板 /host/update/(d+)/ 编辑主机 主机 主机
老板 /host/delete/(d+)/ 删除主机 主机 主机
数据的应用场景:
url :
1 . --> 菜单的跳转地址 : /host/list/
2 . --> 验证用户有没有改地址的访问权限 -> 按钮 -> 删除、编辑
{权限: #template -> 循环这个字典取到值, <a href="{{xx.pos__auth__name}}"> {{xx.pos__auth__name}}</a> #通过<a>标签跳转
[{'pos__auth__name':'查询权限' , 'pos__auth__url':'/auth/list/' } , # /auth/list/ -> 按钮 -> 删除、编辑 都放在查看的页面上,
{'pos__name': 查询岗位},
{'pos__auth__group__ti__title': 查询菜单},
{'name': 查询登录用户},
{'pos__auth__group__name': 查询权限组},
]}
为了解决 跳转页面时,也保留菜单栏的数据,
可以再auth表里添加一个 foreignkey 关联自己的方式自锁
to_display = models.ForeignKey(to='Auth', blank=True, null=True, verbose_name='显示', related_name='authauth')
{ 'pos__auth__group__ti__title': [ # 这里仅查询 # 对应url
{ 'pos__auth__name':'查询权限' , 'pos__auth__url': '/auth/list/' },
{ },
{ },
{ },
]
}
判断登录用户的权限
权限
职位表(组) # 有就循环,没有就过
查看* -> 根据 权限,输出 URL # 岗位 对应 增查改删的权限
boss -> 增查改删
运维 -> 增查改
测试 -> 增查
开发 -> 查
菜单表(组) # 有就循环,没有就过
查看* -> 根据 权限,输出 URL
boss -> 增查改删
运维 -> 增查改
测试 -> 增查
开发 -> 查
权限表(组) # 有就循环,没有就过
查看* -> 根据 权限,输出 URL
boss -> 增查改删
运维 -> 增查改
测试 -> 增查
开发 -> 查
权限组表(组) # 有就循环,没有就过
查看* -> 根据 权限,输出 URL
boss -> 增查改删
运维 -> 增查改
测试 -> 增查
开发 -> 查
主机
用户表(组) # 有就循环,没有就过
查看* -> 根据 权限,输出 URL
boss -> 增查改删
运维 -> 增查改
测试 -> 增查
开发 -> 查
主机表(组) # 有就循环,没有就过
查看* -> 根据 权限,输出 URL
boss -> 增查改删
运维 -> 增查改
测试 -> 增查
开发 -> 查
权限代码演示
登录后要做的两件事:
- 把菜单相关的数据拿到
- 把用户所有能访问的url拿到
views.py
host_obj = models.UserInfo.objects.filter(name=username, passwd=password)
if host_obj.first():
obj_all = host_obj.values('name', # 使用values()方法时,对象必须是queryset_list
'pos__name',
'pos__auth__url',
'pos__auth__name',
'pos__auth__group__name',
'pos__auth__group__ti__title',
)
menu_dict = {} # 自定义字典数据
for i in obj_all:
menu_auth_dict = {
'url': i.get('pos__auth__url'), # 赋值
'name': i.get('pos__auth__name'),
'display_url': i.get('pos__auth__to_display__url'),
'display_name': i.get('pos__auth__to_display__name'),
}
if i.get('pos__auth__group__ti__title') in menu_dict.keys() :
if not i.get('pos__auth__to_display__name'):
menu_dict.get(i.get('pos__auth__group__ti__title')).get('lower').append(menu_auth_dict)
else:
menu_dict.get(i.get('pos__auth__group__ti__title')) = {}
menu_dict.get(i.get('pos__auth__group__ti__title')).get('title') = i.get('pos__auth__group__ti__title')
if not i.get('pos__auth__to_display__name'):
menu_dict.get(i.get('pos__auth__group__ti__title')).get('lower') = [menu_auth_dict,]
else:
menu_dict.get(i.get('pos__auth__group__ti__title')).get('lower') = []
print ('菜单 --- ', menu_dict)
auth_dict = {}
for i in obj_all:
if i.get('pos__auth__group__name') in auth_dict.keys():
auth_dict.get(i.get('pos__auth__group__name')).get('url').append(i.get('pos__auth__url'))
else:
auth_dict.get(i.get('pos__auth__group__name')) = {'url':[i.get('pos__auth__url'),],}
print ('权限 --- ',auth_dict)
输出结果:
菜单
{
'主机': {
'title': '主机',
'lower': [{
'url': '/user/list/',
'name': '查询用户',
'display_url': None,
'display_name': None
},
{
'url': '/host/list/',
'name': '查询主机',
'display_url': None,
'display_name': None
}]
},
'权限': {
'title': '权限',
'lower': [{
'url': '/menu/list/',
'name': '查询菜单',
'display_url': None,
'display_name': None
}, {
'url': '/position/list/',
'name': '查询职位',
'display_url': None,
'display_name': None
}, {
'url': '/authgroup/list/',
'name': '查询权限组',
'display_url': None,
'display_name': None
}, {
'url': '/auth/list/',
'name': '查询权限',
'display_url': None,
'display_name': None
}]
}
}
所有权限 ---> django 中间件,判断登录用户的权限,来显示内容
{
'用户表': {
'url': ['/user/list/', '/user/add/', '/user/update/(\d+)/', '/user/delete/(\d+)/']
},
'菜单表': {
'url': ['/menu/list/', '/menu/add/', '/menu/update/(\d+)/', '/menu/delete/(\d+)/']
},
'职位表': {
'url': ['/position/list/', '/position/add/', '/position/update/(\d+)/', '/position/delete/(\d+)/']
},
'权限组表': {
'url': ['/authgroup/list/', '/authgroup/add/', '/authgroup/update/(\d+)/', '/authgroup/delete/(\d+)/']
},
'权限表': {
'url': ['/auth/list/', '/auth/add/', '/auth/update/(\d+)/', '/auth/delete/(\d+)/']
},
'主机': {
'url': ['/host/list/', '/host/add/', '/host/update/(\d+)/', '/host/delete/(\d+)/']
}
}
菜单层级
一级菜单1 -->(1,'')
第二级菜单(2, 1)
第三级菜单 (3, 2)
一级菜单2 -->(1,'')
第二级菜单(2, 1)
第三级菜单 (3, 2)
# 如果是空的则是母菜单,有值才是该母菜单的子菜单,层级关系
可以封装为一个函数
通过传值即可调用函数
可以将结果存入
- 内存
- 数据库
- NoSQL
- session
这里放在session 里方便
views.py 视图
from hc_auth import auth_data #导入函数
def auth_demo(request):
if request.method == 'GET':
return render(request, 'login_pos.html' ,locals())
else :
username = request.POST.get('username')
password = request.POST.get('password')
host_obj = models.UserInfo.objects.filter(name=username, passwd=password)
if host_obj.first():
auth_data.menu_auth(host_obj, request) # 调用函数,并传值
return redirect('/authority/index/') #
else:
return HttpResponse('错误登录')
def index(request):
menu_dict = request.session.get('menu_dict')
return render(request, 'auth_index.html', locals())
封装函数体
auth_data.py
def menu_auth(host_obj,request):
obj_all = host_obj.values('name', # 使用values()方法时,对象必须是queryset_list
'pos__name',
'pos__auth__url', # 这是 url 路径
'pos__auth__name', # 这是 对应的 名字
'pos__auth__to_display__url', # 权限对应权限的 路径
'pos__auth__to_display__name', # 权限对应权限的 名字
'pos__auth__group__name', # 权限组名
'pos__auth__group__ti__title', # 菜单名
)
menu_dict = {} # 自定义字典数据
for i in obj_all:
menu_auth_dict = { # 临时字典
'url': i.get('pos__auth__url'), # 把数据库取到的值,赋给字典
'name': i.get('pos__auth__name'),
'display_url': i.get('pos__auth__to_display__url'),
# to_display 里面是对应单个查询的权限名的ID,
'display_name': i.get('pos__auth__to_display__name'),
# ID里面包含了 对应ID 的url + name
} # {'url': '/auth/update/(\d+)/', 'name': '编辑权限', 'display_url': '/auth/list/', 'display_name': '查询权限'}
# print (i.get('pos__auth__group__ti__title'),menu_dict.keys()) # 菜单名 ( 主机 , dict_keys([]) )
if i.get('pos__auth__group__ti__title') in menu_dict.keys():
# 第一次是空字典,所以是走else
if not i.get('pos__auth__to_display__name'):
# 第二次 字典有值了 判断 to_display 一对多关系
# not + None 为负负得正
menu_dict[i.get('pos__auth__group__ti__title')]['lower'].append(menu_auth_dict)
# 将字典的lower 的值, 是一个列表 [{},{},{}],往里面添加列表的值(字典) -> append(dict)
else:
menu_dict[i.get('pos__auth__group__ti__title')] = {} # 创建新字典格式 # {'主机': {}}
menu_dict[i.get('pos__auth__group__ti__title')]['title'] = i.get('pos__auth__group__ti__title')
# print (menu_dict[i.get('pos__auth__group__ti__title')]) # {'title': '主机'}
# print (menu_dict[i.get('pos__auth__group__ti__title')]['title'] ) # title -> 主机
# print (i.get('pos__auth__to_display__name')) # -> None 取不到
if not i.get('pos__auth__to_display__name'): # not + None 为负负得正
menu_dict[i.get('pos__auth__group__ti__title')]['lower'] = [menu_auth_dict, ]
# 创建 lower -> {'lower': [{}]}
else:
menu_dict[i.get('pos__auth__group__ti__title')]['lower'] = []
# i.get('pos__auth__to_display__name') 取到值了,就创建列表
# print('菜单 --- ', menu_dict)
request.session['menu_dict']=menu_dict # 存入session
auth_dict = {}
for i in obj_all:
if i.get('pos__auth__group__name') in auth_dict.keys():
auth_dict.get(i.get('pos__auth__group__name')).get('url').append(i.get('pos__auth__url'))
# 将每个url 以列表的append的方法添加进去,成为单个权限组名的 值 {'用户表':{url: ['a','b','c',]}, }
else:
auth_dict[i.get('pos__auth__group__name')] = {'url': [i.get('pos__auth__url'), ], }
# 将 权限组名 作为 auth_dict 字典的 keys, 将对应权限组的url 作为 auth_dict 字典的 values
# print('权限 --- ', auth_dict)
request.session['auth_dict']=auth_dict # 存入session
templates
auth_index.html
<div id='auth_index'>
{% for i in menu_dict.values %}
<h2>{{i.title}}</h2>
{% for ii in i.lower %}
<div>
<a href="{{ii.url}}">{{ii.name}}</a>
</div>
{% endfor %}
{% endfor %}
</div>
代码整理及总结
中间件判断
middleware
import re
VALID_LIST = ['/authority/demo/',] # 白名单 列表
class Middleware_auth(MiddlewareMixin):
def process_request(self, request):
current_url = request.path_info
# print ('当前的url ---> ',current_url) # 取 当前的访问的 url
# 白名单设置
for i in AUTH_LIST:
if re.match(i, current_url): # 匹配白名单,匹配不到 就返回 空,不走if里面的内容
return None # 匹配 中了 就往 视图 走
# 取session信息 并判断
auth_dict = request.session.get('auth_dict') # 取 当前请求的 session 信息
# print('session 信息 ---> ',auth_dict)
if not auth_dict: # Not + (取不到值就为空,取到了就是true) # 负负得正走if 的内容,负正跳过当前if 判断
return redirect('/authority/demo/') # 跳去权限登录页面
flag = False # 打标记开关
for group_name, auth_url in auth_dict.items(): # 取到的是个字典,用for循环可以吧items的key和values拿出来用
for url in auth_url.get('url'): # 因为一个K和V的字典,所用直接get values 才能 来循环里面的url
# print ('--------- url的拼接 ----------')
# print(url) # url
regax = "^{0}$".format(url) # 将它拼接
# print (regax) # 拼接的结果
if re.match(regax, current_url): # 循环判断数据库里的url。 再用re 模块来判断它是否匹配该当前的url
request.permission_code_list = auth_url['url'] #往request里面增加数据,增加的数据是匹配的数据
flag = True # 打开标志,停止当前循环
break
if flag: # 标志位为 True 联动开关,停止循环
break
if not flag: # 如果上面的匹配没通过,则 标志位不变,即为 无权限用户,跳转至提示页面
return HttpResponse('该用户无权访问')
str. format 字符串拼接技巧
http://www.runoob.com/python/att-string-format.html
template 模板的继承
{% extends 'auth_index.html' %}
# 继承它需要在 /host/list/ 页面的视图里添加数据
# menu_dict = request.session.get('menu_dict')
{% block content %}
{% if '/host/add/' in pos_list %}
<div>
<a href="/host/add/"> 添加主机 </a>
</div>
{% endif %}
<table class="table table-bordered table-hover" id="table">
<h3> 当前主机的详细信息! </h3>
<thead >
<tr>
<th> </th>
<th> 主机名 </th>
<th> 实例名 </th>
<th> CPU </th>
<th> 内存/G </th>
<th> 带宽/M </th>
<th> 登录端口 </th>
<th> 公网IP </th>
<th> 主机状态 </th>
<th> 内核版本 </th>
<th> 操作系统 </th>
<th> 来源IP </th>
<th> 标签 </th>
<th>地区</th>
{% if '/host/update/(\d+)/' in pos_list %}
<th>编辑</th>
{% endif %}
{% if '/host/delete/(\d+)/' in pos_list %}
<th>删除</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for host in hosts_page %}
<tr>
<td>{{ host.id }}</td>
<td>{{ host }}</td>
<td> {{ host.ecsname }} </td>
<td> {{ host.cpu }} </td>
<td> {{ host.mem }} </td>
<td> {{ host.speed }} </td>
<td> {{ host.login_port }} </td>
<td> {{ host.eth1_network }} </td>
<td> {{ host.get_state_display }} </td>
<td> {{ host.kernel }} </td>
<td> {{ host.os }} </td>
<td> {{ host.source }} </td>
<td> {{ host.lab }} </td>
<td>{{ host.region }}</td>
{% if '/host/update/(\d+)/' in pos_list %}
<td><a href="/host/edit/{{ host.id }}"> 编辑 </a></td>
# (d+) 因为我url里写了匹配数字,所以这里直接可以写成"/host/edit/{{ host.id }}"
{% endif %}
{% if '/host/delete/(\d+)/' in pos_list %}
<td><a href="/host/del?id={{ host.id }}"> 删除 </a></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table >