diff --git a/apps/common/local.py b/apps/common/local.py index e075d29ff..d0299b43e 100644 --- a/apps/common/local.py +++ b/apps/common/local.py @@ -1,40 +1,7 @@ -# -*- coding: utf-8 -*- -# -from jumpserver.const import DYNAMIC -from werkzeug.local import Local, LocalProxy +from werkzeug.local import Local thread_local = Local() def _find(attr): return getattr(thread_local, attr, None) - - -class _Settings: - pass - - -def get_dynamic_cfg_from_thread_local(): - KEY = 'dynamic_config' - - try: - cfg = getattr(thread_local, KEY) - except AttributeError: - cfg = _Settings() - setattr(thread_local, KEY, cfg) - - return cfg - - -class DynamicDefaultLocalProxy(LocalProxy): - def __getattr__(self, item): - try: - value = super().__getattr__(item) - except AttributeError: - value = getattr(DYNAMIC, item)() - setattr(self, item, value) - - return value - - -LOCAL_DYNAMIC_SETTINGS = DynamicDefaultLocalProxy(get_dynamic_cfg_from_thread_local) diff --git a/apps/common/signals_handlers.py b/apps/common/signals_handlers.py index d1cf1c7fa..0cb3a04ad 100644 --- a/apps/common/signals_handlers.py +++ b/apps/common/signals_handlers.py @@ -5,16 +5,12 @@ import os import logging from collections import defaultdict from django.conf import settings -from django.dispatch import receiver from django.core.signals import request_finished from django.db import connection -from django.conf import LazySettings -from django.db.utils import ProgrammingError, OperationalError from jumpserver.utils import get_current_request from .local import thread_local -from .signals import django_ready pattern = re.compile(r'FROM `(\w+)`') logger = logging.getLogger("jumpserver.common") @@ -74,17 +70,3 @@ if settings.DEBUG and DEBUG_DB: request_finished.connect(on_request_finished_logging_db_query) else: request_finished.connect(on_request_finished_release_local) - - -@receiver(django_ready) -def monkey_patch_settings(sender, **kwargs): - def monkey_patch_getattr(self, name): - val = getattr(self._wrapped, name) - if callable(val): - val = val() - return val - - try: - LazySettings.__getattr__ = monkey_patch_getattr - except (ProgrammingError, OperationalError): - pass diff --git a/apps/common/utils/connection.py b/apps/common/utils/connection.py new file mode 100644 index 000000000..9bdf39628 --- /dev/null +++ b/apps/common/utils/connection.py @@ -0,0 +1,27 @@ +import redis +from django.conf import settings + + +def get_redis_client(db): + rc = redis.StrictRedis( + host=settings.REDIS_HOST, + port=settings.REDIS_PORT, + password=settings.REDIS_PASSWORD, + db=db + ) + return rc + + +class RedisPubSub: + def __init__(self, ch, db=10): + self.ch = ch + self.redis = get_redis_client(db) + + def subscribe(self): + ps = self.redis.pubsub() + ps.subscribe(self.ch) + return ps + + def publish(self, data): + self.redis.publish(self.ch, data) + return True diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 602916cb6..9989103f8 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -426,98 +426,6 @@ class Config(dict): return self.get(item) -class DynamicConfig: - def __init__(self, static_config): - self.static_config = static_config - self.db_setting = None - - def __getitem__(self, item): - return self.dynamic(item) - - def __getattr__(self, item): - return self.dynamic(item) - - def dynamic(self, item): - return lambda: self.get(item) - - def LOGIN_URL(self): - return self.get('LOGIN_URL') - - def AUTHENTICATION_BACKENDS(self): - backends = [ - 'authentication.backends.pubkey.PublicKeyAuthBackend', - 'django.contrib.auth.backends.ModelBackend', - ] - if self.get('AUTH_LDAP'): - backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend') - if self.static_config.get('AUTH_CAS'): - backends.insert(0, 'authentication.backends.cas.CASBackend') - if self.static_config.get('AUTH_OPENID'): - backends.insert(0, 'jms_oidc_rp.backends.OIDCAuthPasswordBackend') - backends.insert(0, 'jms_oidc_rp.backends.OIDCAuthCodeBackend') - if self.static_config.get('AUTH_RADIUS'): - backends.insert(0, 'authentication.backends.radius.RadiusBackend') - if self.static_config.get('AUTH_SSO'): - backends.insert(0, 'authentication.backends.api.SSOAuthentication') - return backends - - def XPACK_LICENSE_IS_VALID(self): - if not HAS_XPACK: - return False - try: - from xpack.plugins.license.models import License - return License.has_valid_license() - except: - return False - - def XPACK_INTERFACE_LOGIN_TITLE(self): - default_title = _('Welcome to the JumpServer open source fortress') - if not HAS_XPACK: - return default_title - try: - from xpack.plugins.interface.models import Interface - return Interface.get_login_title() - except: - return default_title - - def LOGO_URLS(self): - logo_urls = {'logo_logout': static('img/logo.png'), - 'logo_index': static('img/logo_text.png'), - 'login_image': static('img/login_image.png'), - 'favicon': static('img/facio.ico')} - if not HAS_XPACK: - return logo_urls - try: - from xpack.plugins.interface.models import Interface - obj = Interface.interface() - if obj: - if obj.logo_logout: - logo_urls.update({'logo_logout': obj.logo_logout.url}) - if obj.logo_index: - logo_urls.update({'logo_index': obj.logo_index.url}) - if obj.login_image: - logo_urls.update({'login_image': obj.login_image.url}) - if obj.favicon: - logo_urls.update({'favicon': obj.favicon.url}) - except: - pass - return logo_urls - - def get_from_db(self, item): - if self.db_setting is not None: - value = self.db_setting.get(item) - if value is not None: - return value - return None - - def get(self, item): - # 先从数据库中获取 - value = self.get_from_db(item) - if value is not None: - return value - return self.static_config.get(item) - - class ConfigManager: config_class = Config @@ -694,7 +602,3 @@ class ConfigManager: # 对config进行兼容处理 config.compatible() return config - - @classmethod - def get_dynamic_config(cls, config): - return DynamicConfig(config) diff --git a/apps/jumpserver/const.py b/apps/jumpserver/const.py index c2889870e..c4607808f 100644 --- a/apps/jumpserver/const.py +++ b/apps/jumpserver/const.py @@ -4,12 +4,11 @@ import os from .conf import ConfigManager -__all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC'] +__all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG'] BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) VERSION = '2.0.0' CONFIG = ConfigManager.load_user_config() -DYNAMIC = ConfigManager.get_dynamic_config(CONFIG) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 4430aae2f..b0190e8a6 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -3,21 +3,21 @@ import os import ldap -from ..const import CONFIG, DYNAMIC, PROJECT_DIR +from ..const import CONFIG, PROJECT_DIR # OTP settings OTP_ISSUER_NAME = CONFIG.OTP_ISSUER_NAME OTP_VALID_WINDOW = CONFIG.OTP_VALID_WINDOW # Auth LDAP settings -AUTH_LDAP = DYNAMIC.AUTH_LDAP -AUTH_LDAP_SERVER_URI = DYNAMIC.AUTH_LDAP_SERVER_URI -AUTH_LDAP_BIND_DN = DYNAMIC.AUTH_LDAP_BIND_DN -AUTH_LDAP_BIND_PASSWORD = DYNAMIC.AUTH_LDAP_BIND_PASSWORD -AUTH_LDAP_SEARCH_OU = DYNAMIC.AUTH_LDAP_SEARCH_OU -AUTH_LDAP_SEARCH_FILTER = DYNAMIC.AUTH_LDAP_SEARCH_FILTER -AUTH_LDAP_START_TLS = DYNAMIC.AUTH_LDAP_START_TLS -AUTH_LDAP_USER_ATTR_MAP = DYNAMIC.AUTH_LDAP_USER_ATTR_MAP +AUTH_LDAP = CONFIG.AUTH_LDAP +AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI +AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN +AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD +AUTH_LDAP_SEARCH_OU = CONFIG.AUTH_LDAP_SEARCH_OU +AUTH_LDAP_SEARCH_FILTER = CONFIG.AUTH_LDAP_SEARCH_FILTER +AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS +AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP AUTH_LDAP_USER_QUERY_FIELD = 'username' AUTH_LDAP_GLOBAL_OPTIONS = { ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER, @@ -105,4 +105,17 @@ TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION LOGIN_CONFIRM_ENABLE = CONFIG.LOGIN_CONFIRM_ENABLE OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS -AUTHENTICATION_BACKENDS = DYNAMIC.AUTHENTICATION_BACKENDS +AUTHENTICATION_BACKENDS = [ + 'authentication.backends.pubkey.PublicKeyAuthBackend', + 'django.contrib.auth.backends.ModelBackend', +] + +if AUTH_CAS: + AUTHENTICATION_BACKENDS.insert(0, 'authentication.backends.cas.CASBackend') +if AUTH_OPENID: + AUTHENTICATION_BACKENDS.insert(0, 'jms_oidc_rp.backends.OIDCAuthPasswordBackend') + AUTHENTICATION_BACKENDS.insert(0, 'jms_oidc_rp.backends.OIDCAuthCodeBackend') +if AUTH_RADIUS: + AUTHENTICATION_BACKENDS.insert(0, 'authentication.backends.radius.RadiusBackend') +if AUTH_SSO: + AUTHENTICATION_BACKENDS.insert(0, 'authentication.backends.api.SSOAuthentication') diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 2a6291751..bfb956bdc 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -3,7 +3,7 @@ import os from django.urls import reverse_lazy from .. import const -from ..const import CONFIG, DYNAMIC +from ..const import CONFIG # Build paths inside the project like this: os.path.join(BASE_DIR, ...) VERSION = const.VERSION @@ -23,7 +23,7 @@ BOOTSTRAP_TOKEN = CONFIG.BOOTSTRAP_TOKEN DEBUG = CONFIG.DEBUG # Absolute url for some case, for example email link -SITE_URL = DYNAMIC.SITE_URL +SITE_URL = CONFIG.SITE_URL # LOG LEVEL LOG_LEVEL = CONFIG.LOG_LEVEL @@ -216,14 +216,14 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/' FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ] # Email config -EMAIL_HOST = DYNAMIC.EMAIL_HOST -EMAIL_PORT = DYNAMIC.EMAIL_PORT -EMAIL_HOST_USER = DYNAMIC.EMAIL_HOST_USER -EMAIL_HOST_PASSWORD = DYNAMIC.EMAIL_HOST_PASSWORD -EMAIL_FROM = DYNAMIC.EMAIL_FROM -EMAIL_RECIPIENT = DYNAMIC.EMAIL_RECIPIENT -EMAIL_USE_SSL = DYNAMIC.EMAIL_USE_SSL -EMAIL_USE_TLS = DYNAMIC.EMAIL_USE_TLS +EMAIL_HOST = CONFIG.EMAIL_HOST +EMAIL_PORT = CONFIG.EMAIL_PORT +EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER +EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD +EMAIL_FROM = CONFIG.EMAIL_FROM +EMAIL_RECIPIENT = CONFIG.EMAIL_RECIPIENT +EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL +EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS # Custom User Auth model diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index b1f2e1d41..95c1fce16 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from ..const import CONFIG, DYNAMIC +from ..const import CONFIG # Storage settings COMMAND_STORAGE = { @@ -11,7 +11,7 @@ DEFAULT_TERMINAL_COMMAND_STORAGE = { "TYPE": "server", }, } -TERMINAL_COMMAND_STORAGE = DYNAMIC.TERMINAL_COMMAND_STORAGE or {} +TERMINAL_COMMAND_STORAGE = CONFIG.TERMINAL_COMMAND_STORAGE or {} # Server 类型的录像存储 SERVER_REPLAY_STORAGE = CONFIG.SERVER_REPLAY_STORAGE @@ -28,20 +28,20 @@ DEFAULT_TERMINAL_REPLAY_STORAGE = { "TYPE": "server", }, } -TERMINAL_REPLAY_STORAGE = DYNAMIC.TERMINAL_REPLAY_STORAGE +TERMINAL_REPLAY_STORAGE = CONFIG.TERMINAL_REPLAY_STORAGE # Security settings -SECURITY_MFA_AUTH = DYNAMIC.SECURITY_MFA_AUTH -SECURITY_COMMAND_EXECUTION = DYNAMIC.SECURITY_COMMAND_EXECUTION -SECURITY_LOGIN_LIMIT_COUNT = DYNAMIC.SECURITY_LOGIN_LIMIT_COUNT -SECURITY_LOGIN_LIMIT_TIME = DYNAMIC.SECURITY_LOGIN_LIMIT_TIME # Unit: minute -SECURITY_MAX_IDLE_TIME = DYNAMIC.SECURITY_MAX_IDLE_TIME # Unit: minute -SECURITY_PASSWORD_EXPIRATION_TIME = DYNAMIC.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day -SECURITY_PASSWORD_MIN_LENGTH = DYNAMIC.SECURITY_PASSWORD_MIN_LENGTH # Unit: bit -SECURITY_PASSWORD_UPPER_CASE = DYNAMIC.SECURITY_PASSWORD_UPPER_CASE -SECURITY_PASSWORD_LOWER_CASE = DYNAMIC.SECURITY_PASSWORD_LOWER_CASE -SECURITY_PASSWORD_NUMBER = DYNAMIC.SECURITY_PASSWORD_NUMBER -SECURITY_PASSWORD_SPECIAL_CHAR = DYNAMIC.SECURITY_PASSWORD_SPECIAL_CHAR +SECURITY_MFA_AUTH = CONFIG.SECURITY_MFA_AUTH +SECURITY_COMMAND_EXECUTION = CONFIG.SECURITY_COMMAND_EXECUTION +SECURITY_LOGIN_LIMIT_COUNT = CONFIG.SECURITY_LOGIN_LIMIT_COUNT +SECURITY_LOGIN_LIMIT_TIME = CONFIG.SECURITY_LOGIN_LIMIT_TIME # Unit: minute +SECURITY_MAX_IDLE_TIME = CONFIG.SECURITY_MAX_IDLE_TIME # Unit: minute +SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day +SECURITY_PASSWORD_MIN_LENGTH = CONFIG.SECURITY_PASSWORD_MIN_LENGTH # Unit: bit +SECURITY_PASSWORD_UPPER_CASE = CONFIG.SECURITY_PASSWORD_UPPER_CASE +SECURITY_PASSWORD_LOWER_CASE = CONFIG.SECURITY_PASSWORD_LOWER_CASE +SECURITY_PASSWORD_NUMBER = CONFIG.SECURITY_PASSWORD_NUMBER +SECURITY_PASSWORD_SPECIAL_CHAR = CONFIG.SECURITY_PASSWORD_SPECIAL_CHAR SECURITY_PASSWORD_RULES = [ 'SECURITY_PASSWORD_MIN_LENGTH', 'SECURITY_PASSWORD_UPPER_CASE', @@ -51,24 +51,24 @@ SECURITY_PASSWORD_RULES = [ ] SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL SECURITY_VIEW_AUTH_NEED_MFA = CONFIG.SECURITY_VIEW_AUTH_NEED_MFA -SECURITY_SERVICE_ACCOUNT_REGISTRATION = DYNAMIC.SECURITY_SERVICE_ACCOUNT_REGISTRATION +SECURITY_SERVICE_ACCOUNT_REGISTRATION = CONFIG.SECURITY_SERVICE_ACCOUNT_REGISTRATION SECURITY_LOGIN_CAPTCHA_ENABLED = CONFIG.SECURITY_LOGIN_CAPTCHA_ENABLED SECURITY_LOGIN_CHALLENGE_ENABLED = CONFIG.SECURITY_LOGIN_CHALLENGE_ENABLED SECURITY_DATA_CRYPTO_ALGO = CONFIG.SECURITY_DATA_CRYPTO_ALGO -SECURITY_INSECURE_COMMAND = DYNAMIC.SECURITY_INSECURE_COMMAND +SECURITY_INSECURE_COMMAND = CONFIG.SECURITY_INSECURE_COMMAND SECURITY_INSECURE_COMMAND_LEVEL = CONFIG.SECURITY_INSECURE_COMMAND_LEVEL -SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER = DYNAMIC.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER +SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER = CONFIG.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER # Terminal other setting -TERMINAL_PASSWORD_AUTH = DYNAMIC.TERMINAL_PASSWORD_AUTH -TERMINAL_PUBLIC_KEY_AUTH = DYNAMIC.TERMINAL_PUBLIC_KEY_AUTH -TERMINAL_HEARTBEAT_INTERVAL = DYNAMIC.TERMINAL_HEARTBEAT_INTERVAL -TERMINAL_ASSET_LIST_SORT_BY = DYNAMIC.TERMINAL_ASSET_LIST_SORT_BY -TERMINAL_ASSET_LIST_PAGE_SIZE = DYNAMIC.TERMINAL_ASSET_LIST_PAGE_SIZE -TERMINAL_SESSION_KEEP_DURATION = DYNAMIC.TERMINAL_SESSION_KEEP_DURATION -TERMINAL_HOST_KEY = DYNAMIC.TERMINAL_HOST_KEY -TERMINAL_HEADER_TITLE = DYNAMIC.TERMINAL_HEADER_TITLE -TERMINAL_TELNET_REGEX = DYNAMIC.TERMINAL_TELNET_REGEX +TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH +TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH +TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL +TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY +TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE +TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION +TERMINAL_HOST_KEY = CONFIG.TERMINAL_HOST_KEY +TERMINAL_HEADER_TITLE = CONFIG.TERMINAL_HEADER_TITLE +TERMINAL_TELNET_REGEX = CONFIG.TERMINAL_TELNET_REGEX # User or user group permission cache time, default 3600 seconds ASSETS_PERM_CACHE_ENABLE = CONFIG.ASSETS_PERM_CACHE_ENABLE @@ -90,32 +90,25 @@ PERIOD_TASK_ENABLED = CONFIG.PERIOD_TASK_ENABLED USER_LOGIN_SINGLE_MACHINE_ENABLED = CONFIG.USER_LOGIN_SINGLE_MACHINE_ENABLED # Email custom content -EMAIL_SUBJECT_PREFIX = DYNAMIC.EMAIL_SUBJECT_PREFIX -EMAIL_SUFFIX = DYNAMIC.EMAIL_SUFFIX -EMAIL_CUSTOM_USER_CREATED_SUBJECT = DYNAMIC.EMAIL_CUSTOM_USER_CREATED_SUBJECT -EMAIL_CUSTOM_USER_CREATED_HONORIFIC = DYNAMIC.EMAIL_CUSTOM_USER_CREATED_HONORIFIC -EMAIL_CUSTOM_USER_CREATED_BODY = DYNAMIC.EMAIL_CUSTOM_USER_CREATED_BODY -EMAIL_CUSTOM_USER_CREATED_SIGNATURE = DYNAMIC.EMAIL_CUSTOM_USER_CREATED_SIGNATURE +EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX +EMAIL_SUFFIX = CONFIG.EMAIL_SUFFIX +EMAIL_CUSTOM_USER_CREATED_SUBJECT = CONFIG.EMAIL_CUSTOM_USER_CREATED_SUBJECT +EMAIL_CUSTOM_USER_CREATED_HONORIFIC = CONFIG.EMAIL_CUSTOM_USER_CREATED_HONORIFIC +EMAIL_CUSTOM_USER_CREATED_BODY = CONFIG.EMAIL_CUSTOM_USER_CREATED_BODY +EMAIL_CUSTOM_USER_CREATED_SIGNATURE = CONFIG.EMAIL_CUSTOM_USER_CREATED_SIGNATURE DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE DEFAULT_EXPIRED_YEARS = 70 -USER_GUIDE_URL = DYNAMIC.USER_GUIDE_URL +USER_GUIDE_URL = CONFIG.USER_GUIDE_URL HTTP_LISTEN_PORT = CONFIG.HTTP_LISTEN_PORT WS_LISTEN_PORT = CONFIG.WS_LISTEN_PORT -LOGIN_LOG_KEEP_DAYS = DYNAMIC.LOGIN_LOG_KEEP_DAYS +LOGIN_LOG_KEEP_DAYS = CONFIG.LOGIN_LOG_KEEP_DAYS TASK_LOG_KEEP_DAYS = CONFIG.TASK_LOG_KEEP_DAYS ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD AUTH_EXPIRED_SECONDS = 60 * 5 -# XPACK -XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID - -XPACK_INTERFACE_LOGIN_TITLE = DYNAMIC.XPACK_INTERFACE_LOGIN_TITLE - -LOGO_URLS = DYNAMIC.LOGO_URLS - CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index aac0c4d50..49b3d3b2d 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -127,3 +127,8 @@ CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO" CELERY_TASK_SOFT_TIME_LIMIT = 3600 ANSIBLE_LOG_DIR = os.path.join(PROJECT_DIR, 'data', 'ansible') + +# +REDIS_HOST = CONFIG.REDIS_HOST +REDIS_PORT = CONFIG.REDIS_PORT +REDIS_PASSWORD = CONFIG.REDIS_PASSWORD diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index e00cb3d92..ea2503cb9 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 2392990ca..7b24b53d4 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-01-21 15:49+0800\n" +"POT-Creation-Date: 2021-01-26 17:25+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -36,10 +36,10 @@ msgstr "自定义" #: assets/models/base.py:234 assets/models/cluster.py:18 #: assets/models/cmd_filter.py:21 assets/models/domain.py:21 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 -#: orgs/models.py:23 perms/models/base.py:48 settings/models.py:27 +#: orgs/models.py:23 perms/models/base.py:48 settings/models.py:29 #: terminal/models/storage.py:15 terminal/models/storage.py:55 #: terminal/models/task.py:16 terminal/models/terminal.py:131 -#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:518 +#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:517 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -95,10 +95,10 @@ msgstr "" #: assets/models/cmd_filter.py:57 assets/models/domain.py:22 #: assets/models/domain.py:56 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:37 orgs/models.py:26 -#: perms/models/base.py:56 settings/models.py:32 terminal/models/storage.py:21 +#: perms/models/base.py:56 settings/models.py:34 terminal/models/storage.py:21 #: terminal/models/storage.py:61 terminal/models/terminal.py:145 #: tickets/models/ticket.py:73 users/models/group.py:16 -#: users/models/user.py:551 users/templates/users/user_detail.html:115 +#: users/models/user.py:550 users/templates/users/user_detail.html:115 #: users/templates/users/user_granted_database_app.html:38 #: users/templates/users/user_granted_remote_app.html:37 #: users/templates/users/user_group_detail.html:62 @@ -164,7 +164,7 @@ msgstr "目标URL" #: assets/models/base.py:235 assets/models/gathered_user.py:15 #: audits/models.py:99 authentication/forms.py:11 #: authentication/templates/authentication/login.html:101 -#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:516 +#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:515 #: users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:53 #: users/templates/users/user_list.html:15 @@ -181,7 +181,8 @@ msgstr "用户名" #: assets/models/base.py:236 assets/serializers/asset_user.py:71 #: audits/signals_handler.py:42 authentication/forms.py:13 #: authentication/templates/authentication/login.html:109 -#: users/forms/user.py:22 users/forms/user.py:193 +#: settings/serializers/settings.py:84 users/forms/user.py:22 +#: users/forms/user.py:193 #: users/templates/users/user_otp_check_password.html:13 #: users/templates/users/user_password_update.html:43 #: users/templates/users/user_password_verify.html:18 @@ -204,7 +205,7 @@ msgstr "目标URL" #: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:190 assets/models/domain.py:52 -#: assets/serializers/asset_user.py:46 settings/serializers/settings.py:52 +#: assets/serializers/asset_user.py:46 settings/serializers/settings.py:103 #: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 msgid "IP" @@ -260,7 +261,7 @@ msgid "Platform" msgstr "系统平台" #: assets/models/asset.py:191 assets/serializers/asset_user.py:45 -#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:51 +#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:102 #: users/templates/users/_granted_assets.html:25 #: users/templates/users/user_asset_permission.html:157 msgid "Hostname" @@ -368,7 +369,7 @@ msgstr "标签管理" #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cmd_filter.py:60 assets/models/group.py:21 #: common/db/models.py:67 common/mixins/models.py:49 orgs/models.py:24 -#: orgs/models.py:427 perms/models/base.py:54 users/models/user.py:559 +#: orgs/models.py:427 perms/models/base.py:54 users/models/user.py:558 #: users/serializers/group.py:35 users/templates/users/user_detail.html:97 #: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:58 #: xpack/plugins/cloud/models.py:156 xpack/plugins/gathered_user/models.py:30 @@ -430,7 +431,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:537 +#: assets/models/cluster.py:22 users/models/user.py:536 #: users/templates/users/user_detail.html:62 msgid "Phone" msgstr "手机" @@ -456,7 +457,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:678 +#: users/models/user.py:677 msgid "System" msgstr "系统" @@ -560,7 +561,7 @@ msgstr "默认资产组" #: templates/index.html:78 terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:37 #: tickets/models/comment.py:17 users/forms/group.py:15 -#: users/models/user.py:159 users/models/user.py:666 +#: users/models/user.py:158 users/models/user.py:665 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 @@ -574,7 +575,7 @@ msgstr "默认资产组" msgid "User" msgstr "用户" -#: assets/models/label.py:19 assets/models/node.py:413 settings/models.py:28 +#: assets/models/label.py:19 assets/models/node.py:413 settings/models.py:30 msgid "Value" msgstr "值" @@ -741,14 +742,14 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:75 users/forms/profile.py:148 -#: users/models/user.py:548 users/templates/users/user_password_update.html:48 +#: users/models/user.py:547 users/templates/users/user_password_update.html:48 #: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile_update.html:46 #: users/templates/users/user_pubkey_update.html:46 msgid "Public key" msgstr "SSH公钥" -#: assets/serializers/asset_user.py:79 users/models/user.py:545 +#: assets/serializers/asset_user.py:79 users/models/user.py:544 msgid "Private key" msgstr "ssh私钥" @@ -1045,7 +1046,7 @@ msgstr "修改者" msgid "Disabled" msgstr "禁用" -#: audits/models.py:90 settings/models.py:31 +#: audits/models.py:90 settings/models.py:33 #: users/templates/users/user_detail.html:82 msgid "Enabled" msgstr "启用" @@ -1079,7 +1080,7 @@ msgstr "用户代理" #: audits/models.py:104 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/login_otp.html:6 -#: users/forms/profile.py:52 users/models/user.py:540 +#: users/forms/profile.py:52 users/models/user.py:539 #: users/serializers/user.py:232 users/templates/users/user_detail.html:77 #: users/templates/users/user_profile.html:87 msgid "MFA" @@ -1354,7 +1355,7 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:444 users/serializers/user.py:229 +#: users/models/user.py:443 users/serializers/user.py:229 #: users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 @@ -1363,7 +1364,7 @@ msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:445 users/serializers/user.py:230 +#: users/models/user.py:444 users/serializers/user.py:230 #: users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:170 msgid "Enable" @@ -1600,11 +1601,6 @@ msgstr "字段必须唯一" msgid "Should not contains special characters" msgstr "不能包含特殊字符" -#: jumpserver/conf.py:474 xpack/plugins/interface/api.py:18 -#: xpack/plugins/interface/models.py:36 -msgid "Welcome to the JumpServer open source fortress" -msgstr "欢迎使用JumpServer开源堡垒机" - #: jumpserver/views/celery_flower.py:23 msgid "

