teleport/server/www/teleport/webroot/app/controller/system.py

780 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- coding: utf-8 -*-
import datetime
import hashlib
import json
import shutil
import time
import app.model.system as system_model
import tornado.gen
from app.app_ver import TP_SERVER_VER
from app.base import mail
from app.base.configs import tp_cfg
from app.base.controller import TPBaseHandler, TPBaseJsonHandler
from app.base.logger import *
from app.const import *
from app.base.db import get_db
from app.model import syslog
from app.model import record
from app.model import ops
from app.model import audit
from app.model import user
from app.base.core_server import core_service_async_post_http
from app.base.session import tp_session
from app.logic.auth.ldap import Ldap
class DoGetTimeHandler(TPBaseJsonHandler):
def post(self):
time_now = int(datetime.datetime.utcnow().timestamp())
self.write_json(TPE_OK, data=time_now)
class ConfigHandler(TPBaseHandler):
@tornado.gen.coroutine
def get(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_CONFIG)
if ret != TPE_OK:
return
cfg = tp_cfg()
# core_detected = False
req = {'method': 'get_config', 'param': []}
_yr = core_service_async_post_http(req)
code, ret_data = yield _yr
if code != TPE_OK:
cfg.update_core(None)
else:
cfg.update_core(ret_data)
if not tp_cfg().core.detected:
total_size = 0
free_size = 0
else:
total_size, _, free_size = shutil.disk_usage(tp_cfg().core.replay_path)
_db = get_db()
db = {'type': _db.db_type}
if _db.db_type == _db.DB_TYPE_SQLITE:
db['sqlite_file'] = _db.sqlite_file
elif _db.db_type == _db.DB_TYPE_MYSQL:
db['mysql_host'] = _db.mysql_host
db['mysql_port'] = _db.mysql_port
db['mysql_db'] = _db.mysql_db
db['mysql_user'] = _db.mysql_user
param = {
'total_size': total_size,
'free_size': free_size,
'core_cfg': tp_cfg().core,
'sys_cfg': tp_cfg().sys,
'web_cfg': {
'version': TP_SERVER_VER,
'core_server_rpc': tp_cfg().common.core_server_rpc,
'db': db
}
}
self.render('system/config.mako', page_param=json.dumps(param))
class RoleHandler(TPBaseHandler):
def get(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_ROLE)
if ret != TPE_OK:
return
self.render('system/role.mako')
class DoExportDBHandler(TPBaseHandler):
def get(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_CONFIG)
if ret != TPE_OK:
return
sql, err = get_db().export_to_sql()
self.set_header('Content-Type', 'application/sql')
self.set_header('Content-Disposition', 'attachment; filename="teleport-db-export-{}.sql"'.format(time.strftime('%Y%m%d-%H%M%S')))
self.write(sql)
self.finish()
class DoRoleUpdateHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_ROLE)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
role_id = int(args['role_id'])
role_name = args['role_name']
privilege = int(args['privilege'])
except:
log.e('\n')
return self.write_json(TPE_PARAM)
if role_id == 0:
err, role_id = system_model.add_role(self, role_name, privilege)
else:
if role_id == 1:
return self.write_json(TPE_FAILED, '禁止修改系统管理员角色!')
err = system_model.update_role(self, role_id, role_name, privilege)
return self.write_json(err, data=role_id)
class DoRoleRemoveHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_ROLE)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
role_id = int(args['role_id'])
except:
log.e('\n')
return self.write_json(TPE_PARAM)
if role_id == 1:
return self.write_json(TPE_FAILED, '禁止删除系统管理员角色!')
err = system_model.remove_role(self, role_id)
return self.write_json(err)
class SysLogHandler(TPBaseHandler):
def get(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_LOG)
if ret != TPE_OK:
return
self.render('system/syslog.mako')
class DoGetLogsHandler(TPBaseJsonHandler):
def post(self):
# return self.write_json(0, data=[])
filter = dict()
order = dict()
order['name'] = 'log_time'
order['asc'] = False
limit = dict()
limit['page_index'] = 0
limit['per_page'] = 25
args = self.get_argument('args', None)
if args is not None:
args = json.loads(args)
tmp = list()
_filter = args['filter']
if _filter is not None:
for i in _filter:
if i == 'user_name':
_x = _filter[i].strip()
if _x == '全部':
tmp.append(i)
if i == 'search':
_x = _filter[i].strip()
if len(_x) == 0:
tmp.append(i)
continue
for i in tmp:
del _filter[i]
filter.update(_filter)
_limit = args['limit']
if _limit['page_index'] < 0:
_limit['page_index'] = 0
if _limit['per_page'] < 10:
_limit['per_page'] = 10
if _limit['per_page'] > 100:
_limit['per_page'] = 100
limit.update(_limit)
_order = args['order']
if _order is not None:
order['name'] = _order['k']
order['asc'] = _order['v']
err, total, record_list = syslog.get_logs(filter, order, _limit)
if err != TPE_OK:
return self.write_json(err)
ret = dict()
ret['page_index'] = limit['page_index']
ret['total'] = total
ret['data'] = record_list
return self.write_json(0, data=ret)
class DoSaveCfgHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_CONFIG)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
processed = False
if 'smtp' in args:
processed = True
_cfg = args['smtp']
_server = _cfg['server']
_port = _cfg['port']
_ssl = _cfg['ssl']
_sender = _cfg['sender']
_password = _cfg['password']
# TODO: encrypt the password before save by core-service.
# TODO: if not send password, use pre-saved password.
err = system_model.save_config(self, '更新SMTP设置', 'smtp', _cfg)
if err == TPE_OK:
# 同时更新内存缓存
tp_cfg().sys.smtp.server = _server
tp_cfg().sys.smtp.port = _port
tp_cfg().sys.smtp.ssl = _ssl
tp_cfg().sys.smtp.sender = _sender
# 特殊处理,防止前端拿到密码
tp_cfg().sys_smtp_password = _password
else:
return self.write_json(err)
# 增加 url-protocol 的配置
if 'global' in args:
processed = True
_cfg = args['global']
_url_proto = _cfg['url_proto']
err = system_model.save_config(self, '更新全局设置', 'global', _cfg)
if err == TPE_OK:
tp_cfg().sys.glob.url_proto = _url_proto
else:
return self.write_json(err)
if 'password' in args:
processed = True
_cfg = args['password']
_allow_reset = _cfg['allow_reset']
_force_strong = _cfg['force_strong']
_timeout = _cfg['timeout']
err = system_model.save_config(self, '更新密码策略设置', 'password', _cfg)
if err == TPE_OK:
tp_cfg().sys.password.allow_reset = _allow_reset
tp_cfg().sys.password.force_strong = _force_strong
tp_cfg().sys.password.timeout = _timeout
else:
return self.write_json(err)
if 'login' in args:
processed = True
_cfg = args['login']
_session_timeout = _cfg['session_timeout']
_retry = _cfg['retry']
_lock_timeout = _cfg['lock_timeout']
_auth = _cfg['auth']
err = system_model.save_config(self, '更新登录策略设置', 'login', _cfg)
if err == TPE_OK:
tp_cfg().sys.login.session_timeout = _session_timeout
tp_cfg().sys.login.retry = _retry
tp_cfg().sys.login.lock_timeout = _lock_timeout
tp_cfg().sys.login.auth = _auth
tp_session().update_default_expire()
else:
return self.write_json(err)
if 'session' in args:
processed = True
_cfg = args['session']
_noop_timeout = _cfg['noop_timeout']
_flag_record = _cfg['flag_record']
_flag_rdp = _cfg['flag_rdp']
_flag_ssh = _cfg['flag_ssh']
err = system_model.save_config(self, '更新连接控制设置', 'session', _cfg)
if err == TPE_OK:
try:
req = {'method': 'set_config', 'param': {'noop_timeout': _noop_timeout}}
_yr = core_service_async_post_http(req)
code, ret_data = yield _yr
if code != TPE_OK:
log.e('can not set runtime-config to core-server.\n')
return self.write_json(code)
except:
pass
tp_cfg().sys.session.noop_timeout = _noop_timeout
tp_cfg().sys.session.flag_record = _flag_record
tp_cfg().sys.session.flag_rdp = _flag_rdp
tp_cfg().sys.session.flag_ssh = _flag_ssh
else:
return self.write_json(err)
if 'storage' in args:
processed = True
_cfg = args['storage']
_keep_log = _cfg['keep_log']
_keep_record = _cfg['keep_record']
_cleanup_hour = _cfg['cleanup_hour']
_cleanup_minute = _cfg['cleanup_minute']
if not ((30 <= _keep_log <= 365) or _keep_log == 0):
return self.write_json(TPE_PARAM, '系统日志保留时间超出范围!')
if not ((30 <= _keep_record <= 365) or _keep_record == 0):
return self.write_json(TPE_PARAM, '会话录像保留时间超出范围!')
err = system_model.save_config(self, '更新存储策略设置', 'storage', _cfg)
if err == TPE_OK:
tp_cfg().sys.storage.keep_log = _keep_log
tp_cfg().sys.storage.keep_record = _keep_record
tp_cfg().sys.storage.cleanup_hour = _cleanup_hour
tp_cfg().sys.storage.cleanup_minute = _cleanup_minute
else:
return self.write_json(err)
if 'ldap' in args:
processed = True
_cfg = args['ldap']
# _password = _cfg['password']
_server = _cfg['server']
_port = _cfg['port']
_domain = _cfg['domain']
_admin = _cfg['admin']
_base_dn = _cfg['base_dn']
_filter = _cfg['filter']
_attr_username = _cfg['attr_username']
_attr_surname = _cfg['attr_surname']
_attr_email = _cfg['attr_email']
if len(_cfg['password']) == 0:
_cfg['password'] = tp_cfg().sys_ldap_password
if len(_cfg['password']) == 0:
return self.write_json(TPE_PARAM, '请设置LDAP管理员密码')
# TODO: encrypt the password before save by core-service.
err = system_model.save_config(self, '更新LDAP设置', 'ldap', _cfg)
if err == TPE_OK:
tp_cfg().sys.ldap.server = _server
tp_cfg().sys.ldap.port = _port
tp_cfg().sys.ldap.domain = _domain
tp_cfg().sys.ldap.admin = _admin
tp_cfg().sys.ldap.base_dn = _base_dn
tp_cfg().sys.ldap.filter = _filter
tp_cfg().sys.ldap.attr_username = _attr_username
tp_cfg().sys.ldap.attr_surname = _attr_surname
tp_cfg().sys.ldap.attr_email = _attr_email
# 特殊处理,防止前端拿到密码
tp_cfg().sys_ldap_password = _cfg['password']
else:
return self.write_json(err)
if not processed:
return self.write_json(TPE_PARAM)
return self.write_json(TPE_OK)
except:
log.e('\n')
self.write_json(TPE_FAILED)
class DoSendTestMailHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_CONFIG)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
_server = args['server']
_port = int(args['port'])
_ssl = args['ssl']
_sender = args['sender']
_password = args['password']
_recipient = args['recipient']
except:
return self.write_json(TPE_PARAM)
code, msg = yield mail.tp_send_mail(
_recipient,
'您好!\n\n这是一封测试邮件,仅用于验证系统的邮件发送模块工作是否正常。\n\n请忽略本邮件。',
subject='测试邮件',
sender='Teleport Server <{}>'.format(_sender),
server=_server,
port=_port,
use_ssl=_ssl,
username=_sender,
password=_password
)
self.write_json(code, message=msg)
class DoLdapListUserAttrHandler(TPBaseJsonHandler):
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_USER_CREATE)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
cfg = args['ldap']
cfg['port'] = int(cfg['port'])
if len(cfg['password']) == 0:
if len(tp_cfg().sys_ldap_password) == 0:
return self.write_json(TPE_PARAM, message='需要设置LDAP管理员密码')
else:
cfg['password'] = tp_cfg().sys_ldap_password
except:
return self.write_json(TPE_PARAM)
try:
ldap = Ldap(cfg['server'], cfg['port'], cfg['base_dn'])
ret, data, err_msg = ldap.get_all_attr(cfg['admin'], cfg['password'], cfg['filter'])
if ret != TPE_OK:
return self.write_json(ret, message=err_msg)
else:
return self.write_json(ret, data=data)
except:
log.e('')
return self.write_json(TPE_PARAM)
class DoLdapConfigTestHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_USER_CREATE)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
cfg = args['ldap']
cfg['port'] = int(cfg['port'])
if len(cfg['password']) == 0:
if len(tp_cfg().sys_ldap_password) == 0:
return self.write_json(TPE_PARAM, message='需要设置LDAP管理员密码')
else:
cfg['password'] = tp_cfg().sys_ldap_password
except:
return self.write_json(TPE_PARAM)
try:
ldap = Ldap(cfg['server'], cfg['port'], cfg['base_dn'])
ret, data, err_msg = ldap.list_users(
cfg['admin'], cfg['password'], cfg['filter'],
cfg['attr_username'], cfg['attr_surname'], cfg['attr_email'],
size_limit=10
)
if ret != TPE_OK:
return self.write_json(ret, message=err_msg)
else:
return self.write_json(ret, data=data)
except:
log.e('')
return self.write_json(TPE_PARAM)
class DoLdapGetUsersHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_USER_CREATE)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
if len(tp_cfg().sys_ldap_password) == 0:
return self.write_json(TPE_PARAM, message='LDAP未能正确配置需要管理员密码')
else:
_password = tp_cfg().sys_ldap_password
_server = tp_cfg().sys.ldap.server
_port = tp_cfg().sys.ldap.port
_admin = tp_cfg().sys.ldap.admin
_base_dn = tp_cfg().sys.ldap.base_dn
_filter = tp_cfg().sys.ldap.filter
_attr_username = tp_cfg().sys.ldap.attr_username
_attr_surname = tp_cfg().sys.ldap.attr_surname
_attr_email = tp_cfg().sys.ldap.attr_email
except:
return self.write_json(TPE_PARAM)
try:
ldap = Ldap(_server, _port, _base_dn)
ret, data, err_msg = ldap.list_users(_admin, _password, _filter, _attr_username, _attr_surname, _attr_email)
if ret != TPE_OK:
return self.write_json(ret, message=err_msg)
exists_users = user.get_users_by_type(TP_USER_TYPE_LDAP)
bound_users = []
if exists_users is not None:
for u in exists_users:
h = hashlib.sha1()
h.update(u['ldap_dn'].encode())
bound_users.append(h.hexdigest())
ret_data = []
for u in data:
h = hashlib.sha1()
h.update(u.encode())
_id = h.hexdigest()
if _id in bound_users:
continue
_user = data[u]
_user['id'] = h.hexdigest()
ret_data.append(_user)
return self.write_json(ret, data=ret_data)
except:
log.e('')
return self.write_json(TPE_PARAM)
class DoLdapImportHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_USER_CREATE)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
dn_hash_list = args['ldap_users']
if len(tp_cfg().sys_ldap_password) == 0:
return self.write_json(TPE_PARAM, message='LDAP未能正确配置需要管理员密码')
else:
_password = tp_cfg().sys_ldap_password
_server = tp_cfg().sys.ldap.server
_port = tp_cfg().sys.ldap.port
_admin = tp_cfg().sys.ldap.admin
_base_dn = tp_cfg().sys.ldap.base_dn
_filter = tp_cfg().sys.ldap.filter
_attr_username = tp_cfg().sys.ldap.attr_username
_attr_surname = tp_cfg().sys.ldap.attr_surname
_attr_email = tp_cfg().sys.ldap.attr_email
except:
return self.write_json(TPE_PARAM)
try:
ldap = Ldap(_server, _port, _base_dn)
ret, data, err_msg = ldap.list_users(_admin, _password, _filter, _attr_username, _attr_surname, _attr_email)
if ret != TPE_OK:
return self.write_json(ret, message=err_msg)
need_import = []
for u in data:
h = hashlib.sha1()
h.update(u.encode())
dn_hash = h.hexdigest()
for x in dn_hash_list:
if x == dn_hash:
_user = data[u]
_user['dn'] = u
need_import.append(_user)
break
if len(need_import) == 0:
return self.write_json(ret, message='没有可以导入的LDAP用户')
return self._do_import(need_import)
except:
log.e('')
return self.write_json(TPE_PARAM)
def _do_import(self, users):
success = list()
failed = list()
try:
user_list = []
for _u in users:
if 'surname' not in _u:
_u['surname'] = _u['username']
if 'email' not in _u:
_u['email'] = ''
u = dict()
u['_line'] = 0
u['_id'] = 0
u['type'] = TP_USER_TYPE_LDAP
u['ldap_dn'] = _u['dn']
u['username'] = '{}@{}'.format(_u['username'], tp_cfg().sys.ldap.domain)
u['surname'] = _u['surname']
u['email'] = _u['email']
u['mobile'] = ''
u['qq'] = ''
u['wechat'] = ''
u['desc'] = ''
u['password'] = ''
# fix
if len(u['surname']) == 0:
u['surname'] = u['username']
u['username'] = u['username'].lower()
user_list.append(u)
print(user_list)
user.create_users(self, user_list, success, failed)
# 对于创建成功的用户,发送密码邮件函
sys_smtp_password = tp_cfg().sys_smtp_password
if len(sys_smtp_password) > 0:
web_url = '{}://{}'.format(self.request.protocol, self.request.host)
for u in user_list:
if u['_id'] == 0 or len(u['email']) == 0:
continue
mail_body = '{surname} 您好!\n\n已为您创建teleport系统用户账号现在可以使用以下信息登录teleport系统\n\n' \
'登录用户名:{username}\n' \
'密码:您正在使用的域登录密码\n' \
'地址:{web_url}\n\n\n\n' \
'[本邮件由teleport系统自动发出请勿回复]' \
'\n\n' \
''.format(surname=u['surname'], username=u['username'], web_url=web_url)
err, msg = yield mail.tp_send_mail(u['email'], mail_body, subject='用户密码函')
if err != TPE_OK:
failed.append({'line': u['_line'], 'error': '无法发送密码函到邮箱 {},错误:{}'.format(u['email'], msg)})
# 统计结果
total_success = 0
total_failed = 0
for u in user_list:
if u['_id'] == 0:
total_failed += 1
else:
total_success += 1
# 生成最终结果信息
if len(failed) == 0:
# ret['code'] = TPE_OK
# ret['message'] = '共导入 {} 个用户账号!'.format(total_success)
return self.write_json(TPE_OK, message='共导入 {} 个用户账号!'.format(total_success))
else:
# ret['code'] = TPE_FAILED
msg = ''
if total_success > 0:
msg = '{} 个用户账号导入成功,'.format(total_success)
if total_failed > 0:
msg += '{} 个用户账号未能导入!'.format(total_failed)
# ret['data'] = failed
return self.write_json(TPE_FAILED, data=failed, message=msg)
except:
log.e('got exception when import LDAP user.\n')
# ret['code'] = TPE_FAILED
msg = ''
if len(success) > 0:
msg += '{} 个用户账号导入后发生异常!'.format(len(success))
else:
msg = '发生异常!'
# ret['data'] = failed
return self.write_json(TPE_FAILED, data=failed, message=msg)
class DoCleanupStorageHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_SYS_CONFIG)
if ret != TPE_OK:
return
code, msg = yield record.cleanup_storage(self)
self.write_json(code, data=msg)
class DoRebuildOpsAuzMapHandler(TPBaseJsonHandler):
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_OPS_AUZ)
if ret != TPE_OK:
return
err = audit.build_auz_map()
self.write_json(err)
class DoRebuildAuditAuzMapHandler(TPBaseJsonHandler):
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_AUDIT_AUZ)
if ret != TPE_OK:
return
err = ops.build_auz_map()
self.write_json(err)