teleport/server/www/teleport/webroot/app/base/configs.py

592 lines
21 KiB
Python

# -*- coding: utf-8 -*-
import configparser
import os
import json
from app.const import *
from .logger import log
from .utils import AttrDict, tp_convert_to_attr_dict, tp_make_dir
__all__ = ['get_cfg']
class BaseAppConfig(dict):
def __init__(self, **kwargs):
super().__init__(**kwargs)
import builtins
if '__app_cfg__' in builtins.__dict__:
raise RuntimeError('AppConfig instance already exists.')
self['_cfg_default'] = {}
self['_cfg_loaded'] = {}
self['_kvs'] = {'_': AttrDict()}
self['_cfg_file'] = ''
self._on_init()
def __getattr__(self, name):
_name = name.replace('-', '_')
if _name in self['_kvs']:
return self['_kvs'][_name]
else:
if _name in self['_kvs']['_']:
return self['_kvs']['_'][_name]
else:
return AttrDict()
def __setattr__(self, key, val):
x = key.split('::')
if 1 == len(x):
_sec = '_'
_key = x[0].replace('-', '_')
elif 2 == len(x):
_sec = x[0].replace('-', '_')
_key = x[1].replace('-', '_')
else:
raise RuntimeError('invalid name.')
if _sec not in self['_kvs']:
self['_kvs'][_sec] = {}
self['_kvs'][_sec][_key] = val
def _on_init(self):
raise RuntimeError('can not create instance for base class.')
def _on_get_save_info(self):
raise RuntimeError('can not create instance for base class.')
def _on_load(self, cfg_parser):
raise RuntimeError('can not create instance for base class.')
def reload(self):
self['_cfg_default'] = {}
self['_cfg_loaded'] = {}
self['_kvs'] = {'_': self['_kvs']['_']}
self._on_init()
return self.load(self['_cfg_file'])
def set_kv(self, key, val):
x = key.split('::')
if 1 == len(x):
_sec = '_'
_key = x[0].replace('-', '_')
elif 2 == len(x):
_sec = x[0].replace('-', '_')
_key = x[1].replace('-', '_')
else:
raise RuntimeError('invalid name.')
if _sec not in self['_cfg_loaded']:
self['_cfg_loaded'][_sec] = {}
self['_cfg_loaded'][_sec][_key] = val
self._update_kvs(_sec, _key, val)
def set_default(self, key, val, comment=None):
x = key.split('::')
if 1 == len(x):
_sec = '_'
_key = x[0].replace('-', '_')
elif 2 == len(x):
_sec = x[0].replace('-', '_')
_key = x[1].replace('-', '_')
else:
raise RuntimeError('invalid name.')
if _sec not in self['_cfg_default']:
self['_cfg_default'][_sec] = {}
if _key not in self['_cfg_default'][_sec]:
self['_cfg_default'][_sec][_key] = {}
self['_cfg_default'][_sec][_key]['value'] = val
self['_cfg_default'][_sec][_key]['comment'] = comment
else:
self['_cfg_default'][_sec][_key]['value'] = val
if comment is not None:
self['_cfg_default'][_sec][_key]['comment'] = comment
elif 'comment' not in self['_cfg_default'][_sec][_key]:
self['_cfg_default'][_sec][_key]['comment'] = None
self._update_kvs(_sec, _key, val)
def load(self, cfg_file):
if not os.path.exists(cfg_file):
log.e('configuration file does not exists: [{}]\n'.format(cfg_file))
return False
try:
_cfg = configparser.ConfigParser()
_cfg.read(cfg_file)
except:
log.e('can not load configuration file: [{}]\n'.format(cfg_file))
return False
if not self._on_load(_cfg):
return False
self['_cfg_file'] = cfg_file
return True
def save(self, cfg_file=None):
if cfg_file is None:
cfg_file = self['_cfg_file']
_save = self._on_get_save_info()
cnt = ['; codec: utf-8\n']
is_first_section = True
for sections in _save:
for sec_name in sections:
sec_name = sec_name.replace('-', '_')
if sec_name in self['_cfg_default'] or sec_name in self['_cfg_loaded']:
if not is_first_section:
cnt.append('\n')
cnt.append('[{}]'.format(sec_name))
is_first_section = False
for k in sections[sec_name]:
_k = k.replace('-', '_')
have_comment = False
if sec_name in self['_cfg_default'] and _k in self['_cfg_default'][sec_name] and 'comment' in self['_cfg_default'][sec_name][_k]:
comments = self['_cfg_default'][sec_name][_k]['comment']
if comments is not None:
comments = self['_cfg_default'][sec_name][_k]['comment'].split('\n')
cnt.append('')
have_comment = True
for comment in comments:
cnt.append('; {}'.format(comment))
if sec_name in self['_cfg_loaded'] and _k in self['_cfg_loaded'][sec_name]:
if not have_comment:
cnt.append('')
cnt.append('{}={}'.format(k, self['_cfg_loaded'][sec_name][_k]))
cnt.append('\n')
tmp_file = '{}.tmp'.format(cfg_file)
try:
with open(tmp_file, 'w', encoding='utf8') as f:
f.write('\n'.join(cnt))
if os.path.exists(cfg_file):
os.unlink(cfg_file)
os.rename(tmp_file, cfg_file)
return True
except Exception as e:
print(e.__str__())
return False
def _update_kvs(self, section, key, val):
if section not in self['_kvs']:
self['_kvs'][section] = AttrDict()
self['_kvs'][section][key] = val
def get_str(self, key, def_value=None):
x = key.split('::')
if 1 == len(x):
_sec = '_'
_key = x[0].replace('-', '_')
elif 2 == len(x):
_sec = x[0].replace('-', '_')
_key = x[1].replace('-', '_')
else:
return def_value, False
if _sec not in self['_kvs']:
return def_value, False
if _key not in self['_kvs'][_sec]:
return def_value, False
if self['_kvs'][_sec][_key] is None:
return def_value, False
return str(self['_kvs'][_sec][_key]), True
def get_int(self, key, def_value=-1):
x = key.split('::')
if 1 == len(x):
_sec = '_'
_key = x[0].replace('-', '_')
elif 2 == len(x):
_sec = x[0].replace('-', '_')
_key = x[1].replace('-', '_')
else:
return def_value, False
if _sec not in self['_kvs']:
return def_value, False
if _key not in self['_kvs'][_sec]:
return def_value, False
if self['_kvs'][_sec][_key] is None:
return def_value, False
try:
return int(self['_kvs'][_sec][_key]), True
except ValueError as e:
print(e.__str__())
return def_value, False
def get_bool(self, key, def_value=False):
x = key.split('::')
if 1 == len(x):
_sec = '_'
_key = x[0].replace('-', '_')
elif 2 == len(x):
_sec = x[0].replace('-', '_')
_key = x[1].replace('-', '_')
else:
return def_value, False
if _sec not in self['_kvs']:
return def_value, False
if _key not in self['_kvs'][_sec]:
return def_value, False
if self['_kvs'][_sec][_key] is None:
return def_value, False
tmp = str(self['_kvs'][_sec][_key]).lower()
if tmp in ['yes', 'true', '1']:
return True, True
elif tmp in ['no', 'false', '0']:
return False, True
else:
return def_value, False
class AppConfig(BaseAppConfig):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# 核心服务的配置信息,通过核心服务的接口获取
self.core = AttrDict()
self.core.detected = False
# 系统默认配置,通过数据库查询得到
self.sys = AttrDict()
self.sys.loaded = False
self.sys_smtp_password = '' # 密码单独处理,避免无意中传递给前端页面了
def _on_init(self):
self.set_default('common::ip', '0.0.0.0', 'ip=0.0.0.0')
self.set_default('common::port', 7190,
'port listen by web server, default to 7190.\n'
'DO NOT FORGET update common::web-server-rpc in base.ini if you modified this setting.\n'
'port=7190')
self.set_default('common::log-file', None,
'log file of web server, default to /var/log/teleport/tpweb.log\n'
'log-file=/var/log/teleport/tpweb.log'
)
self.set_default('common::log-level', 2,
'`log-level` can be 0 ~ 4, default to 2.\n'
'LOG_LEVEL_DEBUG 0 log every-thing.\n'
'LOG_LEVEL_VERBOSE 1 log every-thing but without debug message.\n'
'LOG_LEVEL_INFO 2 log information/warning/error message.\n'
'LOG_LEVEL_WARN 3 log warning and error message.\n'
'LOG_LEVEL_ERROR 4 log error message only.\n'
'log-level=2'
)
self.set_default('common::debug-mode', 0,
'0/1. default to 0.\n'
'in debug mode, `log-level` force to 0 and trace call stack when exception raised.\n'
'debug-mode=0'
)
self.set_default('common::core-server-rpc', 'http://127.0.0.1:52080/rpc',
'`core-server-rpc` is the rpc interface of base server.\n'
'DO NOT FORGET update this setting if you modified rpc::bind-port in core.ini.\n'
'core-server-rpc=http://127.0.0.1:52080/rpc'
)
self.set_default('database::type', 'sqlite',
'database in use, should be sqlite/mysql, default to sqlite.\n'
'type=sqlite'
)
self.set_default('database::sqlite-file', None,
'sqlite-file=/var/lib/teleport/data/ts_db.db'
)
self.set_default('database::mysql-host', '127.0.0.1', 'mysql-host=127.0.0.1')
self.set_default('database::mysql-port', 3306, 'mysql-port=3306')
self.set_default('database::mysql-db', 'teleport', 'mysql-db=teleport')
self.set_default('database::mysql-prefix', 'tp_', 'mysql-prefix=tp_')
self.set_default('database::mysql-user', 'teleport', 'mysql-user=teleport')
self.set_default('database::mysql-password', 'password', 'mysql-password=password')
def _on_get_save_info(self):
return [
{'common': ['ip', 'port', 'log-file', 'log-level', 'debug-mode', 'base-server-rpc']},
{'database': ['type', 'sqlite-file', 'mysql-host', 'mysql-port', 'mysql-db', 'mysql-prefix', 'mysql-user', 'mysql-password']}
]
def _on_load(self, cfg_parser):
if 'common' not in cfg_parser:
log.e('invalid config file, need `common` section.\n')
return False
if 'database' not in cfg_parser:
log.e('invalid config file, need `database` section.\n')
return False
_sec = cfg_parser['common']
_tmp_int = _sec.getint('log-level', -1)
if log.LOG_DEBUG <= _tmp_int <= log.LOG_ERROR:
self.set_kv('common::log-level', _tmp_int)
log.set_attribute(min_level=self.common.log_level)
_tmp_bool = _sec.getint('debug-mode', False)
self.set_kv('common::debug-mode', _tmp_bool)
if _tmp_bool:
log.set_attribute(min_level=log.LOG_DEBUG, trace_error=log.TRACE_ERROR_FULL)
_tmp_str = _sec.get('ip', None)
if _tmp_str is not None:
self.set_kv('common::ip', _tmp_str)
_tmp_int = _sec.getint('port', -1)
if -1 != _tmp_int:
self.set_kv('common::port', _tmp_int)
_tmp_str = _sec.get('log-file', None)
if _tmp_str is not None:
self.set_kv('common::log-file', _tmp_str)
_tmp_str = _sec.get('base-server-rpc', None)
if _tmp_str is not None:
self.set_kv('common::base-server-rpc', _tmp_str)
_sec = cfg_parser['database']
_tmp_str = _sec.get('type', None)
if _tmp_str is not None:
self.set_kv('database::type', _tmp_str)
_tmp_str = _sec.get('sqlite-file', None)
if _tmp_str is not None:
self.set_kv('database::sqlite-file', _tmp_str)
_tmp_str = _sec.get('mysql-host', None)
if _tmp_str is not None:
self.set_kv('database::mysql-host', _tmp_str)
_tmp_int = _sec.getint('mysql-port', -1)
if _tmp_int != -1:
self.set_kv('database::mysql-port', _tmp_int)
_tmp_str = _sec.get('mysql-db', None)
if _tmp_str is not None:
self.set_kv('database::mysql-db', _tmp_str)
_tmp_str = _sec.get('mysql-prefix', None)
if _tmp_str is not None:
self.set_kv('database::mysql-prefix', _tmp_str)
_tmp_str = _sec.get('mysql-user', None)
if _tmp_str is not None:
self.set_kv('database::mysql-user', _tmp_str)
_tmp_str = _sec.get('mysql-password', None)
if _tmp_str is not None:
self.set_kv('database::mysql-password', _tmp_str)
_log_file, ok = self.get_str('common::log-file')
if ok and _log_file:
self.log_path = os.path.abspath(os.path.dirname(_log_file))
else:
_log_file = os.path.join(self.log_path, 'tpweb.log')
self.set_default('common::log-file', _log_file)
if not os.path.exists(self.log_path):
tp_make_dir(self.log_path)
if not os.path.exists(self.log_path):
log.e('Can not create log path:{}\n'.format(self.log_path))
return False
log.set_attribute(filename=_log_file)
return True
def update_core(self, conf_data):
self.core = AttrDict()
self.core.detected = False
if conf_data is None:
log.w('core server config info is empty.\n')
return True
try:
self.core.ssh = AttrDict()
self.core.ssh.enable = False
self.core.ssh.port = 52189
if 'ssh' in conf_data:
self.core.ssh.enable = conf_data['ssh']['enable']
self.core.ssh.port = conf_data['ssh']['port']
self.core.rdp = AttrDict()
self.core.rdp.enable = False
self.core.rdp.port = 52089
if 'rdp' in conf_data:
self.core.rdp.enable = conf_data['rdp']['enable']
self.core.rdp.port = conf_data['rdp']['port']
self.core.telnet = AttrDict()
self.core.telnet.enable = False
self.core.telnet.port = 52389
if 'telnet' in conf_data:
self.core.telnet.enable = conf_data['telnet']['enable']
self.core.telnet.port = conf_data['telnet']['port']
if 'replay-path' in conf_data:
self.core.replay_path = conf_data['replay-path']
if 'web-server-rpc' in conf_data:
self.core.web_server_rpc = conf_data['web-server-rpc']
if 'version' in conf_data:
self.core.version = conf_data['version']
self.core.detected = True
except IndexError:
log.e('invalid core server config.\n')
return False
return True
def update_sys(self, conf_data):
self.sys = AttrDict()
self.sys.loaded = False
if conf_data is None:
log.w('system default config info is empty.\n')
return True
# =====================================
# 密码策略相关
# =====================================
# conf_data['password'] = '{"find":false,"strong":true}'
try:
_password = json.loads(conf_data['password'])
except:
log.w('invalid password config, use default.\n')
_password = {}
self.sys.password = tp_convert_to_attr_dict(_password)
if not self.sys.password.is_exists('allow_reset'):
self.sys.password.allow_reset = True
if not self.sys.password.is_exists('force_strong'):
self.sys.password.force_strong = True
if not self.sys.password.is_exists('timeout'):
self.sys.password.timeout = 0
# =====================================
# 登录相关
# =====================================
try:
_login = json.loads(conf_data['login'])
except:
log.w('invalid login config, use default.\n')
_login = {}
self.sys.login = tp_convert_to_attr_dict(_login)
if not self.sys.login.is_exists('session_timeout'):
self.sys.login.session_timeout = 30
if not self.sys.login.is_exists('retry'):
self.sys.login.retry = 0
if not self.sys.login.is_exists('lock_timeout'):
self.sys.login.lock_timeout = 30 # 30 min
if not self.sys.login.is_exists('auth'):
self.sys.login.auth = TP_LOGIN_AUTH_USERNAME_PASSWORD | TP_LOGIN_AUTH_USERNAME_PASSWORD_CAPTCHA | TP_LOGIN_AUTH_USERNAME_OATH | TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH
# print('==login==', json.dumps(self.sys.login, separators=(',', ':')))
# =====================================
# SMTP相关
# =====================================
self.sys_smtp_password = ''
try:
_smtp = json.loads(conf_data['smtp'])
except:
log.w('invalid smtp config, use default.\n')
_smtp = {}
self.sys.smtp = tp_convert_to_attr_dict(_smtp)
if not self.sys.smtp.is_exists('server'):
self.sys.smtp.server = ''
if not self.sys.smtp.is_exists('port'):
self.sys.smtp.port = 25
if not self.sys.smtp.is_exists('ssl'):
self.sys.smtp.ssl = False
if not self.sys.smtp.is_exists('sender'):
self.sys.smtp.sender = ''
if self.sys.smtp.is_exists('password'):
self.sys_smtp_password = self.sys.smtp.password
self.sys.smtp.password = '********'
# del self.sys.smtp.password
# =====================================
# 存储相关
# =====================================
try:
_storage = json.loads(conf_data['storage'])
except:
log.w('invalid storage config, use default.\n')
_storage = {}
self.sys.storage = tp_convert_to_attr_dict(_storage)
if not self.sys.storage.is_exists('keep_log'):
self.sys.storage.keep_log = 0
if not self.sys.storage.is_exists('keep_record'):
self.sys.storage.keep_record = 0
self.sys.loaded = True
# except IndexError:
# log.e('invalid system default config.\n')
# return False
return True
def get_cfg():
"""
:rtype: app.base.configs.AppConfig
"""
import builtins
if '__app_cfg__' not in builtins.__dict__:
builtins.__dict__['__app_cfg__'] = AppConfig()
return builtins.__dict__['__app_cfg__']
# def app_cfg():
# import builtins
# if '__web_config__' not in builtins.__dict__:
# builtins.__dict__['__web_config__'] = ConfigFile()
# return builtins.__dict__['__web_config__']
# if __name__ == '__main__':
# cfg = AppConfig()
# cfg.set_default('common::log-file', 'E:/test/log/web.log')
# cfg.load('E:/test/config/web.ini')
# cfg.aaa = 'this is aaa'
# cfg.bbb = 123
# cfg.ccc = False
#
# print('----usage--------------------')
# print(cfg.common.port)
# print(cfg.get_str('aaa'))
# print(cfg.get_str('bbb'))
# print(cfg.get_str('ccc'))
# print('----usage--------------------')
# print(cfg.get_int('aaa'))
# print(cfg.get_int('bbb'))
# print(cfg.get_int('ccc'))
# print('----usage--------------------')
# print(cfg.get_bool('aaa'))
# print(cfg.get_bool('bbb'))
# print(cfg.get_bool('ccc'))
# print('----usage--------------------')
# print(cfg.common)
# print('----usage--------------------')
# print(cfg.aaa)
# print(cfg.bbb)
# print(cfg.ccc)
#
# cfg.save('E:/test/config/web-new.ini')
# cfg.save()