Files
aaPanel/class/userlogin.py
T
bt.cn d2a66661db Add panel SSL own certificate to fill in the entry
Fixed known bugs
Panel Feature Modification

Points to note after updating the panel:
1. The default port of the panel is changed to 7800
(Port 8888 has been flooded)
2. Panel entry error will prompt 404
3. After the panel is bound to the domain name,
if the domain access is not used,it will return 401
4. After the panel is set to authorize IP access,
other IP access panels will return 401
5. The website welcome page is changed to the nginx welcome page
6. Change the stop page to Nginx 404 page
2022-01-18 18:01:37 +08:00

335 lines
16 KiB
Python

#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# +-------------------------------------------------------------------
import public,os,sys,db,time,json,re
from BTPanel import session,cache,json_header
from flask import request,redirect,g
class userlogin:
def request_post(self,post):
if not hasattr(post, 'username') or not hasattr(post, 'password'):
return public.returnJson(False,'LOGIN_USER_EMPTY'),json_header
self.error_num(False)
if self.limit_address('?') < 1: return public.returnJson(False,'LOGIN_ERR_LIMIT'),json_header
post.username = post.username.strip()
public.chdck_salt()
sql = db.Sql()
user_list = sql.table('users').field('id,username,password,salt').select()
userInfo = None
for u_info in user_list:
if public.md5(u_info['username']) == post.username:
userInfo = u_info
if 'code' in session:
if session['code'] and not 'is_verify_password' in session:
if not hasattr(post, 'code'): return public.returnJson(False,'Verification code can not be empty!'),json_header
if not public.checkCode(post.code):
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_CODE',('****','****',public.GetClientIp()))
return public.returnJson(False,'CODE_ERR'),json_header
try:
if not userInfo['salt']:
public.chdck_salt()
userInfo = sql.table('users').where('id=?',(userInfo['id'],)).field('id,username,password,salt').find()
password = public.md5(post.password.strip() + userInfo['salt'])
if public.md5(userInfo['username']) != post.username or userInfo['password'] != password:
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp()))
num = self.limit_address('+')
return public.returnJson(False,'LOGIN_USER_ERR',(str(num),)),json_header
_key_file = "/www/server/panel/data/two_step_auth.txt"
#登陆告警
public.run_thread(public.login_send_body,("Userinfo",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT'))))
# public.login_send_body("Userinfo",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT')))
if hasattr(post,'vcode'):
if self.limit_address('?',v="vcode") < 1: return public.returnJson(False,'You have failed verification many times, forbidden for 10 minutes'),json_header
import pyotp
secret_key = public.readFile(_key_file)
if not secret_key:
return public.returnJson(False, "Did not find the key, please close Google verification on the command line and trun on again"),json_header
t = pyotp.TOTP(secret_key)
result = t.verify(post.vcode)
if not result:
if public.sync_date(): result = t.verify(post.vcode)
if not result:
num = self.limit_address('++',v="vcode")
return public.returnJson(False, 'Invalid Verification code. You have [{}] times left to try!'.format(num)), json_header
now = int(time.time())
public.writeFile("/www/server/panel/data/dont_vcode_ip.txt",json.dumps({"client_ip":public.GetClientIp(),"add_time":now}))
self.limit_address('--',v="vcode")
self.set_cdn_host(post)
return self._set_login_session(userInfo)
acc_client_ip = self.check_two_step_auth()
if not os.path.exists(_key_file) or acc_client_ip:
self.set_cdn_host(post)
return self._set_login_session(userInfo)
self.limit_address('-')
session['is_verify_password'] = True
return "1"
except Exception as ex:
stringEx = str(ex)
if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1:
public.ExecShell("rm -f /tmp/sess_*")
public.ExecShell("rm -f /www/wwwlogs/*log")
public.ServiceReload()
return public.returnJson(False,'USER_INODE_ERR'),json_header
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp()))
num = self.limit_address('+')
return public.returnJson(False,'LOGIN_USER_ERR',(str(num),)),json_header
def request_tmp(self,get):
try:
if not hasattr(get,'tmp_token'): return public.returnJson(False,'INIT_ARGS_ERR'),json_header
if len(get.tmp_token) == 48:
return self.request_temp(get)
if len(get.tmp_token) != 64: return public.returnJson(False,'INIT_ARGS_ERR'),json_header
if not re.match(r"^\w+$",get.tmp_token):return public.returnJson(False,'INIT_ARGS_ERR'),json_header
save_path = '/www/server/panel/config/api.json'
data = json.loads(public.ReadFile(save_path))
if not 'tmp_token' in data or not 'tmp_time' in data: return public.returnJson(False,'VERIFICATION_FAILED'),json_header
if (time.time() - data['tmp_time']) > 120: return public.returnJson(False,'EXPIRED_TOKEN'),json_header
if get.tmp_token != data['tmp_token']: return public.returnJson(False,'INIT_TOKEN_ERR'),json_header
userInfo = public.M('users').where("id=?",(1,)).field('id,username').find()
session['login'] = True
session['username'] = userInfo['username']
session['tmp_login'] = True
session['uid'] = userInfo['id']
public.WriteLog('TYPE_LOGIN','LOGIN_SUCCESS',(userInfo['username'],public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT'))))
self.limit_address('-')
cache.delete('panelNum')
cache.delete('dologin')
session['session_timeout'] = time.time() + public.get_session_timeout()
del(data['tmp_token'])
del(data['tmp_time'])
public.writeFile(save_path,json.dumps(data))
self.set_request_token()
self.login_token()
self.set_cdn_host(get)
return redirect('/')
except:
return public.returnJson(False,'Login failed,' + public.get_error_info()),json_header
def request_temp(self,get):
try:
if len(get.__dict__.keys()) > 2: return public.getMsg('INIT_ARGS_ERR')
if not hasattr(get,'tmp_token'): return public.getMsg('INIT_ARGS_ERR')
if len(get.tmp_token) != 48: return public.getMsg('INIT_ARGS_ERR')
if not re.match(r"^\w+$",get.tmp_token):return public.getMsg('INIT_ARGS_ERR')
skey = public.GetClientIp() + '_temp_login'
if not public.get_error_num(skey,10): return public.getMsg('AUTH_FAILED')
s_time = int(time.time())
data = public.M('temp_login').where('state=? and expire>?',(0,s_time)).field('id,token,salt,expire').find()
if not data:
public.set_error_num(skey)
return public.getMsg('VERIFICATION_FAILED')
if not isinstance(data,dict):
public.set_error_num(skey)
return public.getMsg('VERIFICATION_FAILED')
r_token = public.md5(get.tmp_token + data['salt'])
if r_token != data['token']:
public.set_error_num(skey)
return public.getMsg('VERIFICATION_FAILED')
public.set_error_num(skey,True)
userInfo = public.M('users').where("id=?",(1,)).field('id,username').find()
session['login'] = True
session['username'] = public.getMsg('TEMPORARY_ID',(data['id'],))
session['tmp_login'] = True
session['tmp_login_id'] = str(data['id'])
session['tmp_login_expire'] = time.time() + 3600
session['uid'] = data['id']
sess_path = 'data/session'
if not os.path.exists(sess_path):
os.makedirs(sess_path,384)
public.writeFile(sess_path + '/' + str(data['id']),'')
login_addr = public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT'))
public.WriteLog('TYPE_LOGIN','LOGIN_SUCCESS',(userInfo['username'],login_addr))
public.M('temp_login').where('id=?',(data['id'],)).update({"login_time":s_time,'state':1,'login_addr':login_addr})
self.limit_address('-')
cache.delete('panelNum')
cache.delete('dologin')
session['session_timeout'] = time.time() + public.get_session_timeout()
self.set_request_token()
self.login_token()
self.set_cdn_host(get)
public.login_send_body("Temporary authorization",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT')))
return redirect('/')
except:
return public.getMsg('LOGIN_FAIL')
def login_token(self):
import config
config.config().reload_session()
def request_get(self,get):
#if os.path.exists('/www/server/panel/install.pl'): raise redirect('/install');
if not 'title' in session: session['title'] = public.getMsg('NAME')
domain = public.readFile('data/domain.conf')
if domain:
if(public.GetHost().lower() != domain.strip().lower()):
return 404
# errorStr = public.ReadFile('./BTPanel/templates/' + public.GetConfigValue('template') + '/error2.html')
# try:
# errorStr = errorStr.format(public.getMsg('PAGE_ERR_TITLE'),public.getMsg('PAGE_ERR_DOMAIN_H1'),public.getMsg('PAGE_ERR_DOMAIN_P1'),public.getMsg('PAGE_ERR_DOMAIN_P2'),public.getMsg('PAGE_ERR_DOMAIN_P3'),public.getMsg('NAME'),public.getMsg('PAGE_ERR_HELP'))
# except IndexError:pass
# return errorStr
if os.path.exists('data/limitip.conf'):
iplist = public.readFile('data/limitip.conf')
if iplist:
iplist = iplist.strip()
if not public.GetClientIp() in iplist.split(','):
errorStr = public.ReadFile('./BTPanel/templates/' + public.GetConfigValue('template') + '/error2.html')
try:
errorStr = errorStr.format(public.getMsg('PAGE_ERR_TITLE'),public.getMsg('PAGE_ERR_IP_H1'),public.getMsg('PAGE_ERR_IP_P1',(public.GetClientIp(),)),public.getMsg('PAGE_ERR_IP_P2'),public.getMsg('PAGE_ERR_IP_P3'),public.getMsg('NAME'),public.getMsg('PAGE_ERR_HELP'))
except IndexError:pass
return errorStr
if 'login' in session:
if session['login'] == True:
return redirect('/')
if not 'code' in session:
session['code'] = False
self.error_num(False)
#生成request_token
def set_request_token(self):
session['request_token_head'] = public.GetRandomString(48)
def set_cdn_host(self,get):
try:
if not 'cdn_url' in get: return True
plugin_path = 'plugin/static_cdn'
if not os.path.exists(plugin_path): return True
cdn_url = public.get_cdn_url()
if not cdn_url or cdn_url == get.cdn_url: return True
public.set_cdn_url(get.cdn_url)
except:
return False
#防暴破
def error_num(self,s = True):
nKey = 'panelNum'
num = cache.get(nKey)
if not num:
cache.set(nKey,1)
num = 1
if s: cache.inc(nKey,1)
if num > 6: session['code'] = True
#IP限制
def limit_address(self,type,v=""):
import time
clientIp = public.GetClientIp()
numKey = 'limitIpNum_' + v + clientIp
limit = 6
outTime = 600
try:
#初始化
num1 = cache.get(numKey)
if not num1:
cache.set(numKey,1,outTime)
num1 = 1
#计数
if type == '+':
cache.inc(numKey,1)
self.error_num()
session['code'] = True
return limit - (num1+1)
#计数验证器
if type == '++':
cache.inc(numKey,1)
self.error_num()
session['code'] = False
return limit - (num1+1)
#清空
if type == '-':
cache.delete(numKey)
session['code'] = False
return 1
#清空验证器
if type == '--':
cache.delete(numKey)
session['code'] = False
return 1
return limit - num1
except:
return limit
# 登录成功设置session
def _set_login_session(self,userInfo):
try:
session['login'] = True
session['username'] = userInfo['username']
session['uid'] = userInfo['id']
session['login_user_agent'] = public.md5(request.headers.get('User-Agent',''))
public.WriteLog('TYPE_LOGIN','LOGIN_SUCCESS',(userInfo['username'],public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT'))))
self.limit_address('-')
cache.delete('panelNum')
cache.delete('dologin')
session['session_timeout'] = time.time() + public.get_session_timeout()
self.set_request_token()
self.login_token()
login_type = 'data/app_login.pl'
if os.path.exists(login_type):
os.remove(login_type)
return public.returnJson(True,'LOGIN_SUCCESS'),json_header
except Exception as ex:
stringEx = str(ex)
if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1:
public.ExecShell("rm -f /tmp/sess_*")
public.ExecShell("rm -f /www/wwwlogs/*log")
public.ServiceReload()
return public.returnJson(False,'USER_INODE_ERR'),json_header
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp()))
num = self.limit_address('+')
return public.returnJson(False,'LOGIN_USER_ERR',(str(num),)),json_header
# 检查是否需要进行二次验证
def check_two_step_auth(self):
dont_vcode_ip_info = public.readFile("/www/server/panel/data/dont_vcode_ip.txt")
acc_client_ip = False
if dont_vcode_ip_info:
dont_vcode_ip_info = json.loads(dont_vcode_ip_info)
ip = dont_vcode_ip_info["client_ip"] == public.GetClientIp()
now = int(time.time())
v_time = now - int(dont_vcode_ip_info["add_time"])
if ip and v_time < 86400:
acc_client_ip = True
return acc_client_ip
# 清理多余SESSION数据
def clear_session(self):
try:
session_file = '/dev/shm/session.db'
if not os.path.exists(session_file): return False
s_size = os.path.getsize(session_file)
if s_size < 1024 * 512: return False
if s_size > 1024 * 1024 * 10:
from BTPanel import sdb
if os.path.exists(session_file): os.remove(session_file)
sdb.create_all()
if not os.path.exists(session_file):
public.writeFile('/www/server/panel/data/reload.pl','True')
return False
return True
except:
return False