Flow service unavailable, check it

" msgstr "" @@ -1813,7 +1809,7 @@ msgstr "组织管理员" msgid "Organization auditor" msgstr "组织审计员" -#: orgs/models.py:424 users/forms/user.py:27 users/models/user.py:528 +#: orgs/models.py:424 users/forms/user.py:27 users/models/user.py:527 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:73 #: users/templates/users/user_list.html:16 @@ -1837,7 +1833,7 @@ msgstr "管理员正在修改授权,请稍等" msgid "The authorization cannot be revoked for the time being" msgstr "该授权暂时不能撤销" -#: perms/models/application_permission.py:27 users/models/user.py:160 +#: perms/models/application_permission.py:27 users/models/user.py:159 msgid "Application" msgstr "应用程序" @@ -1845,7 +1841,7 @@ msgstr "应用程序" msgid "Application permission" msgstr "应用管理" -#: perms/models/asset_permission.py:34 settings/serializers/settings.py:56 +#: perms/models/asset_permission.py:34 settings/serializers/settings.py:107 msgid "All" msgstr "全部" @@ -1887,7 +1883,7 @@ msgid "Asset permission" msgstr "资产授权" #: perms/models/base.py:50 templates/_nav.html:21 users/forms/user.py:168 -#: users/models/group.py:31 users/models/user.py:524 +#: users/models/group.py:31 users/models/user.py:523 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 @@ -1905,7 +1901,7 @@ msgstr "用户组" #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:77 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:43 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:81 -#: users/models/user.py:556 users/templates/users/user_detail.html:93 +#: users/models/user.py:555 users/templates/users/user_detail.html:93 #: users/templates/users/user_profile.html:120 msgid "Date expired" msgstr "失效日期" @@ -1944,27 +1940,310 @@ msgstr "收藏夹" msgid "Please wait while your data is being initialized" msgstr "数据正在初始化,请稍等" -#: settings/api.py:34 +#: settings/api/common.py:24 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" -#: settings/api.py:244 +#: settings/api/common.py:110 xpack/plugins/interface/api.py:18 +#: xpack/plugins/interface/models.py:36 +msgid "Welcome to the JumpServer open source fortress" +msgstr "欢迎使用JumpServer开源堡垒机" + +#: settings/api/ldap.py:189 msgid "Get ldap users is None" msgstr "获取 LDAP 用户为 None" -#: settings/api.py:251 +#: settings/api/ldap.py:196 msgid "Imported {} users successfully" msgstr "导入 {} 个用户成功" -#: settings/models.py:96 users/templates/users/reset_password.html:29 +#: settings/models.py:123 users/templates/users/reset_password.html:29 #: users/templates/users/user_profile.html:20 msgid "Setting" msgstr "设置" -#: settings/serializers/settings.py:57 +#: settings/serializers/settings.py:15 +msgid "Site url" +msgstr "当前站点URL" + +#: settings/serializers/settings.py:16 +msgid "eg: http://demo.jumpserver.org:8080" +msgstr "如: http://demo.jumpserver.org:8080" + +#: settings/serializers/settings.py:19 +msgid "User guide url" +msgstr "用户向导URL" + +#: settings/serializers/settings.py:20 +msgid "User first login update profile done redirect to it" +msgstr "用户第一次登录,修改profile后重定向到地址, 可以是 wiki 或 其他说明文档" + +#: settings/serializers/settings.py:27 +msgid "SMTP host" +msgstr "SMTP 主机" + +#: settings/serializers/settings.py:28 +msgid "SMTP port" +msgstr "SMTP 端口" + +#: settings/serializers/settings.py:29 +msgid "SMTP account" +msgstr "SMTP 账号" + +#: settings/serializers/settings.py:31 +msgid "SMTP password" +msgstr "SMTP 密码" + +#: settings/serializers/settings.py:32 +msgid "Tips: Some provider use token except password" +msgstr "提示:一些邮件提供商需要输入的是授权码" + +#: settings/serializers/settings.py:35 +msgid "Send user" +msgstr "发件人" + +#: settings/serializers/settings.py:36 +msgid "Tips: Send mail account, default SMTP account as the send account" +msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" + +#: settings/serializers/settings.py:39 +msgid "Test recipient" +msgstr "测试收件人" + +#: settings/serializers/settings.py:40 +msgid "Tips: Used only as a test mail recipient" +msgstr "提示:仅用来作为测试邮件收件人" + +#: settings/serializers/settings.py:43 +msgid "Use SSL" +msgstr "使用 SSL" + +#: settings/serializers/settings.py:44 +msgid "If SMTP port is 465, may be select" +msgstr "如果SMTP端口是465,通常需要启用 SSL" + +#: settings/serializers/settings.py:47 +msgid "Use TLS" +msgstr "使用 TLS" + +#: settings/serializers/settings.py:48 +msgid "If SMTP port is 587, may be select" +msgstr "如果SMTP端口是587,通常需要启用 TLS" + +#: settings/serializers/settings.py:51 +msgid "Subject prefix" +msgstr "主题前缀" + +#: settings/serializers/settings.py:58 +msgid "Create user email subject" +msgstr "邮件主题" + +#: settings/serializers/settings.py:59 +msgid "" +"Tips: When creating a user, send the subject of the email (eg:Create account " +"successfully)" +msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" + +#: settings/serializers/settings.py:63 +msgid "Create user honorific" +msgstr "邮件的敬语" + +#: settings/serializers/settings.py:64 +msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" +msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" + +#: settings/serializers/settings.py:68 +msgid "Create user email content" +msgstr "邮件的内容" + +#: settings/serializers/settings.py:69 +msgid "Tips:When creating a user, send the content of the email" +msgstr "提示: 创建用户时,发送设置密码邮件的内容" + +#: settings/serializers/settings.py:72 +msgid "Signature" +msgstr "署名" + +#: settings/serializers/settings.py:73 +msgid "Tips: Email signature (eg:jumpserver)" +msgstr "邮件署名 (如:jumpserver)" + +#: settings/serializers/settings.py:81 +msgid "LDAP server" +msgstr "LDAP 地址" + +#: settings/serializers/settings.py:81 +msgid "eg: ldap://localhost:389" +msgstr "" + +#: settings/serializers/settings.py:83 +msgid "Bind DN" +msgstr "绑定 DN" + +#: settings/serializers/settings.py:86 +msgid "User OU" +msgstr "用户 OU" + +#: settings/serializers/settings.py:87 +msgid "Use | split multi OUs" +msgstr "多个 OU 使用 | 分割" + +#: settings/serializers/settings.py:90 +msgid "User search filter" +msgstr "用户过滤器" + +#: settings/serializers/settings.py:91 +#, python-format +msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" +msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" + +#: settings/serializers/settings.py:94 +msgid "User attr map" +msgstr "用户属性映射" + +#: settings/serializers/settings.py:95 +msgid "" +"User attr map present how to map LDAP user attr to jumpserver, username,name," +"email is jumpserver attr" +msgstr "" +"用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," +"email 是jumpserver的用户需要属性" + +#: settings/serializers/settings.py:97 +msgid "Enable LDAP auth" +msgstr "启用 LDAP 认证" + +#: settings/serializers/settings.py:108 msgid "Auto" msgstr "自动" +#: settings/serializers/settings.py:114 +msgid "Password auth" +msgstr "密码认证" + +#: settings/serializers/settings.py:115 +msgid "Public key auth" +msgstr "密钥认证" + +#: settings/serializers/settings.py:116 +msgid "List sort by" +msgstr "资产列表排序" + +#: settings/serializers/settings.py:117 +msgid "List page size" +msgstr "资产列表每页数量" + +#: settings/serializers/settings.py:119 +msgid "Session keep duration" +msgstr "会话日志保存时间" + +#: settings/serializers/settings.py:120 +msgid "" +"Units: days, Session, record, command will be delete if more than duration, " +"only in database" +msgstr "" +"单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" +"受影响)" + +#: settings/serializers/settings.py:122 +msgid "Telnet login regex" +msgstr "Telnet 成功正则表达式" + +#: settings/serializers/settings.py:127 +msgid "Global MFA auth" +msgstr "全局启用 MFA 认证" + +#: settings/serializers/settings.py:128 +msgid "All user enable MFA" +msgstr "强制每个启用多因子认证" + +#: settings/serializers/settings.py:131 +msgid "Batch command execution" +msgstr "批量命令执行" + +#: settings/serializers/settings.py:132 +msgid "Allow user run batch command or not using ansible" +msgstr "是否允许用户使用 ansible 执行批量命令" + +#: settings/serializers/settings.py:135 +msgid "Enable terminal register" +msgstr "终端注册" + +#: settings/serializers/settings.py:136 +msgid "" +"Allow terminal register, after all terminal setup, you should disable this " +"for security" +msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" + +#: settings/serializers/settings.py:140 +msgid "Limit the number of login failures" +msgstr "限制登录失败次数" + +#: settings/serializers/settings.py:144 +msgid "Block logon interval" +msgstr "禁止登录时间间隔" + +#: settings/serializers/settings.py:145 +msgid "" +"Tip: (unit/minute) if the user has failed to log in for a limited number of " +"times, no login is allowed during this time interval." +msgstr "" +"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" + +#: settings/serializers/settings.py:149 +msgid "Connection max idle time" +msgstr "连接最大空闲时间" + +#: settings/serializers/settings.py:150 +msgid "If idle time more than it, disconnect connection Unit: minute" +msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" + +#: settings/serializers/settings.py:154 +msgid "User password expiration" +msgstr "用户密码过期时间" + +#: settings/serializers/settings.py:155 +msgid "" +"Tip: (unit: day) If the user does not update the password during the time, " +"the user password will expire failure;The password expiration reminder mail " +"will be automatic sent to the user by system within 5 days (daily) before " +"the password expires" +msgstr "" +"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" +"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" + +#: settings/serializers/settings.py:159 +msgid "Password minimum length" +msgstr "密码最小长度" + +#: settings/serializers/settings.py:162 +msgid "Must contain capital" +msgstr "必须包含大写字符" + +#: settings/serializers/settings.py:164 +msgid "Must contain lowercase" +msgstr "必须包含小写字符" + +#: settings/serializers/settings.py:165 +msgid "Must contain numeric" +msgstr "必须包含数字" + +#: settings/serializers/settings.py:166 +msgid "Must contain special" +msgstr "必须包含特殊字符" + +#: settings/serializers/settings.py:167 +msgid "Insecure command alert" +msgstr "危险命令告警" + +#: settings/serializers/settings.py:169 +msgid "Email recipient" +msgstr "邮件收件人" + +#: settings/serializers/settings.py:170 +msgid "Multiple user using , split" +msgstr "多个用户,使用 , 分割" + #: settings/utils/ldap.py:411 msgid "Host or port is disconnected: {}" msgstr "主机或端口不可连接: {}" @@ -3215,7 +3494,7 @@ msgstr "确认密码" msgid "Password does not match" msgstr "密码不一致" -#: users/forms/profile.py:89 users/models/user.py:520 +#: users/forms/profile.py:89 users/models/user.py:519 #: users/templates/users/user_detail.html:57 #: users/templates/users/user_profile.html:59 msgid "Email" @@ -3256,7 +3535,7 @@ msgstr "不能和原来的密钥相同" msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/user.py:31 users/models/user.py:563 +#: users/forms/user.py:31 users/models/user.py:562 #: users/templates/users/user_detail.html:89 #: users/templates/users/user_list.html:18 #: users/templates/users/user_profile.html:102 @@ -3290,39 +3569,39 @@ msgstr "设置密码" msgid "Password strategy" msgstr "密码策略" -#: users/models/user.py:157 +#: users/models/user.py:156 msgid "System administrator" msgstr "系统管理员" -#: users/models/user.py:158 +#: users/models/user.py:157 msgid "System auditor" msgstr "系统审计员" -#: users/models/user.py:446 users/templates/users/user_profile.html:90 +#: users/models/user.py:445 users/templates/users/user_profile.html:90 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:508 +#: users/models/user.py:507 msgid "Local" msgstr "数据库" -#: users/models/user.py:531 +#: users/models/user.py:530 msgid "Avatar" msgstr "头像" -#: users/models/user.py:534 users/templates/users/user_detail.html:68 +#: users/models/user.py:533 users/templates/users/user_detail.html:68 msgid "Wechat" msgstr "微信" -#: users/models/user.py:567 +#: users/models/user.py:566 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:674 +#: users/models/user.py:673 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:677 +#: users/models/user.py:676 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" diff --git a/apps/settings/api/__init__.py b/apps/settings/api/__init__.py new file mode 100644 index 000000000..151617be5 --- /dev/null +++ b/apps/settings/api/__init__.py @@ -0,0 +1,2 @@ +from .common import * +from .ldap import * diff --git a/apps/settings/api/common.py b/apps/settings/api/common.py new file mode 100644 index 000000000..ba7945ec7 --- /dev/null +++ b/apps/settings/api/common.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +# + +from smtplib import SMTPSenderRefused +from rest_framework import generics +from rest_framework.views import Response, APIView +from rest_framework.permissions import AllowAny +from django.conf import settings +from django.core.mail import send_mail, get_connection +from django.utils.translation import ugettext_lazy as _ +from django.templatetags.static import static + +from common.permissions import IsSuperUser +from common.utils import get_logger +from .. import serializers +from ..models import Setting + +logger = get_logger(__file__) + + +class MailTestingAPI(APIView): + permission_classes = (IsSuperUser,) + serializer_class = serializers.MailTestSerializer + success_message = _("Test mail sent to {}, please check") + + def post(self, request): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + + email_host = serializer.validated_data['EMAIL_HOST'] + email_port = serializer.validated_data['EMAIL_PORT'] + email_host_user = serializer.validated_data["EMAIL_HOST_USER"] + email_host_password = serializer.validated_data['EMAIL_HOST_PASSWORD'] + email_from = serializer.validated_data["EMAIL_FROM"] + email_recipient = serializer.validated_data["EMAIL_RECIPIENT"] + email_use_ssl = serializer.validated_data['EMAIL_USE_SSL'] + email_use_tls = serializer.validated_data['EMAIL_USE_TLS'] + + # 设置 settings 的值,会导致动态配置在当前进程失效 + # for k, v in serializer.validated_data.items(): + # if k.startswith('EMAIL'): + # setattr(settings, k, v) + try: + subject = "Test" + message = "Test smtp setting" + email_from = email_from or email_host_user + email_recipient = email_recipient or email_from + connection = get_connection( + host=email_host, port=email_port, + username=email_host_user, password=email_host_password, + use_tls=email_use_tls, use_ssl=email_use_ssl, + ) + send_mail( + subject, message, email_from, [email_recipient], + connection=connection + ) + except SMTPSenderRefused as e: + error = e.smtp_error + if isinstance(error, bytes): + for coding in ('gbk', 'utf8'): + try: + error = error.decode(coding) + except UnicodeDecodeError: + continue + else: + break + return Response({"error": str(error)}, status=400) + except Exception as e: + logger.error(e) + return Response({"error": str(e)}, status=400) + return Response({"msg": self.success_message.format(email_recipient)}) + + +class PublicSettingApi(generics.RetrieveAPIView): + permission_classes = (AllowAny,) + serializer_class = serializers.PublicSettingSerializer + + @staticmethod + def get_logo_urls(): + logo_urls = { + 'logo_logout': static('img/logo.png'), + 'logo_index': static('img/logo_text.png'), + 'login_image': static('img/login_image.png'), + 'favicon': static('img/facio.ico') + } + if not settings.XPACK_ENABLED: + return logo_urls + from xpack.plugins.interface.models import Interface + obj = Interface.interface() + if not obj: + return logo_urls + for attr in ['logo_logout', 'logo_index', 'login_image', 'favicon']: + if getattr(obj, attr, '') and getattr(obj, attr).url: + logo_urls.update({attr: getattr(obj, attr).url}) + return logo_urls + + @staticmethod + def get_xpack_license_is_valid(): + if not settings.XPACK_ENABLED: + return False + try: + from xpack.plugins.license.models import License + return License.has_valid_license() + except Exception as e: + logger.error(e) + return False + + @staticmethod + def get_login_title(): + default_title = _('Welcome to the JumpServer open source fortress') + if not settings.XPACK_ENABLED: + return default_title + from xpack.plugins.interface.models import Interface + return Interface.get_login_title() + + def get_object(self): + instance = { + "data": { + "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": settings.WINDOWS_SKIP_ALL_MANUAL_PASSWORD, + "SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME, + "XPACK_ENABLED": settings.XPACK_ENABLED, + "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE, + "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, + "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, + "SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION, + "SECURITY_PASSWORD_EXPIRATION_TIME": settings.SECURITY_PASSWORD_EXPIRATION_TIME, + "XPACK_LICENSE_IS_VALID": self.get_xpack_license_is_valid(), + "LOGIN_TITLE": self.get_login_title(), + "LOGO_URLS": self.get_logo_urls(), + "TICKETS_ENABLED": settings.TICKETS_ENABLED, + "PASSWORD_RULE": { + 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, + 'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE, + 'SECURITY_PASSWORD_LOWER_CASE': settings.SECURITY_PASSWORD_LOWER_CASE, + 'SECURITY_PASSWORD_NUMBER': settings.SECURITY_PASSWORD_NUMBER, + 'SECURITY_PASSWORD_SPECIAL_CHAR': settings.SECURITY_PASSWORD_SPECIAL_CHAR, + } + } + } + return instance + + +class SettingsApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsSuperUser,) + serializer_class_mapper = { + 'all': serializers.SettingsSerializer, + 'basic': serializers.BasicSettingSerializer, + 'terminal': serializers.TerminalSettingSerializer, + 'security': serializers.SecuritySettingSerializer, + 'ldap': serializers.LDAPSettingSerializer, + 'email': serializers.EmailSettingSerializer, + 'email_content': serializers.EmailContentSettingSerializer, + } + + def get_serializer_class(self): + category = self.request.query_params.get('category', serializers.BasicSettingSerializer) + return self.serializer_class_mapper.get(category, serializers.BasicSettingSerializer) + + def get_fields(self): + serializer = self.get_serializer_class()() + fields = serializer.get_fields() + return fields + + def get_object(self): + items = self.get_fields().keys() + return {item: getattr(settings, item) for item in items} + + def parse_serializer_data(self, serializer): + data = [] + fields = self.get_fields() + encrypted_items = [name for name, field in fields.items() if field.write_only] + category = self.request.query_params.get('category', '') + for name, value in serializer.validated_data.items(): + encrypted = name in encrypted_items + data.append({ + 'name': name, 'value': value, + 'encrypted': encrypted, 'category': category + }) + return data + + def perform_update(self, serializer): + settings_items = self.parse_serializer_data(serializer) + serializer_data = getattr(serializer, 'data', {}) + for item in settings_items: + changed, setting = Setting.update_or_create(**item) + if not changed: + continue + serializer_data[setting.name] = setting.cleaned_value + setattr(serializer, '_data', serializer_data) diff --git a/apps/settings/api.py b/apps/settings/api/ldap.py similarity index 58% rename from apps/settings/api.py rename to apps/settings/api/ldap.py index 1fba03e6a..0c982d1b1 100644 --- a/apps/settings/api.py +++ b/apps/settings/api/ldap.py @@ -10,16 +10,15 @@ from rest_framework.views import Response, APIView from django.conf import settings from django.core.mail import send_mail, get_connection from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers -from .utils import ( +from ..utils import ( LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil, LDAP_USE_CACHE_FLAGS, LDAPTestUtil, ObjectDict ) -from .tasks import sync_ldap_user +from ..tasks import sync_ldap_user from common.permissions import IsOrgAdmin, IsSuperUser from common.utils import get_logger -from .serializers import ( +from ..serializers import ( MailTestSerializer, LDAPTestConfigSerializer, LDAPUserSerializer, PublicSettingSerializer, LDAPTestLoginSerializer, SettingsSerializer ) @@ -28,60 +27,6 @@ from users.models import User logger = get_logger(__file__) -class MailTestingAPI(APIView): - permission_classes = (IsSuperUser,) - serializer_class = MailTestSerializer - success_message = _("Test mail sent to {}, please check") - - def post(self, request): - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - email_host = serializer.validated_data['EMAIL_HOST'] - email_port = serializer.validated_data['EMAIL_PORT'] - email_host_user = serializer.validated_data["EMAIL_HOST_USER"] - email_host_password = serializer.validated_data['EMAIL_HOST_PASSWORD'] - email_from = serializer.validated_data["EMAIL_FROM"] - email_recipient = serializer.validated_data["EMAIL_RECIPIENT"] - email_use_ssl = serializer.validated_data['EMAIL_USE_SSL'] - email_use_tls = serializer.validated_data['EMAIL_USE_TLS'] - - # 设置 settings 的值,会导致动态配置在当前进程失效 - # for k, v in serializer.validated_data.items(): - # if k.startswith('EMAIL'): - # setattr(settings, k, v) - try: - subject = "Test" - message = "Test smtp setting" - email_from = email_from or email_host_user - email_recipient = email_recipient or email_from - connection = get_connection( - host=email_host, port=email_port, - username=email_host_user, password=email_host_password, - use_tls=email_use_tls, use_ssl=email_use_ssl, - ) - send_mail( - subject, message, email_from, [email_recipient], - connection=connection - ) - except SMTPSenderRefused as e: - resp = e.smtp_error - if isinstance(resp, bytes): - for coding in ('gbk', 'utf8'): - try: - resp = resp.decode(coding) - except UnicodeDecodeError: - continue - else: - break - return Response({"error": str(resp)}, status=400) - except Exception as e: - print(e) - return Response({"error": str(e)}, status=400) - return Response({"msg": self.success_message.format(email_recipient)}) - else: - return Response({"error": str(serializer.errors)}, status=400) - - class LDAPTestingConfigAPI(APIView): permission_classes = (IsSuperUser,) serializer_class = LDAPTestConfigSerializer @@ -262,59 +207,3 @@ class LDAPCacheRefreshAPI(generics.RetrieveAPIView): return Response(data={'msg': str(e)}, status=400) return Response(data={'msg': 'success'}) - -class PublicSettingApi(generics.RetrieveAPIView): - permission_classes = () - serializer_class = PublicSettingSerializer - - def get_object(self): - instance = { - "data": { - "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": settings.WINDOWS_SKIP_ALL_MANUAL_PASSWORD, - "SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME, - "XPACK_ENABLED": settings.XPACK_ENABLED, - "XPACK_LICENSE_IS_VALID": settings.XPACK_LICENSE_IS_VALID, - "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE, - "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, - "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, - "SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION, - "LOGIN_TITLE": settings.XPACK_INTERFACE_LOGIN_TITLE, - "SECURITY_PASSWORD_EXPIRATION_TIME": settings.SECURITY_PASSWORD_EXPIRATION_TIME, - "LOGO_URLS": settings.LOGO_URLS, - "TICKETS_ENABLED": settings.TICKETS_ENABLED, - "PASSWORD_RULE": { - 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, - 'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE, - 'SECURITY_PASSWORD_LOWER_CASE': settings.SECURITY_PASSWORD_LOWER_CASE, - 'SECURITY_PASSWORD_NUMBER': settings.SECURITY_PASSWORD_NUMBER, - 'SECURITY_PASSWORD_SPECIAL_CHAR': settings.SECURITY_PASSWORD_SPECIAL_CHAR, - } - } - } - return instance - - -class SettingsApi(generics.RetrieveUpdateAPIView): - permission_classes = (IsSuperUser,) - serializer_class = SettingsSerializer - - def get_object(self): - instance = {category: self._get_setting_fields_obj(list(category_serializer.get_fields())) - for category, category_serializer in self.serializer_class().get_fields().items() - if isinstance(category_serializer, serializers.Serializer)} - return ObjectDict(instance) - - def perform_update(self, serializer): - serializer.save() - - def _get_setting_fields_obj(self, category_fields): - if isinstance(category_fields, Iterable): - fields_data = {field_name: getattr(settings, field_name) - for field_name in category_fields} - return ObjectDict(fields_data) - - if isinstance(category_fields, str): - fields_data = {category_fields: getattr(settings, category_fields)} - return ObjectDict(fields_data) - - return ObjectDict() diff --git a/apps/settings/models.py b/apps/settings/models.py index 30732a00c..5617b010d 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -3,9 +3,11 @@ import json from django.db import models from django.db.utils import ProgrammingError, OperationalError from django.utils.translation import ugettext_lazy as _ -from django.core.cache import cache +from django.conf import settings -from common.utils import signer +from common.utils import signer, get_logger + +logger = get_logger(__name__) class SettingQuerySet(models.QuerySet): @@ -37,24 +39,6 @@ class Setting(models.Model): def __str__(self): return self.name - @classmethod - def get(cls, item): - cached = cls.get_from_cache(item) - if cached is not None: - return cached - instances = cls.objects.filter(name=item) - if len(instances) == 1: - s = instances[0] - s.refresh_setting() - return s.cleaned_value - return None - - @classmethod - def get_from_cache(cls, item): - key = cls.cache_key_prefix + item - cached = cache.get(key) - return cached - @property def cleaned_value(self): try: @@ -87,9 +71,52 @@ class Setting(models.Model): except (ProgrammingError, OperationalError): pass + @classmethod + def refresh_item(cls, name): + item = cls.objects.filter(name=name).first() + if not item: + return + item.refresh_setting() + def refresh_setting(self): - key = self.cache_key_prefix + self.name - cache.set(key, self.cleaned_value, None) + logger.debug(f"Refresh setting: {self.name}") + if hasattr(self.__class__, f'refresh_{self.name}'): + getattr(self.__class__, f'refresh_{self.name}')() + else: + setattr(settings, self.name, self.cleaned_value) + + @classmethod + def refresh_AUTH_LDAP(cls): + setting = cls.objects.filter(name='AUTH_LDAP').first() + if not setting: + return + ldap_backend = 'authentication.backends.ldap.LDAPAuthorizationBackend' + backends = settings.AUTHENTICATION_BACKENDS + has = ldap_backend in backends + if setting.cleaned_value and not has: + settings.AUTHENTICATION_BACKENDS.insert(0, ldap_backend) + + if not setting.cleaned_value and has: + index = backends.index(ldap_backend) + backends.pop(index) + settings.AUTH_LDAP = setting.cleaned_value + + @classmethod + def update_or_create(cls, name='', value='', encrypted=False, category=''): + """ + 不能使用 Model 提供的,update_or_create 因为这里有 encrypted 和 cleaned_value + :return: (changed, instance) + """ + setting = cls.objects.filter(name=name).first() + changed = False + if not setting: + setting = Setting(name=name, encrypted=encrypted, category=category) + if setting.cleaned_value != value: + setting.encrypted = encrypted + setting.cleaned_value = value + setting.save() + changed = True + return changed, setting class Meta: db_table = "settings_setting" diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 208c86078..982d78ff4 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -1,49 +1,100 @@ # coding: utf-8 -from django.db import transaction from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from ..models import Setting -__all__ = ['SettingsSerializer'] +__all__ = [ + 'BasicSettingSerializer', 'EmailSettingSerializer', 'EmailContentSettingSerializer', + 'LDAPSettingSerializer', 'TerminalSettingSerializer', 'SecuritySettingSerializer', + 'SettingsSerializer' +] class BasicSettingSerializer(serializers.Serializer): - SITE_URL = serializers.URLField(required=True) - USER_GUIDE_URL = serializers.URLField(required=False, allow_blank=True, ) - EMAIL_SUBJECT_PREFIX = serializers.CharField(max_length=1024, required=True) + SITE_URL = serializers.URLField( + required=True, label=_("Site url"), + help_text=_('eg: http://demo.jumpserver.org:8080') + ) + USER_GUIDE_URL = serializers.URLField( + required=False, allow_blank=True, label=_("User guide url"), + help_text=_('User first login update profile done redirect to it') + ) class EmailSettingSerializer(serializers.Serializer): - encrypt_fields = ["EMAIL_HOST_PASSWORD", ] + # encrypt_fields 现在使用 write_only 来判断了 - EMAIL_HOST = serializers.CharField(max_length=1024, required=True) - EMAIL_PORT = serializers.CharField(max_length=5, required=True) - EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True) - EMAIL_HOST_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False, ) - EMAIL_FROM = serializers.CharField(max_length=128, allow_blank=True, required=False) - EMAIL_RECIPIENT = serializers.CharField(max_length=128, allow_blank=True, required=False) - EMAIL_USE_SSL = serializers.BooleanField(required=False) - EMAIL_USE_TLS = serializers.BooleanField(required=False) + EMAIL_HOST = serializers.CharField(max_length=1024, required=True, label=_("SMTP host")) + EMAIL_PORT = serializers.CharField(max_length=5, required=True, label=_("SMTP port")) + EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True, label=_("SMTP account")) + EMAIL_HOST_PASSWORD = serializers.CharField( + max_length=1024, write_only=True, required=False, label=_("SMTP password"), + help_text=_("Tips: Some provider use token except password") + ) + EMAIL_FROM = serializers.CharField( + max_length=128, allow_blank=True, required=False, label=_('Send user'), + help_text=_('Tips: Send mail account, default SMTP account as the send account') + ) + EMAIL_RECIPIENT = serializers.CharField( + max_length=128, allow_blank=True, required=False, label=_('Test recipient'), + help_text=_('Tips: Used only as a test mail recipient') + ) + EMAIL_USE_SSL = serializers.BooleanField( + required=False, label=_('Use SSL'), + help_text=_('If SMTP port is 465, may be select') + ) + EMAIL_USE_TLS = serializers.BooleanField( + required=False, label=_("Use TLS"), + help_text=_('If SMTP port is 587, may be select') + ) + EMAIL_SUBJECT_PREFIX = serializers.CharField( + max_length=1024, required=True, label=_('Subject prefix') + ) class EmailContentSettingSerializer(serializers.Serializer): - EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField(max_length=1024, allow_blank=True, required=False, ) - EMAIL_CUSTOM_USER_CREATED_HONORIFIC = serializers.CharField(max_length=1024, allow_blank=True, required=False, ) - EMAIL_CUSTOM_USER_CREATED_BODY = serializers.CharField(max_length=4096, allow_blank=True, required=False) - EMAIL_CUSTOM_USER_CREATED_SIGNATURE = serializers.CharField(max_length=512, allow_blank=True, required=False) + EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField( + max_length=1024, allow_blank=True, required=False, + label=_('Create user email subject'), + help_text=_('Tips: When creating a user, send the subject of the email (eg:Create account successfully)') + ) + EMAIL_CUSTOM_USER_CREATED_HONORIFIC = serializers.CharField( + max_length=1024, allow_blank=True, required=False, + label=_('Create user honorific'), + help_text=_('Tips: When creating a user, send the honorific of the email (eg:Hello)') + ) + EMAIL_CUSTOM_USER_CREATED_BODY = serializers.CharField( + max_length=4096, allow_blank=True, required=False, + label=_('Create user email content'), + help_text=_('Tips:When creating a user, send the content of the email') + ) + EMAIL_CUSTOM_USER_CREATED_SIGNATURE = serializers.CharField( + max_length=512, allow_blank=True, required=False, label=_('Signature'), + help_text=_('Tips: Email signature (eg:jumpserver)') + ) -class LdapSettingSerializer(serializers.Serializer): - encrypt_fields = ["AUTH_LDAP_BIND_PASSWORD", ] +class LDAPSettingSerializer(serializers.Serializer): + # encrypt_fields 现在使用 write_only 来判断了 - AUTH_LDAP_SERVER_URI = serializers.CharField(required=True) - AUTH_LDAP_BIND_DN = serializers.CharField(required=False) - AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False) - AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, allow_blank=True, required=False) - AUTH_LDAP_SEARCH_FILTER = serializers.CharField(max_length=1024, required=True) - AUTH_LDAP_USER_ATTR_MAP = serializers.DictField(required=True) - AUTH_LDAP = serializers.BooleanField(required=False) + AUTH_LDAP_SERVER_URI = serializers.CharField( + required=True, max_length=1024, label=_('LDAP server'), help_text=_('eg: ldap://localhost:389') + ) + AUTH_LDAP_BIND_DN = serializers.CharField(required=False, max_length=1024, label=_('Bind DN')) + AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False, label=_('Password')) + AUTH_LDAP_SEARCH_OU = serializers.CharField( + max_length=1024, allow_blank=True, required=False, label=_('User OU'), + help_text=_('Use | split multi OUs') + ) + AUTH_LDAP_SEARCH_FILTER = serializers.CharField( + max_length=1024, required=True, label=_('User search filter'), + help_text=_('Choice may be (cn|uid|sAMAccountName)=%(user)s)') + ) + AUTH_LDAP_USER_ATTR_MAP = serializers.DictField( + required=True, label=_('User attr map'), + help_text=_('User attr map present how to map LDAP user attr to jumpserver, username,name,email is jumpserver attr') + ) + AUTH_LDAP = serializers.BooleanField(required=False, label=_('Enable LDAP auth')) class TerminalSettingSerializer(serializers.Serializer): @@ -60,67 +111,75 @@ class TerminalSettingSerializer(serializers.Serializer): ('25', '25'), ('50', '50'), ) - TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False) - TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField(required=False) - TERMINAL_HEARTBEAT_INTERVAL = serializers.IntegerField(min_value=5, max_value=99999, required=False) - TERMINAL_ASSET_LIST_SORT_BY = serializers.ChoiceField(SORT_BY_CHOICES, required=False) - TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField(PAGE_SIZE_CHOICES, required=False) - TERMINAL_SESSION_KEEP_DURATION = serializers.IntegerField(min_value=1, max_value=99999, required=True) - TERMINAL_TELNET_REGEX = serializers.CharField(allow_blank=True, required=False) + TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False, label=_('Password auth')) + TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField(required=False, label=_('Public key auth')) + TERMINAL_ASSET_LIST_SORT_BY = serializers.ChoiceField(SORT_BY_CHOICES, required=False, label=_('List sort by')) + TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField(PAGE_SIZE_CHOICES, required=False, label=_('List page size')) + TERMINAL_SESSION_KEEP_DURATION = serializers.IntegerField( + min_value=1, max_value=99999, required=True, label=_('Session keep duration'), + help_text=_('Units: days, Session, record, command will be delete if more than duration, only in database') + ) + TERMINAL_TELNET_REGEX = serializers.CharField(allow_blank=True, max_length=1024, required=False, label=_('Telnet login regex')) class SecuritySettingSerializer(serializers.Serializer): - SECURITY_MFA_AUTH = serializers.BooleanField(required=False) - SECURITY_COMMAND_EXECUTION = serializers.BooleanField(required=False) - SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(required=True) - SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(min_value=3, max_value=99999, required=True) - SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField(min_value=5, max_value=99999, required=True) - SECURITY_MAX_IDLE_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=False) - SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=True) - SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField(min_value=6, max_value=30, required=True) - SECURITY_PASSWORD_UPPER_CASE = serializers.BooleanField(required=False) - SECURITY_PASSWORD_LOWER_CASE = serializers.BooleanField(required=False) - SECURITY_PASSWORD_NUMBER = serializers.BooleanField(required=False) - SECURITY_PASSWORD_SPECIAL_CHAR = serializers.BooleanField(required=False) - SECURITY_INSECURE_COMMAND = serializers.BooleanField(required=False) - SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER = serializers.CharField(max_length=8192, required=False, allow_blank=True) + SECURITY_MFA_AUTH = serializers.BooleanField( + required=False, label=_("Global MFA auth"), + help_text=_('All user enable MFA') + ) + SECURITY_COMMAND_EXECUTION = serializers.BooleanField( + required=False, label=_('Batch command execution'), + help_text=_('Allow user run batch command or not using ansible') + ) + SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField( + required=True, label=_('Enable terminal register'), + help_text=_("Allow terminal register, after all terminal setup, you should disable this for security") + ) + SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField( + min_value=3, max_value=99999, + label=_('Limit the number of login failures') + ) + SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField( + min_value=5, max_value=99999, required=True, + label=_('Block logon interval'), + help_text=_('Tip: (unit/minute) if the user has failed to log in for a limited number of times, no login is allowed during this time interval.') + ) + SECURITY_MAX_IDLE_TIME = serializers.IntegerField( + min_value=1, max_value=99999, required=False, + label=_('Connection max idle time'), + help_text=_('If idle time more than it, disconnect connection Unit: minute') + ) + SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField( + min_value=1, max_value=99999, required=True, + label=_('User password expiration'), + help_text=_('Tip: (unit: day) If the user does not update the password during the time, the user password will expire failure;The password expiration reminder mail will be automatic sent to the user by system within 5 days (daily) before the password expires') + ) + SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField( + min_value=6, max_value=30, required=True, + label=_('Password minimum length') + ) + SECURITY_PASSWORD_UPPER_CASE = serializers.BooleanField( + required=False, label=_('Must contain capital') + ) + SECURITY_PASSWORD_LOWER_CASE = serializers.BooleanField(required=False, label=_('Must contain lowercase')) + SECURITY_PASSWORD_NUMBER = serializers.BooleanField(required=False, label=_('Must contain numeric')) + SECURITY_PASSWORD_SPECIAL_CHAR = serializers.BooleanField(required=False, label=_('Must contain special')) + SECURITY_INSECURE_COMMAND = serializers.BooleanField(required=False, label=_('Insecure command alert')) + SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER = serializers.CharField( + max_length=8192, required=False, allow_blank=True, label=_('Email recipient'), + help_text=_('Multiple user using , split') + ) -class SettingsSerializer(serializers.Serializer): - basic = BasicSettingSerializer(required=False) - email = EmailSettingSerializer(required=False) - email_content = EmailContentSettingSerializer(required=False) - ldap = LdapSettingSerializer(required=False) - terminal = TerminalSettingSerializer(required=False) - security = SecuritySettingSerializer(required=False) +class SettingsSerializer( + BasicSettingSerializer, + EmailSettingSerializer, + EmailContentSettingSerializer, + LDAPSettingSerializer, + TerminalSettingSerializer, + SecuritySettingSerializer +): - encrypt_fields = ["EMAIL_HOST_PASSWORD", "AUTH_LDAP_BIND_PASSWORD"] + # encrypt_fields 现在使用 write_only 来判断了 + pass - def create(self, validated_data): - pass - - def update(self, instance, validated_data): - for category, category_data in validated_data.items(): - if not category_data: - continue - self.update_validated_settings(category_data) - for field_name, field_value in category_data.items(): - setattr(getattr(instance, category), field_name, field_value) - - return instance - - def update_validated_settings(self, validated_data, category='default'): - if not validated_data: - return - with transaction.atomic(): - for field_name, field_value in validated_data.items(): - try: - setting = Setting.objects.get(name=field_name) - except Setting.DoesNotExist: - setting = Setting() - encrypted = True if field_name in self.encrypt_fields else False - setting.name = field_name - setting.category = category - setting.encrypted = encrypted - setting.cleaned_value = field_value - setting.save() diff --git a/apps/settings/signals_handler.py b/apps/settings/signals_handler.py index 94c4569bd..9625df3f6 100644 --- a/apps/settings/signals_handler.py +++ b/apps/settings/signals_handler.py @@ -1,28 +1,44 @@ # -*- coding: utf-8 -*- # import json +import threading from django.dispatch import receiver from django.db.models.signals import post_save, pre_save +from django.utils.functional import LazyObject from jumpserver.utils import current_request +from common.decorator import on_transaction_commit from common.utils import get_logger, ssh_key_gen +from common.utils.connection import RedisPubSub from common.signals import django_ready from .models import Setting logger = get_logger(__file__) -@receiver(post_save, sender=Setting, dispatch_uid="my_unique_identifier") +def get_settings_pub_sub(): + return RedisPubSub('settings') + + +class SettingSubPub(LazyObject): + def _setup(self): + self._wrapped = get_settings_pub_sub() + + +setting_pub_sub = SettingSubPub() + + +@receiver(post_save, sender=Setting) +@on_transaction_commit def refresh_settings_on_changed(sender, instance=None, **kwargs): if instance: - instance.refresh_setting() + setting_pub_sub.publish(instance.name) @receiver(django_ready) def on_django_ready_add_db_config(sender, **kwargs): - from django.conf import settings - settings.DYNAMIC.db_setting = Setting + Setting.refresh_all_settings() @receiver(django_ready) @@ -41,9 +57,27 @@ def auto_generate_terminal_host_key(sender, **kwargs): def on_create_set_created_by(sender, instance=None, **kwargs): if getattr(instance, '_ignore_auto_created_by', False) is True: return - if hasattr(instance, 'created_by') and not instance.created_by: - if current_request and current_request.user.is_authenticated: - user_name = current_request.user.name - if isinstance(user_name, str): - user_name = user_name[:30] - instance.created_by = user_name + if not hasattr(instance, 'created_by') or instance.created_by: + return + if current_request and current_request.user.is_authenticated: + user_name = current_request.user.name + if isinstance(user_name, str): + user_name = user_name[:30] + instance.created_by = user_name + + +@receiver(django_ready) +def subscribe_settings_change(sender, **kwargs): + logger.debug("Start subscribe setting change") + + def keep_subscribe(): + sub = setting_pub_sub.subscribe() + for msg in sub.listen(): + if msg["type"] != "message": + continue + item = msg['data'].decode() + logger.debug("Found setting change: {}".format(str(item))) + Setting.refresh_item(item) + t = threading.Thread(target=keep_subscribe) + t.daemon = True + t.start() diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 36e5eb330..1d8590ed0 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -16,7 +16,6 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse -from common.local import LOCAL_DYNAMIC_SETTINGS from orgs.utils import current_org from orgs.models import OrganizationMember, Organization from common.utils import date_expired_default, get_logger, lazyproperty @@ -452,7 +451,7 @@ class MFAMixin: @property def mfa_force_enabled(self): - if LOCAL_DYNAMIC_SETTINGS.SECURITY_MFA_AUTH: + if settings.SECURITY_MFA_AUTH: return True return self.mfa_level == 2