diff --git a/archer/settings.py b/archer/settings.py index 54840139..df3040d1 100644 --- a/archer/settings.py +++ b/archer/settings.py @@ -14,9 +14,6 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os -import ldap -# from django_auth_ldap.config import LDAPSearch, GroupOfNamesType -from django_auth_ldap.config import LDAPSearch, GroupOfUniqueNamesType import pymysql pymysql.install_as_MySQLdb() @@ -131,72 +128,90 @@ INCEPTION_REMOTE_BACKUP_USER='inception' INCEPTION_REMOTE_BACKUP_PASSWORD='inception' +# 账户登录失败锁定时间(秒) +LOCK_TIME_THRESHOLD = 300 +# 账户登录失败 几次 锁账户 +LOCK_CNT_THRESHOLD = 5 + # LDAP ENABLE_LDAP = False -# if use self signed certificate, Remove AUTH_LDAP_GLOBAL_OPTIONS annotations -#AUTH_LDAP_GLOBAL_OPTIONS={ -# ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER -#} -AUTH_LDAP_BIND_DN = "cn=ro,dc=xxx,dc=cn" -AUTH_LDAP_BIND_PASSWORD = "xxxxxx" -AUTH_LDAP_SERVER_URI = "ldap://auth.xxx.com" -AUTH_LDAP_BASEDN = "ou=users,dc=xxx,dc=cn" -AUTH_LDAP_USER_DN_TEMPLATE = "cn=%(user)s,ou=users,dc=xxx,dc=cn" -AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=groups,dc=xxx,dc=cn", - ldap.SCOPE_SUBTREE, "(objectClass=groupOfUniqueNames)" -) -AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType() -AUTH_LDAP_USER_ATTRLIST = ["cn", "sn", "mail"] -AUTH_LDAP_USER_ATTR_MAP = { - "username": "cn", - "display": "sn", - "email": "mail" -} +if ENABLE_LDAP: + import ldap + # from django_auth_ldap.config import LDAPSearch, GroupOfNamesType + from django_auth_ldap.config import LDAPSearch, GroupOfUniqueNamesType + # if use self signed certificate, Remove AUTH_LDAP_GLOBAL_OPTIONS annotations + #AUTH_LDAP_GLOBAL_OPTIONS={ + # ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER + #} + AUTH_LDAP_BIND_DN = "cn=ro,dc=xxx,dc=cn" + AUTH_LDAP_BIND_PASSWORD = "xxxxxx" + AUTH_LDAP_SERVER_URI = "ldap://auth.xxx.com" + AUTH_LDAP_BASEDN = "ou=users,dc=xxx,dc=cn" + AUTH_LDAP_USER_DN_TEMPLATE = "cn=%(user)s,ou=users,dc=xxx,dc=cn" + AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=groups,dc=xxx,dc=cn", + ldap.SCOPE_SUBTREE, "(objectClass=groupOfUniqueNames)" + ) + AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType() + AUTH_LDAP_USER_ATTRLIST = ["cn", "sn", "mail"] + AUTH_LDAP_USER_ATTR_MAP = { + "username": "cn", + "display": "sn", + "email": "mail" + } -# AUTH_LDAP_MIRROR_GROUPS = True # 直接把ldap的组复制到django一份,和AUTH_LDAP_FIND_GROUP_PERMS互斥.用户每次登录会根据ldap来更新数据库的组关系 -# AUTH_LDAP_FIND_GROUP_PERMS = True # django从ldap的组权限中获取权限,这种方式,django自身不创建组,每次请求都调用ldap -# AUTH_LDAP_CACHE_GROUPS = True # 如打开FIND_GROUP_PERMS后,此配置生效,对组关系进行缓存,不用每次请求都调用ldap -# AUTH_LDAP_GROUP_CACHE_TIMEOUT = 600 # 缓存时间 + # AUTH_LDAP_MIRROR_GROUPS = True # 直接把ldap的组复制到django一份,和AUTH_LDAP_FIND_GROUP_PERMS互斥.用户每次登录会根据ldap来更新数据库的组关系 + # AUTH_LDAP_FIND_GROUP_PERMS = True # django从ldap的组权限中获取权限,这种方式,django自身不创建组,每次请求都调用ldap + # AUTH_LDAP_CACHE_GROUPS = True # 如打开FIND_GROUP_PERMS后,此配置生效,对组关系进行缓存,不用每次请求都调用ldap + # AUTH_LDAP_GROUP_CACHE_TIMEOUT = 600 # 缓存时间 #开启以下配置注释,可以帮助调试ldap集成 -#LDAP_LOGS = '/tmp/ldap.log' -#stamdard_format = '[%(asctime)s][%(threadName)s:%(thread)d]' + \ -# '[task_id:%(name)s][%(filename)s:%(lineno)d] ' + \ -# '[%(levelname)s]- %(message)s' -#LOGGING = { -# 'version': 1, -# 'disable_existing_loggers': False, -# 'formatters': { -# 'standard': { # 详细 -# 'format': stamdard_format -# }, -# }, -# 'handlers': { -# 'default': { -# 'level': 'DEBUG', -# 'class': 'logging.handlers.RotatingFileHandler', -# 'filename': LDAP_LOGS, -# 'maxBytes': 1024 * 1024 * 100, # 5 MB -# 'backupCount': 5, -# 'formatter': 'standard', -# }, -# 'console': { -# 'level': 'DEBUG', -# 'class': 'logging.StreamHandler', -# } -# }, -# 'loggers': { -# '': { # default日志,存放于log中 -# 'handlers': ['default'], -# 'level': 'DEBUG', -# }, -# 'django_auth_ldap': { # django_auth_ldap模块相关日志打印到console -# 'handlers': ['default'], -# 'level': 'DEBUG', -# 'propagate': True, # 选择关闭继承,不然这个logger继承自默认,日志就会被记录2次了(''一次,自己一次) -# } -# } -#} +LDAP_LOGS = '/tmp/ldap.log' +DEFAULT_LOGS = '/tmp/default.log' +stamdard_format = '[%(asctime)s][%(threadName)s:%(thread)d]' + \ + '[task_id:%(name)s][%(filename)s:%(lineno)d] ' + \ + '[%(levelname)s]- %(message)s' +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'standard': { # 详细 + 'format': stamdard_format + }, + }, + 'handlers': { + 'default': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': DEFAULT_LOGS, + 'maxBytes': 1024 * 1024 * 100, # 5 MB + 'backupCount': 5, + 'formatter': 'standard', + }, + 'ldap': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': LDAP_LOGS, + 'maxBytes': 1024 * 1024 * 100, # 5 MB + 'backupCount': 5, + 'formatter': 'standard', + }, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + } + }, + 'loggers': { + 'default': { # default日志,存放于log中 + 'handlers': ['default'], + 'level': 'DEBUG', + }, + 'django_auth_ldap': { # django_auth_ldap模块相关日志打印到console + 'handlers': ['ldap'], + 'level': 'DEBUG', + 'propagate': True, # 选择关闭继承,不然这个logger继承自默认,日志就会被记录2次了(''一次,自己一次) + } + } +} #是否开启邮件提醒功能:发起SQL上线后会发送邮件提醒审核人审核,执行完毕会发送给DBA. on是开,off是关,配置为其他值均会被archer认为不开启邮件功能 MAIL_ON_OFF='on' diff --git a/sql/views_ajax.py b/sql/views_ajax.py index 336f9bf8..7497125a 100644 --- a/sql/views_ajax.py +++ b/sql/views_ajax.py @@ -12,41 +12,49 @@ from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required from django.contrib.auth.hashers import check_password -from django_auth_ldap.backend import LDAPBackend +if settings.ENABLE_LDAP: + from django_auth_ldap.backend import LDAPBackend from .dao import Dao from .const import Const from .inception import InceptionDao from .aes_decryptor import Prpcrypt from .models import users, master_config, workflow +from sql.sendmail import MailSender +import logging +logger = logging.getLogger('default') +mailSender = MailSender() dao = Dao() inceptionDao = InceptionDao() prpCryptor = Prpcrypt() login_failure_counter = {} #登录失败锁定计数器,给loginAuthenticate用的 sqlSHA1_cache = {} #存储SQL文本与SHA1值的对应关系,尽量减少与数据库的交互次数,提高效率。格式: {工单ID1:{SQL内容1:sqlSHA1值1, SQL内容2:sqlSHA1值2},} +def log_mail_record(login_failed_message): + mail_title = 'login inception' + logger.warning(login_failed_message) + mailSender.sendEmail(mail_title, login_failed_message, getattr(settings, 'MAIL_REVIEW_DBA_ADDR')) + #ajax接口,登录页面调用,用来验证用户名密码 @csrf_exempt def loginAuthenticate(username, password): """登录认证,包含一个登录失败计数器,5分钟内连续失败5次的账号,会被锁定5分钟""" - lockCntThreshold = 5 - lockTimeThreshold = 300 + lockCntThreshold = settings.LOCK_CNT_THRESHOLD + lockTimeThreshold = settings.LOCK_TIME_THRESHOLD #服务端二次验证参数 strUsername = username strPassword = password + if strUsername == "" or strPassword == "" or strUsername is None or strPassword is None: result = {'status':2, 'msg':'登录用户名或密码为空,请重新输入!', 'data':''} elif strUsername in login_failure_counter and login_failure_counter[strUsername]["cnt"] >= lockCntThreshold and (datetime.datetime.now() - login_failure_counter[strUsername]["last_failure_time"]).seconds <= lockTimeThreshold: + log_mail_record('user:{},login failed, account locking...'.format(strUsername)) result = {'status':3, 'msg':'登录失败超过5次,该账号已被锁定5分钟!', 'data':''} else: correct_users = users.objects.filter(username=strUsername) - if len(correct_users) == 0: - result = {'status':4, 'msg':'该用户不存在!', 'data':''} - elif not correct_users[0].is_active: - result = {'status': 5, 'msg': 'user is not active', 'data': ''} - elif len(correct_users) == 1 and check_password(strPassword, correct_users[0].password) == True: + if len(correct_users) == 1 and correct_users[0].is_active and check_password(strPassword, correct_users[0].password) == True: #调用了django内置函数check_password函数检测输入的密码是否与django默认的PBKDF2算法相匹配 if strUsername in login_failure_counter: #如果登录失败计数器中存在该用户名,则清除之 @@ -63,6 +71,7 @@ def loginAuthenticate(username, password): #上一次登录失败时间早于5分钟前,则重新计数。以达到超过5分钟自动解锁的目的。 login_failure_counter[strUsername]["cnt"] = 1 login_failure_counter[strUsername]["last_failure_time"] = datetime.datetime.now() + log_mail_record('user:{},login failed, fail count:{}'.format(strUsername,login_failure_counter[strUsername]["cnt"])) result = {'status':1, 'msg':'用户名或密码错误,请重新输入!', 'data':''} return result @@ -77,15 +86,21 @@ def authenticateEntry(request): strUsername = request.POST['username'] strPassword = request.POST['password'] + lockCntThreshold = settings.LOCK_CNT_THRESHOLD + lockTimeThreshold = settings.LOCK_TIME_THRESHOLD + if settings.ENABLE_LDAP: ldap = LDAPBackend() user = ldap.authenticate(username=strUsername, password=strPassword) - if user: - if user.is_active: - request.session['login_username'] = strUsername - result = {'status': 0, 'msg': 'ok', 'data': ''} - else: - result = {'status': 5, 'msg': 'user is not active', 'data': ''} + if strUsername in login_failure_counter and login_failure_counter[strUsername]["cnt"] >= lockCntThreshold and ( + datetime.datetime.now() - login_failure_counter[strUsername][ + "last_failure_time"]).seconds <= lockTimeThreshold: + log_mail_record('user:{},login failed, account locking...'.format(strUsername)) + result = {'status': 3, 'msg': '登录失败超过5次,该账号已被锁定5分钟!', 'data': ''} + return HttpResponse(json.dumps(result), content_type='application/json') + if user and user.is_active: + request.session['login_username'] = strUsername + result = {'status': 0, 'msg': 'ok', 'data': ''} return HttpResponse(json.dumps(result), content_type='application/json') result = loginAuthenticate(strUsername, strPassword)