Skip to content

Commit

Permalink
增加登录审计日志 (#1251)
Browse files Browse the repository at this point in the history
* 增加登录审计日志

Co-authored-by: ningyu <ningyu@jiuyescm.com>
  • Loading branch information
ningyu1 and ningyu committed Dec 13, 2021
1 parent b607bb0 commit f35e633
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 3 deletions.
3 changes: 3 additions & 0 deletions common/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@
</ul>
<!-- /.nav-third-level -->
</li>
<li>
<a href="/audit/">登录审计日志</a>
</li>
</ul>
<!-- /.nav-second-level -->
</li>
Expand Down
10 changes: 9 additions & 1 deletion sql/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
AliyunRdsConfig, CloudAccessKey, ResourceGroup, QueryPrivilegesApply, \
QueryPrivileges, InstanceAccount, InstanceDatabase, ArchiveConfig, \
WorkflowAudit, WorkflowLog, ParamTemplate, ParamHistory, InstanceTag, \
Tunnel
Tunnel, AuditEntry


# 用户管理
Expand Down Expand Up @@ -244,3 +244,11 @@ class ArchiveConfigAdmin(admin.ModelAdmin):
@admin.register(CloudAccessKey)
class CloudAccessKeyAdmin(admin.ModelAdmin):
list_display = ('type', 'key_id', 'key_secret', 'remark')


# 登录审计日志
@admin.register(AuditEntry)
class AuditEntryAdmin(admin.ModelAdmin):
list_display = ('user_id', 'user_name', 'action', 'ip', 'action_time')
list_filter = ('user_id', 'user_name', 'action', 'ip')

72 changes: 72 additions & 0 deletions sql/audit_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- coding: UTF-8 -*-
import logging
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
from django.dispatch import receiver
from .models import AuditEntry, Users
from django.utils import timezone
from common.utils.permission import superuser_required
from django.http import HttpResponse
import simplejson as json
from common.utils.extend_json_encoder import ExtendJSONEncoder
from django.db.models import Q

log = logging.getLogger('default')


@superuser_required
def audit_log(request):
"""获取登录审计日志列表"""
limit = int(request.POST.get('limit'))
offset = int(request.POST.get('offset'))
limit = offset + limit
search = request.POST.get('search', '')

# 过滤搜索条件
audit_log_obj = AuditEntry.objects.filter(Q(user_name__icontains=search) | Q(action__icontains=search)| Q(ip__icontains=search))
audit_log_count = audit_log_obj.count()
audit_log_list = audit_log_obj.order_by('-action_time')[offset:limit].values("user_id", "user_name", "ip", "action", "action_time")

# QuerySet 序列化
rows = [row for row in audit_log_list]

result = {"total": audit_log_count, "rows": rows}
# 返回查询结果
return HttpResponse(json.dumps(result, cls=ExtendJSONEncoder, bigint_as_string=True),
content_type='application/json')


def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip


@receiver(user_logged_in)
def user_logged_in_callback(sender, request, user, **kwargs):
ip = get_client_ip(request)
now = timezone.now()
AuditEntry.objects.create(action=u'登入', ip=ip, user_id=user.id, user_name=user.username, action_time=now)


@receiver(user_logged_out)
def user_logged_out_callback(sender, request, user, **kwargs):
ip = get_client_ip(request)
now = timezone.now()
AuditEntry.objects.create(action=u'登出', ip=ip, user_id=user.id, user_name=user.username, action_time=now)


@receiver(user_login_failed)
def user_login_failed_callback(sender, credentials, **kwargs):
now = timezone.now()
user_name = credentials.get('username', None)
user_obj = Users.objects.filter(username=user_name)[0:1]
user_count = user_obj.count()
user_id = 0
if user_count > 0:
user_id = user_obj[0].id
AuditEntry.objects.create(action=u'登入失败', user_id=user_id, user_name=user_name
, action_time=now)

25 changes: 25 additions & 0 deletions sql/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -853,3 +853,28 @@ class Meta:
index_together = ('hostname_max', 'ts_min')
verbose_name = u'慢日志明细'
verbose_name_plural = u'慢日志明细'


class AuditEntry(models.Model):
"""
登录审计日志
"""
user_id = models.IntegerField('用户ID')
user_name = models.CharField('用户名称', max_length=255, null=True)
action = models.CharField('动作', max_length=255)
ip = models.GenericIPAddressField('IP', null=True)
action_time = models.DateTimeField('操作时间', auto_now_add=True)

class Meta:
managed = True
db_table = 'audit_log'
verbose_name = u'审计日志'
verbose_name_plural = u'审计日志'

def __unicode__(self):
return '{0} - {1} - {2} - {3} - {4}'.format(self.user_id, self.user_name, self.ip
, self.action, self.action_time)

def __str__(self):
return '{0} - {1} - {2} - {3} - {4}'.format(self.user_id, self.user_name, self.ip
, self.action, self.action_time)
94 changes: 94 additions & 0 deletions sql/templates/audit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{% extends "base.html" %}

{% block content %}
<!-- 表格-->
<div class="table-responsive">
<table id="group-list" data-toggle="table" class="table table-striped table-hover"
style="table-layout:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
</table>
</div>
{% endblock content %}
{% block js %}
{% load static %}
<script src="{% static 'bootstrap-table/js/bootstrap-table-export.min.js' %}"></script>
<script src="{% static 'bootstrap-table/js/tableExport.min.js' %}"></script>
<script>
//获取列表
function grouplist() {
//采取异步请求
//初始化table
$('#group-list').bootstrapTable('destroy').bootstrapTable({
escape: true,
method: 'post',
contentType: "application/x-www-form-urlencoded",
url: "/audit/log/",
striped: true, //是否显示行间隔色
cache: false, //是否使用缓存默认为true所以一般情况下需要设置一下这个属性*
pagination: true, //是否显示分页*
sortable: true, //是否启用排序
sortOrder: "asc", //排序方式
sidePagination: "server", //分页方式client客户端分页server服务端分页*
pageNumber: 1, //初始化加载第一页默认第一页,并记录
pageSize: 14, //每页的记录行数*
pageList: [20, 30, 50, 100], //可供选择的每页的行数*
showExport: true, //是否显示导出按钮
exportOptions: {
fileName: 'audit_log' //文件名称设置
},
search: true, //是否显示表格搜索
strictSearch: false, //是否全匹配搜索
showColumns: true, //是否显示所有的列选择显示的列
showRefresh: true, //是否显示刷新按钮
minimumCountColumns: 2, //最少允许的列数
clickToSelect: true, //是否启用点击选中行
uniqueId: "id", //每一行的唯一标识一般为主键列
showToggle: true, //是否显示详细视图和列表视图的切换按钮
cardView: false, //是否显示详细视图
detailView: false, //是否显示父子表
locale: 'zh-CN', //本地化
toolbar: "#toolbar", //指明自定义的toolbar
queryParamsType: 'limit',
//请求服务数据时所传参数
queryParams:
function (params) {
return {
limit: params.limit,
offset: params.offset,
search: params.search
}
},
columns: [{
title: '用户',
field: 'user_name',
formatter: function (value, row, index) {
return "<a target=\"_blank\" href=\"/admin/sql/users/" + row.user_id + "/change/\">" + value + "</a>"
}
},{
title: 'IP',
field: 'ip'
},{
title: '动作',
field: 'action'
}, {
title: '操作时间',
field: 'action_time'
}],
onLoadSuccess: function () {
},
onLoadError: function () {
alert("数据加载失败!请检查接口返回信息和错误日志!");
},
onSearch: function (e) {
//传搜索参数给服务器
queryParams(e)
}
});

}

//初始化数据
$(document).ready(function () {
grouplist();
});
</script>
{% endblock %}
6 changes: 6 additions & 0 deletions sql/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ def test_group(self):
r = self.client.get(f'/group/', data=data)
self.assertEqual(r.status_code, 200)

def test_audit(self):
"""测试audit页面"""
data = {}
r = self.client.get(f'/audit/', data=data)
self.assertEqual(r.status_code, 200)

def test_groupmgmt(self):
"""测试groupmgmt页面"""
data = {}
Expand Down
7 changes: 5 additions & 2 deletions sql/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sql.sql_optimize
from common import auth, config, workflow, dashboard, check
from sql import views, sql_workflow, sql_analyze, query, slowlog, instance, instance_account, db_diagnostic, \
resource_group, binlog, data_dictionary, archiver
resource_group, binlog, data_dictionary, archiver, audit_log
from sql.utils import tasks
from common.utils import ding_api

Expand Down Expand Up @@ -55,6 +55,7 @@
path('archive/', views.archive),
path('archive/<int:id>/', views.archive_detail, name='archive_detail'),
path('config/', views.config),
path('audit/', views.audit),

path('authenticate/', auth.authenticate_entry),
path('sqlworkflow_list/', sql_workflow.sql_workflow_list),
Expand Down Expand Up @@ -148,5 +149,7 @@
path('archive/once/', archiver.archive_once),
path('archive/log/', archiver.archive_log),

path('4admin/sync_ding_user/', ding_api.sync_ding_user)
path('4admin/sync_ding_user/', ding_api.sync_ding_user),

path('audit/log/', audit_log.audit_log),
]
6 changes: 6 additions & 0 deletions sql/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,9 @@ def dbaprinciples(request):
with open(file, 'r') as f:
md = f.read().replace('\n', '\\n')
return render(request, 'dbaprinciples.html', {'md': md})


@superuser_required
def audit(request):
"""登录审计日志页面"""
return render(request, 'audit.html')
11 changes: 11 additions & 0 deletions src/init_sql/v1.8.3.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- 增加登录审计日志
CREATE TABLE `audit_log` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` int(11) DEFAULT NULL COMMENT '用户id',
`user_name` varchar(255) DEFAULT NULL COMMENT '用户名称',
`ip` varchar(255) DEFAULT NULL COMMENT '登录ip',
`action` varchar(255) DEFAULT NULL COMMENT '动作',
`action_time` datetime(6) NOT NULL COMMENT '操作时间',
PRIMARY KEY (`id`),
KEY `idx_username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='登录审计日志表';

0 comments on commit f35e633

Please sign in to comment.