From 16cc4a0f4e01274cde69278e8a142fc1e4b05ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Thu, 22 Nov 2018 12:27:27 +0800 Subject: [PATCH] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9settings=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=20(#2067)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 修改settings配置 * [Update] 修改settings * [Update] 修改密码校验规则前后端逻辑 * [Update] 修改用户config机制 * [Update] 修改配置 * [Update] 修改config example增加翻译 --- apps/common/api.py | 17 +- apps/common/forms.py | 23 +- apps/common/models.py | 30 +- apps/common/signals.py | 1 - apps/common/signals_handler.py | 49 ++- apps/common/tasks.py | 7 +- apps/common/urls/api_urls.py | 2 +- apps/common/utils.py | 13 +- apps/common/views.py | 3 - apps/jumpserver/conf.py | 331 ++++++++++++++++++ apps/jumpserver/settings.py | 105 +++--- apps/locale/zh/LC_MESSAGES/django.mo | Bin 55767 -> 55734 bytes apps/locale/zh/LC_MESSAGES/django.po | 289 ++++++++------- apps/locale/zh/LC_MESSAGES/djangojs.mo | Bin 2069 -> 2465 bytes apps/locale/zh/LC_MESSAGES/djangojs.po | 62 ++-- apps/perms/views.py | 4 +- apps/static/js/jumpserver.js | 53 ++- apps/terminal/models.py | 5 +- apps/users/models/user.py | 3 +- .../users/templates/users/reset_password.html | 8 +- .../templates/users/user_password_update.html | 8 +- apps/users/templates/users/user_update.html | 8 +- apps/users/utils.py | 73 ++-- apps/users/views/login.py | 13 +- apps/users/views/user.py | 10 +- config_example.py | 59 ++-- 26 files changed, 777 insertions(+), 399 deletions(-) create mode 100644 apps/jumpserver/conf.py diff --git a/apps/common/api.py b/apps/common/api.py index 51416fa3b..d0512ebca 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -168,13 +168,16 @@ class DjangoSettingsAPI(APIView): return Response("Not in debug mode") data = {} - for k, v in settings.__dict__.items(): - if k and k.isupper(): - try: - json.dumps(v) - data[k] = v - except (json.JSONDecodeError, TypeError): - data[k] = str(v) + for i in [settings, getattr(settings, '_wrapped')]: + if not i: + continue + for k, v in i.__dict__.items(): + if k and k.isupper(): + try: + json.dumps(v) + data[k] = v + except (json.JSONDecodeError, TypeError): + data[k] = str(v) return Response(data) diff --git a/apps/common/forms.py b/apps/common/forms.py index d164699fe..34337159f 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -5,7 +5,7 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from django.db import transaction -from .models import Setting, common_settings +from .models import Setting, settings from .fields import FormDictField, FormEncryptCharField, \ FormEncryptMixin @@ -14,7 +14,7 @@ class BaseForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): - value = getattr(common_settings, name) + value = getattr(settings, name, None) # django_value = getattr(settings, name) if hasattr(settings, name) else None if value is None: # and django_value is None: @@ -43,7 +43,7 @@ class BaseForm(forms.Form): field = self.fields[name] if isinstance(field.widget, forms.PasswordInput) and not value: continue - if value == getattr(common_settings, name): + if value == getattr(settings, name): continue encrypted = True if isinstance(field, FormEncryptMixin) else False @@ -69,7 +69,6 @@ class BasicSettingForm(BaseForm): ) EMAIL_SUBJECT_PREFIX = forms.CharField( max_length=1024, label=_("Email Subject Prefix"), - initial="[Jumpserver] " ) @@ -97,21 +96,21 @@ class EmailSettingForm(BaseForm): class LDAPSettingForm(BaseForm): AUTH_LDAP_SERVER_URI = forms.CharField( - label=_("LDAP server"), initial='ldap://localhost:389' + label=_("LDAP server"), ) AUTH_LDAP_BIND_DN = forms.CharField( - label=_("Bind DN"), initial='cn=admin,dc=jumpserver,dc=org' + label=_("Bind DN"), ) AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField( - label=_("Password"), initial='', + label=_("Password"), widget=forms.PasswordInput, required=False ) AUTH_LDAP_SEARCH_OU = forms.CharField( - label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org', + label=_("User OU"), help_text=_("Use | split User OUs") ) AUTH_LDAP_SEARCH_FILTER = forms.CharField( - label=_("User search filter"), initial='(cn=%(user)s)', + label=_("User search filter"), help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)") ) AUTH_LDAP_USER_ATTR_MAP = FormDictField( @@ -119,14 +118,14 @@ class LDAPSettingForm(BaseForm): help_text=_( "User attr map present how to map LDAP user attr to jumpserver, " "username,name,email is jumpserver attr" - ) + ), ) # AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU # AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER AUTH_LDAP_START_TLS = forms.BooleanField( - label=_("Use SSL"), initial=False, required=False + label=_("Use SSL"), required=False ) - AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), initial=False, required=False) + AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), required=False) class TerminalSettingForm(BaseForm): diff --git a/apps/common/models.py b/apps/common/models.py index a1ae9b9ad..a678d32dd 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -2,6 +2,7 @@ import json import ldap from django.db import models +from django.core.cache import cache from django.db.utils import ProgrammingError, OperationalError from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -40,15 +41,7 @@ class Setting(models.Model): return self.name def __getattr__(self, item): - default = getattr(settings, item, None) - try: - instances = self.__class__.objects.filter(name=item) - except Exception: - return default - if len(instances) == 1: - return instances[0].cleaned_value - else: - return default + return cache.get(item) @property def cleaned_value(self): @@ -106,22 +99,15 @@ class Setting(models.Model): def refresh_setting(self): setattr(settings, self.name, self.cleaned_value) - if self.name == "AUTH_LDAP": if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS: - settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND) + old_setting = settings.AUTHENTICATION_BACKENDS + old_setting.insert(0, settings.AUTH_LDAP_BACKEND) + settings.AUTHENTICATION_BACKENDS = old_setting elif not self.cleaned_value and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS: - settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND) - - if self.name == "AUTH_LDAP_SEARCH_FILTER": - settings.AUTH_LDAP_USER_SEARCH_UNION = [ - LDAPSearch(USER_SEARCH, ldap.SCOPE_SUBTREE, settings.AUTH_LDAP_SEARCH_FILTER) - for USER_SEARCH in str(settings.AUTH_LDAP_SEARCH_OU).split("|") - ] - settings.AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*settings.AUTH_LDAP_USER_SEARCH_UNION) + old_setting = settings.AUTHENTICATION_BACKENDS + old_setting.remove(settings.AUTH_LDAP_BACKEND) + settings.AUTHENTICATION_BACKENDS = old_setting class Meta: db_table = "settings" - - -common_settings = Setting() diff --git a/apps/common/signals.py b/apps/common/signals.py index 6edf140e2..de8a84139 100644 --- a/apps/common/signals.py +++ b/apps/common/signals.py @@ -4,4 +4,3 @@ from django.dispatch import Signal django_ready = Signal() -ldap_auth_enable = Signal(providing_args=["enabled"]) diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py index a46dcfd0b..92d4bce97 100644 --- a/apps/common/signals_handler.py +++ b/apps/common/signals_handler.py @@ -2,13 +2,14 @@ # from django.dispatch import receiver from django.db.models.signals import post_save, pre_save -from django.conf import settings +from django.conf import LazySettings, empty from django.db.utils import ProgrammingError, OperationalError +from django.core.cache import cache from jumpserver.utils import current_request from .models import Setting from .utils import get_logger -from .signals import django_ready, ldap_auth_enable +from .signals import django_ready logger = get_logger(__file__) @@ -25,25 +26,43 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): def refresh_all_settings_on_django_ready(sender, **kwargs): logger.debug("Receive django ready signal") logger.debug(" - fresh all settings") + CACHE_KEY_PREFIX = '_SETTING_' + + def monkey_patch_getattr(self, name): + key = CACHE_KEY_PREFIX + name + cached = cache.get(key) + if cached is not None: + return cached + if self._wrapped is empty: + self._setup(name) + val = getattr(self._wrapped, name) + # self.__dict__[name] = val # Never set it + return val + + def monkey_patch_setattr(self, name, value): + key = CACHE_KEY_PREFIX + name + cache.set(key, value, None) + if name == '_wrapped': + self.__dict__.clear() + else: + self.__dict__.pop(name, None) + super(LazySettings, self).__setattr__(name, value) + + def monkey_patch_delattr(self, name): + super(LazySettings, self).__delattr__(name) + self.__dict__.pop(name, None) + key = CACHE_KEY_PREFIX + name + cache.delete(key) + try: + LazySettings.__getattr__ = monkey_patch_getattr + LazySettings.__setattr__ = monkey_patch_setattr + LazySettings.__delattr__ = monkey_patch_delattr Setting.refresh_all_settings() except (ProgrammingError, OperationalError): pass -@receiver(ldap_auth_enable, dispatch_uid="my_unique_identifier") -def ldap_auth_on_changed(sender, enabled=True, **kwargs): - if enabled: - logger.debug("Enable LDAP auth") - if settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS: - settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND) - - else: - logger.debug("Disable LDAP auth") - if settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS: - settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND) - - @receiver(pre_save, dispatch_uid="my_unique_identifier") def on_create_set_created_by(sender, instance=None, **kwargs): if hasattr(instance, 'created_by') and not instance.created_by: diff --git a/apps/common/tasks.py b/apps/common/tasks.py index 00420bc8b..5aefd28ee 100644 --- a/apps/common/tasks.py +++ b/apps/common/tasks.py @@ -3,7 +3,6 @@ from django.conf import settings from celery import shared_task from .utils import get_logger from .models import Setting -from common.models import common_settings logger = get_logger(__file__) @@ -23,13 +22,9 @@ def send_mail_async(*args, **kwargs): Example: send_mail_sync.delay(subject, message, recipient_list, fail_silently=False, html_message=None) """ - configs = Setting.objects.filter(name__startswith='EMAIL') - for config in configs: - setattr(settings, config.name, config.cleaned_value) - if len(args) == 3: args = list(args) - args[0] = common_settings.EMAIL_SUBJECT_PREFIX + args[0] + args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0] args.insert(2, settings.EMAIL_HOST_USER) args = tuple(args) diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index 3c86a3a2a..a4ae8f9f1 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -13,5 +13,5 @@ urlpatterns = [ path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'), path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'), path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'), - # path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'), + path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'), ] diff --git a/apps/common/utils.py b/apps/common/utils.py index 74baec06b..13985d300 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -37,8 +37,7 @@ def reverse(view_name, urlconf=None, args=None, kwargs=None, kwargs=kwargs, current_app=current_app) if external: - from common.models import common_settings - site_url = common_settings.SITE_URL + site_url = settings.SITE_URL url = site_url.strip('/') + url return url @@ -390,17 +389,15 @@ def get_request_ip(request): def get_command_storage_setting(): - from common.models import common_settings - default = settings.TERMINAL_COMMAND_STORAGE - value = common_settings.TERMINAL_COMMAND_STORAGE + default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE + value = settings.TERMINAL_COMMAND_STORAGE value.update(default) return value def get_replay_storage_setting(): - from common.models import common_settings - default = settings.TERMINAL_REPLAY_STORAGE - value = common_settings.TERMINAL_REPLAY_STORAGE + default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE + value = settings.TERMINAL_REPLAY_STORAGE value.update(default) return value diff --git a/apps/common/views.py b/apps/common/views.py index 80f4c3b2b..524b6cbd4 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -6,7 +6,6 @@ from django.utils.translation import ugettext as _ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ TerminalSettingForm, SecuritySettingForm from common.permissions import SuperUserRequiredMixin -from .signals import ldap_auth_enable from . import utils @@ -79,8 +78,6 @@ class LDAPSettingView(SuperUserRequiredMixin, TemplateView): form = self.form_class(request.POST) if form.is_valid(): form.save() - if "AUTH_LDAP" in form.cleaned_data: - ldap_auth_enable.send(sender=self.__class__, enabled=form.cleaned_data["AUTH_LDAP"]) msg = _("Update setting successfully, please restart program") messages.success(request, msg) return redirect('settings:ldap-setting') diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py new file mode 100644 index 000000000..aceb3c841 --- /dev/null +++ b/apps/jumpserver/conf.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +import os +import sys +import types +import errno +import json +import yaml +from importlib import import_module + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PROJECT_DIR = os.path.dirname(BASE_DIR) + + +def import_string(dotted_path): + try: + module_path, class_name = dotted_path.rsplit('.', 1) + except ValueError as err: + raise ImportError("%s doesn't look like a module path" % dotted_path) from err + + module = import_module(module_path) + + try: + return getattr(module, class_name) + except AttributeError as err: + raise ImportError('Module "%s" does not define a "%s" attribute/class' % ( + module_path, class_name) + ) from err + + +class Config(dict): + """Works exactly like a dict but provides ways to fill it from files + or special dictionaries. There are two common patterns to populate the + config. + + Either you can fill the config from a config file:: + + app.config.from_pyfile('yourconfig.cfg') + + Or alternatively you can define the configuration options in the + module that calls :meth:`from_object` or provide an import path to + a module that should be loaded. It is also possible to tell it to + use the same module and with that provide the configuration values + just before the call:: + + DEBUG = True + SECRET_KEY = 'development key' + app.config.from_object(__name__) + + In both cases (loading from any Python file or loading from modules), + only uppercase keys are added to the config. This makes it possible to use + lowercase values in the config file for temporary values that are not added + to the config or to define the config keys in the same file that implements + the application. + + Probably the most interesting way to load configurations is from an + environment variable pointing to a file:: + + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + + In this case before launching the application you have to set this + environment variable to the file you want to use. On Linux and OS X + use the export statement:: + + export YOURAPPLICATION_SETTINGS='/path/to/config/file' + + On windows use `set` instead. + + :param root_path: path to which files are read relative from. When the + config object is created by the application, this is + the application's :attr:`~flask.Flask.root_path`. + :param defaults: an optional dictionary of default values + """ + + def __init__(self, root_path=None, defaults=None): + self.defaults = defaults or {} + self.root_path = root_path + super().__init__({}) + + def from_envvar(self, variable_name, silent=False): + """Loads a configuration from an environment variable pointing to + a configuration file. This is basically just a shortcut with nicer + error messages for this line of code:: + + app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) + + :param variable_name: name of the environment variable + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: bool. ``True`` if able to load config, ``False`` otherwise. + """ + rv = os.environ.get(variable_name) + if not rv: + if silent: + return False + raise RuntimeError('The environment variable %r is not set ' + 'and as such configuration could not be ' + 'loaded. Set this variable and make it ' + 'point to a configuration file' % + variable_name) + return self.from_pyfile(rv, silent=silent) + + def from_pyfile(self, filename, silent=False): + """Updates the values in the config from a Python file. This function + behaves as if the file was imported as module with the + :meth:`from_object` function. + + :param filename: the filename of the config. This can either be an + absolute filename or a filename relative to the + root path. + :param silent: set to ``True`` if you want silent failure for missing + files. + + .. versionadded:: 0.7 + `silent` parameter. + """ + if self.root_path: + filename = os.path.join(self.root_path, filename) + d = types.ModuleType('config') + d.__file__ = filename + try: + with open(filename, mode='rb') as config_file: + exec(compile(config_file.read(), filename, 'exec'), d.__dict__) + except IOError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR): + return False + e.strerror = 'Unable to load configuration file (%s)' % e.strerror + raise + self.from_object(d) + return True + + def from_object(self, obj): + """Updates the values from the given object. An object can be of one + of the following two types: + + - a string: in this case the object with that name will be imported + - an actual object reference: that object is used directly + + Objects are usually either modules or classes. :meth:`from_object` + loads only the uppercase attributes of the module/class. A ``dict`` + object will not work with :meth:`from_object` because the keys of a + ``dict`` are not attributes of the ``dict`` class. + + Example of module-based configuration:: + + app.config.from_object('yourapplication.default_config') + from yourapplication import default_config + app.config.from_object(default_config) + + You should not use this function to load the actual configuration but + rather configuration defaults. The actual config should be loaded + with :meth:`from_pyfile` and ideally from a location not within the + package because the package might be installed system wide. + + See :ref:`config-dev-prod` for an example of class-based configuration + using :meth:`from_object`. + + :param obj: an import name or object + """ + if isinstance(obj, str): + obj = import_string(obj) + for key in dir(obj): + if key.isupper(): + self[key] = getattr(obj, key) + + def from_json(self, filename, silent=False): + """Updates the values in the config from a JSON file. This function + behaves as if the JSON object was a dictionary and passed to the + :meth:`from_mapping` function. + + :param filename: the filename of the JSON file. This can either be an + absolute filename or a filename relative to the + root path. + :param silent: set to ``True`` if you want silent failure for missing + files. + + .. versionadded:: 0.11 + """ + if self.root_path: + filename = os.path.join(self.root_path, filename) + try: + with open(filename) as json_file: + obj = json.loads(json_file.read()) + except IOError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR): + return False + e.strerror = 'Unable to load configuration file (%s)' % e.strerror + raise + return self.from_mapping(obj) + + def from_yaml(self, filename, silent=False): + if self.root_path: + filename = os.path.join(self.root_path, filename) + try: + with open(filename) as json_file: + obj = yaml.load(json_file) + except IOError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR): + return False + e.strerror = 'Unable to load configuration file (%s)' % e.strerror + raise + return self.from_mapping(obj) + + def from_mapping(self, *mapping, **kwargs): + """Updates the config like :meth:`update` ignoring items with non-upper + keys. + + .. versionadded:: 0.11 + """ + mappings = [] + if len(mapping) == 1: + if hasattr(mapping[0], 'items'): + mappings.append(mapping[0].items()) + else: + mappings.append(mapping[0]) + elif len(mapping) > 1: + raise TypeError( + 'expected at most 1 positional argument, got %d' % len(mapping) + ) + mappings.append(kwargs.items()) + for mapping in mappings: + for (key, value) in mapping: + if key.isupper(): + self[key] = value + return True + + def get_namespace(self, namespace, lowercase=True, trim_namespace=True): + """Returns a dictionary containing a subset of configuration options + that match the specified namespace/prefix. Example usage:: + + app.config['IMAGE_STORE_TYPE'] = 'fs' + app.config['IMAGE_STORE_PATH'] = '/var/app/images' + app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com' + image_store_config = app.config.get_namespace('IMAGE_STORE_') + + The resulting dictionary `image_store_config` would look like:: + + { + 'types': 'fs', + 'path': '/var/app/images', + 'base_url': 'http://img.website.com' + } + + This is often useful when configuration options map directly to + keyword arguments in functions or class constructors. + + :param namespace: a configuration namespace + :param lowercase: a flag indicating if the keys of the resulting + dictionary should be lowercase + :param trim_namespace: a flag indicating if the keys of the resulting + dictionary should not include the namespace + + .. versionadded:: 0.11 + """ + rv = {} + for k, v in self.items(): + if not k.startswith(namespace): + continue + if trim_namespace: + key = k[len(namespace):] + else: + key = k + if lowercase: + key = key.lower() + rv[key] = v + return rv + + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self)) + + def __getitem__(self, item): + try: + value = super().__getitem__(item) + except KeyError: + value = None + if value is not None: + return value + value = os.environ.get(item, None) + if value is not None: + return value + return self.defaults.get(item) + + def __getattr__(self, item): + return self.__getitem__(item) + + +defaults = { + 'SECRET_KEY': '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x', + 'BOOTSTRAP_TOKEN': 'PleaseChangeMe', + 'DEBUG': True, + 'SITE_URL': 'http://localhost', + 'LOG_LEVEL': 'DEBUG', + 'LOG_DIR': os.path.join(PROJECT_DIR, 'logs'), + 'DB_ENGINE': 'mysql', + 'DB_NAME': 'jumpserver', + 'DB_HOST': '127.0.0.1', + 'DB_PORT': 3306, + 'DB_USER': 'root', + 'DB_PASSWORD': '', + 'REDIS_HOST': '127.0.0.1', + 'REDIS_PORT': 6379, + 'REDIS_PASSWORD': '', + 'REDIS_DB_CELERY_BROKER': 3, + 'REDIS_DB_CACHE': 4, + 'CAPTCHA_TEST_MODE': None, + 'TOKEN_EXPIRATION': 3600, + 'DISPLAY_PER_PAGE': 25, + 'DEFAULT_EXPIRED_YEARS': 70, + 'SESSION_COOKIE_DOMAIN': None, + 'CSRF_COOKIE_DOMAIN': None, + 'SESSION_COOKIE_AGE': 3600 * 24, + 'AUTH_OPENID': False, +} + + +def load_user_config(): + sys.path.insert(0, PROJECT_DIR) + config = Config(PROJECT_DIR, defaults) + try: + from config import config as c + config.from_object(c) + except ImportError: + msg = """ + + Error: No config file found. + + You can run `cp config_example.py config.py`, and edit it. + """ + raise ImportError(msg) + return config diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 19419b769..7433435e1 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -17,24 +17,12 @@ import ldap from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion from django.urls import reverse_lazy +from .conf import load_user_config + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) - -sys.path.append(PROJECT_DIR) - -# Import project config setting -try: - from config import config as CONFIG -except ImportError: - msg = """ - - Error: No config file found. - - You can run `cp config_example.py config.py`, and edit it. - """ - raise ImportError(msg) - # CONFIG = type('_', (), {'__getattr__': lambda arg1, arg2: None})() +CONFIG = load_user_config() # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ @@ -43,15 +31,15 @@ except ImportError: SECRET_KEY = CONFIG.SECRET_KEY # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = CONFIG.DEBUG or False +DEBUG = CONFIG.DEBUG # Absolute url for some case, for example email link -SITE_URL = CONFIG.SITE_URL or 'http://localhost' +SITE_URL = CONFIG.SITE_URL # LOG LEVEL -LOG_LEVEL = 'DEBUG' if DEBUG else CONFIG.LOG_LEVEL or 'WARNING' +LOG_LEVEL = CONFIG.LOG_LEVEL -ALLOWED_HOSTS = CONFIG.ALLOWED_HOSTS or [] +ALLOWED_HOSTS = ['*'] # Application definition @@ -152,9 +140,9 @@ TEMPLATES = [ LOGIN_REDIRECT_URL = reverse_lazy('index') LOGIN_URL = reverse_lazy('users:login') -SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN or None -CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN or None -SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE or 3600 * 24 +SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN +CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN +SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' # Database @@ -317,13 +305,13 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'data', 'media').replace('\\', '/') + '/' FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ] # Email config -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_USE_SSL = CONFIG.EMAIL_USE_SSL -EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS -EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX +EMAIL_HOST = 'smtp.jumpserver.org' +EMAIL_PORT = 25 +EMAIL_HOST_USER = 'noreply@jumpserver.org' +EMAIL_HOST_PASSWORD = '' +EMAIL_USE_SSL = False +EMAIL_USE_TLS = False +EMAIL_SUBJECT_PREFIX = '[Jumpserver] ' REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, @@ -363,23 +351,23 @@ FILE_UPLOAD_PERMISSIONS = 0o644 FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755 # Auth LDAP settings -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_SEARCH_UNION = [ +AUTH_LDAP = False +AUTH_LDAP_SERVER_URI = 'ldap://localhost:389' +AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org' +AUTH_LDAP_BIND_PASSWORD = '' +AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org' +AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)' +AUTH_LDAP_START_TLS = False +AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"} +AUTH_LDAP_USER_SEARCH_UNION = lambda: [ LDAPSearch(USER_SEARCH, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER) for USER_SEARCH in str(AUTH_LDAP_SEARCH_OU).split("|") ] -AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*AUTH_LDAP_USER_SEARCH_UNION) +AUTH_LDAP_USER_SEARCH = lambda: LDAPSearchUnion(*AUTH_LDAP_USER_SEARCH_UNION()) AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER + AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER ) AUTH_LDAP_CONNECTION_OPTIONS = { ldap.OPT_TIMEOUT: 5 @@ -414,7 +402,7 @@ CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % { 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', 'host': CONFIG.REDIS_HOST or '127.0.0.1', 'port': CONFIG.REDIS_PORT or 6379, - 'db':CONFIG.REDIS_DB_CELERY_BROKER or 3, + 'db': CONFIG.REDIS_DB_CELERY_BROKER or 3, } CELERY_TASK_SERIALIZER = 'pickle' CELERY_RESULT_SERIALIZER = 'pickle' @@ -436,10 +424,10 @@ CACHES = { 'default': { 'BACKEND': 'redis_cache.RedisCache', 'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % { - 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', - 'host': CONFIG.REDIS_HOST or '127.0.0.1', - 'port': CONFIG.REDIS_PORT or 6379, - 'db': CONFIG.REDIS_DB_CACHE or 4, + 'password': CONFIG.REDIS_PASSWORD, + 'host': CONFIG.REDIS_HOST, + 'port': CONFIG.REDIS_PORT, + 'db': CONFIG.REDIS_DB_CACHE, } } } @@ -454,27 +442,44 @@ COMMAND_STORAGE = { 'ENGINE': 'terminal.backends.command.db', } -TERMINAL_COMMAND_STORAGE = { +DEFAULT_TERMINAL_COMMAND_STORAGE = { "default": { "TYPE": "server", }, +} + +TERMINAL_COMMAND_STORAGE = { # 'ali-es': { # 'TYPE': 'elasticsearch', # 'HOSTS': ['http://elastic:changeme@localhost:9200'], # }, } -TERMINAL_REPLAY_STORAGE = { +DEFAULT_TERMINAL_REPLAY_STORAGE = { "default": { "TYPE": "server", }, } +TERMINAL_REPLAY_STORAGE = { +} -SECURITY_PASSWORD_MIN_LENGTH = 6 +SECURITY_MFA_AUTH = False SECURITY_LOGIN_LIMIT_COUNT = 7 SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute SECURITY_MAX_IDLE_TIME = 30 # Unit: minute +SECURITY_PASSWORD_MIN_LENGTH = 6 +SECURITY_PASSWORD_UPPER_CASE = False +SECURITY_PASSWORD_LOWER_CASE = False +SECURITY_PASSWORD_NUMBER = False +SECURITY_PASSWORD_SPECIAL_CHAR = False +SECURITY_PASSWORD_RULES = [ + 'SECURITY_PASSWORD_MIN_LENGTH', + 'SECURITY_PASSWORD_UPPER_CASE', + 'SECURITY_PASSWORD_LOWER_CASE', + 'SECURITY_PASSWORD_NUMBER', + 'SECURITY_PASSWORD_SPECIAL_CHAR' +] # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html BOOTSTRAP3 = { @@ -486,8 +491,8 @@ BOOTSTRAP3 = { 'success_css_class': '', } -TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600 -DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25 +TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION +DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE DEFAULT_EXPIRED_YEARS = 70 USER_GUIDE_URL = "" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index bb3a0102676cdcc8f6fff601e97337378be1ec37..0f6f072a5c3e256927a0ce2064434c6d6ecf0a7e 100644 GIT binary patch delta 17145 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 ZC5q=PUbbAZ(n(vVAC5>GxukiS{{#BP>&XBB delta 17192 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 361e05589..c5ec299d2 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: 2018-11-08 19:18+0800\n" +"POT-Creation-Date: 2018-11-21 19:06+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -126,7 +126,7 @@ msgstr "端口" #: perms/templates/perms/asset_permission_create_update.html:40 #: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:148 -#: terminal/backends/command/models.py:13 terminal/models.py:133 +#: terminal/backends/command/models.py:13 terminal/models.py:134 #: terminal/templates/terminal/command_list.html:40 #: terminal/templates/terminal/command_list.html:73 #: terminal/templates/terminal/session_list.html:41 @@ -154,7 +154,7 @@ msgstr "不能包含特殊字符" #: assets/templates/assets/domain_list.html:25 #: assets/templates/assets/label_list.html:14 #: assets/templates/assets/system_user_detail.html:58 -#: assets/templates/assets/system_user_list.html:29 common/models.py:30 +#: assets/templates/assets/system_user_list.html:29 common/models.py:31 #: common/templates/common/command_storage_create.html:41 #: common/templates/common/replay_storage_create.html:44 #: common/templates/common/terminal_setting.html:80 @@ -164,9 +164,9 @@ msgstr "不能包含特殊字符" #: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_list.html:53 #: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:18 -#: terminal/models.py:160 terminal/templates/terminal/terminal_detail.html:43 +#: terminal/models.py:161 terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 -#: users/models/user.py:51 users/templates/users/_select_user_modal.html:13 +#: users/models/user.py:52 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_detail.html:63 #: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_list.html:12 @@ -191,7 +191,7 @@ msgstr "名称" #: assets/templates/assets/system_user_list.html:30 #: audits/templates/audits/login_log_list.html:49 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15 -#: users/forms.py:33 users/models/authentication.py:72 users/models/user.py:49 +#: users/forms.py:33 users/models/authentication.py:72 users/models/user.py:50 #: users/templates/users/_select_user_modal.html:14 #: users/templates/users/login.html:62 #: users/templates/users/user_detail.html:67 @@ -204,7 +204,7 @@ msgstr "用户名" msgid "Password or private key passphrase" msgstr "密码或密钥密码" -#: assets/forms/user.py:26 assets/models/base.py:24 common/forms.py:107 +#: assets/forms/user.py:26 assets/models/base.py:24 common/forms.py:105 #: users/forms.py:17 users/forms.py:35 users/forms.py:47 #: users/templates/users/login.html:65 #: users/templates/users/reset_password.html:53 @@ -216,7 +216,7 @@ msgstr "密码或密钥密码" msgid "Password" msgstr "密码" -#: assets/forms/user.py:29 users/models/user.py:78 +#: assets/forms/user.py:29 users/models/user.py:79 msgid "Private key" msgstr "ssh私钥" @@ -265,7 +265,7 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写" #: assets/templates/assets/system_user_asset.html:51 #: assets/templates/assets/user_asset_list.html:46 #: assets/templates/assets/user_asset_list.html:162 -#: audits/templates/audits/login_log_list.html:52 common/forms.py:136 +#: audits/templates/audits/login_log_list.html:52 common/forms.py:134 #: perms/templates/perms/asset_permission_asset.html:55 #: users/templates/users/user_granted_asset.html:45 #: users/templates/users/user_group_granted_asset.html:45 @@ -278,7 +278,7 @@ msgstr "IP" #: assets/templates/assets/asset_list.html:92 #: assets/templates/assets/system_user_asset.html:50 #: assets/templates/assets/user_asset_list.html:45 -#: assets/templates/assets/user_asset_list.html:161 common/forms.py:135 +#: assets/templates/assets/user_asset_list.html:161 common/forms.py:133 #: perms/templates/perms/asset_permission_asset.html:54 #: users/templates/users/user_granted_asset.html:44 #: users/templates/users/user_group_granted_asset.html:44 @@ -388,7 +388,7 @@ msgstr "标签管理" #: assets/templates/assets/system_user_detail.html:100 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:37 #: perms/models.py:84 perms/templates/perms/asset_permission_detail.html:98 -#: users/models/user.py:92 users/templates/users/user_detail.html:111 +#: users/models/user.py:93 users/templates/users/user_detail.html:111 #: xpack/plugins/cloud/models.py:46 xpack/plugins/cloud/models.py:140 msgid "Created by" msgstr "创建者" @@ -426,11 +426,11 @@ msgstr "创建日期" #: assets/templates/assets/domain_list.html:28 #: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_list.html:37 -#: assets/templates/assets/user_asset_list.html:170 common/models.py:35 +#: assets/templates/assets/user_asset_list.html:170 common/models.py:36 #: ops/models/adhoc.py:43 orgs/models.py:17 perms/models.py:39 #: perms/models.py:86 perms/templates/perms/asset_permission_detail.html:102 #: terminal/models.py:28 terminal/templates/terminal/terminal_detail.html:63 -#: users/models/group.py:15 users/models/user.py:84 +#: users/models/group.py:15 users/models/user.py:85 #: users/templates/users/user_detail.html:123 #: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_list.html:14 @@ -461,7 +461,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:70 +#: assets/models/cluster.py:22 users/models/user.py:71 #: users/templates/users/user_detail.html:76 msgid "Phone" msgstr "手机" @@ -487,7 +487,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:363 +#: users/models/user.py:364 msgid "System" msgstr "系统" @@ -515,7 +515,7 @@ msgstr "BGP全网通" msgid "Regex" msgstr "正则表达式" -#: assets/models/cmd_filter.py:35 terminal/models.py:139 +#: assets/models/cmd_filter.py:35 terminal/models.py:140 #: terminal/templates/terminal/command_list.html:55 #: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/session_detail.html:48 @@ -616,14 +616,14 @@ msgstr "默认资产组" #: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:142 templates/index.html:87 -#: terminal/backends/command/models.py:12 terminal/models.py:132 +#: terminal/backends/command/models.py:12 terminal/models.py:133 #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:312 -#: users/models/user.py:33 users/models/user.py:351 +#: users/models/user.py:32 users/models/user.py:352 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:13 users/views/user.py:385 +#: users/templates/users/user_group_list.html:13 users/views/user.py:384 #: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 @@ -631,7 +631,7 @@ msgid "User" msgstr "用户" #: assets/models/label.py:19 assets/models/node.py:20 -#: assets/templates/assets/label_list.html:15 common/models.py:31 +#: assets/templates/assets/label_list.html:15 common/models.py:32 msgid "Value" msgstr "值" @@ -699,7 +699,7 @@ msgstr "登录模式" #: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_list.html:58 #: perms/templates/perms/asset_permission_list.html:154 templates/_nav.html:25 -#: terminal/backends/command/models.py:14 terminal/models.py:134 +#: terminal/backends/command/models.py:14 terminal/models.py:135 #: terminal/templates/terminal/command_list.html:48 #: terminal/templates/terminal/command_list.html:74 #: terminal/templates/terminal/session_list.html:49 @@ -1060,7 +1060,7 @@ msgstr "选择节点" #: users/templates/users/user_detail.html:476 #: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_list.html:88 -#: users/templates/users/user_list.html:201 +#: users/templates/users/user_list.html:205 #: users/templates/users/user_profile.html:232 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 @@ -1277,7 +1277,7 @@ msgstr "重命名失败,不能更改root节点的名称" #: users/templates/users/user_detail.html:402 #: users/templates/users/user_detail.html:470 #: users/templates/users/user_group_list.html:82 -#: users/templates/users/user_list.html:195 +#: users/templates/users/user_list.html:199 msgid "Are you sure?" msgstr "你确认吗?" @@ -1293,7 +1293,7 @@ msgstr "删除选择资产" #: users/templates/users/user_detail.html:474 #: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_list.html:86 -#: users/templates/users/user_list.html:199 +#: users/templates/users/user_list.html:203 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 msgid "Cancel" msgstr "取消" @@ -1624,7 +1624,7 @@ msgstr "系统用户集群资产" #: audits/templates/audits/ftp_log_list.html:73 #: audits/templates/audits/operate_log_list.html:70 #: audits/templates/audits/password_change_log_list.html:52 -#: terminal/models.py:136 terminal/templates/terminal/session_list.html:74 +#: terminal/models.py:137 terminal/templates/terminal/session_list.html:74 #: terminal/templates/terminal/terminal_detail.html:47 msgid "Remote addr" msgstr "远端地址" @@ -1665,7 +1665,7 @@ msgstr "修改者" #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/task_history.html:58 perms/models.py:35 -#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:143 +#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:144 #: terminal/templates/terminal/session_list.html:78 msgid "Date start" msgstr "开始日期" @@ -1707,7 +1707,7 @@ msgid "City" msgstr "城市" #: audits/templates/audits/login_log_list.html:54 users/forms.py:169 -#: users/models/authentication.py:77 users/models/user.py:73 +#: users/models/authentication.py:77 users/models/user.py:74 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" @@ -1758,9 +1758,9 @@ msgstr "改密日志" #: audits/views.py:187 templates/_nav.html:10 users/views/group.py:28 #: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76 -#: users/views/group.py:92 users/views/login.py:334 users/views/user.py:68 -#: users/views/user.py:83 users/views/user.py:111 users/views/user.py:193 -#: users/views/user.py:354 users/views/user.py:404 users/views/user.py:439 +#: users/views/group.py:92 users/views/login.py:332 users/views/user.py:68 +#: users/views/user.py:83 users/views/user.py:111 users/views/user.py:192 +#: users/views/user.py:353 users/views/user.py:403 users/views/user.py:437 msgid "Users" msgstr "用户管理" @@ -1819,88 +1819,88 @@ msgstr "不是字符类型" msgid "Encrypt field using Secret Key" msgstr "" -#: common/forms.py:64 +#: common/forms.py:63 msgid "Current SITE URL" msgstr "当前站点URL" -#: common/forms.py:68 +#: common/forms.py:67 msgid "User Guide URL" msgstr "用户向导URL" -#: common/forms.py:69 +#: common/forms.py:68 msgid "User first login update profile done redirect to it" msgstr "用户第一次登录,修改profile后重定向到地址" -#: common/forms.py:72 +#: common/forms.py:71 msgid "Email Subject Prefix" msgstr "Email主题前缀" -#: common/forms.py:79 +#: common/forms.py:77 msgid "SMTP host" msgstr "SMTP主机" -#: common/forms.py:81 +#: common/forms.py:79 msgid "SMTP port" msgstr "SMTP端口" -#: common/forms.py:83 +#: common/forms.py:81 msgid "SMTP user" msgstr "SMTP账号" -#: common/forms.py:86 +#: common/forms.py:84 msgid "SMTP password" msgstr "SMTP密码" -#: common/forms.py:87 +#: common/forms.py:85 msgid "Some provider use token except password" msgstr "一些邮件提供商需要输入的是Token" -#: common/forms.py:90 common/forms.py:128 +#: common/forms.py:88 common/forms.py:126 msgid "Use SSL" msgstr "使用SSL" -#: common/forms.py:91 +#: common/forms.py:89 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用SSL" -#: common/forms.py:94 +#: common/forms.py:92 msgid "Use TLS" msgstr "使用TLS" -#: common/forms.py:95 +#: common/forms.py:93 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用TLS" -#: common/forms.py:101 +#: common/forms.py:99 msgid "LDAP server" msgstr "LDAP地址" -#: common/forms.py:104 +#: common/forms.py:102 msgid "Bind DN" msgstr "绑定DN" -#: common/forms.py:111 +#: common/forms.py:109 msgid "User OU" msgstr "用户OU" -#: common/forms.py:112 +#: common/forms.py:110 msgid "Use | split User OUs" msgstr "使用|分隔各OU" -#: common/forms.py:115 +#: common/forms.py:113 msgid "User search filter" msgstr "用户过滤器" -#: common/forms.py:116 +#: common/forms.py:114 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" -#: common/forms.py:119 +#: common/forms.py:117 msgid "User attr map" msgstr "LDAP属性映射" -#: common/forms.py:121 +#: common/forms.py:119 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -1908,103 +1908,103 @@ msgstr "" "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的属性" -#: common/forms.py:130 +#: common/forms.py:128 msgid "Enable LDAP auth" msgstr "启用LDAP认证" -#: common/forms.py:139 +#: common/forms.py:137 msgid "Password auth" msgstr "密码认证" -#: common/forms.py:142 +#: common/forms.py:140 msgid "Public key auth" msgstr "密钥认证" -#: common/forms.py:145 +#: common/forms.py:143 msgid "Heartbeat interval" msgstr "心跳间隔" -#: common/forms.py:145 ops/models/adhoc.py:38 +#: common/forms.py:143 ops/models/adhoc.py:38 msgid "Units: seconds" msgstr "单位: 秒" -#: common/forms.py:148 +#: common/forms.py:146 msgid "List sort by" msgstr "资产列表排序" -#: common/forms.py:160 +#: common/forms.py:158 msgid "MFA Secondary certification" msgstr "MFA 二次认证" -#: common/forms.py:162 +#: common/forms.py:160 msgid "" "After opening, the user login must use MFA secondary authentication (valid " "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: common/forms.py:169 +#: common/forms.py:167 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: common/forms.py:174 +#: common/forms.py:172 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: common/forms.py:176 +#: common/forms.py:174 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 "" "提示: (单位: 分钟) 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录." -#: common/forms.py:182 +#: common/forms.py:180 msgid "Connection max idle time" msgstr "SSH最大空闲时间" -#: common/forms.py:184 +#: common/forms.py:182 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" msgstr "提示: (单位: 分钟) 如果超过该配置没有操作,连接会被断开(仅ssh) " -#: common/forms.py:190 +#: common/forms.py:188 msgid "Password minimum length" msgstr "密码最小长度 " -#: common/forms.py:196 +#: common/forms.py:194 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: common/forms.py:198 +#: common/forms.py:196 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: common/forms.py:204 +#: common/forms.py:202 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: common/forms.py:205 +#: common/forms.py:203 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: common/forms.py:211 +#: common/forms.py:209 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: common/forms.py:212 +#: common/forms.py:210 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: common/forms.py:218 +#: common/forms.py:216 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: common/forms.py:219 +#: common/forms.py:217 msgid "" "After opening, the user password changes and resets must contain special " "characters" @@ -2018,7 +2018,7 @@ msgstr "" msgid "discard time" msgstr "" -#: common/models.py:34 users/models/authentication.py:51 +#: common/models.py:35 users/models/authentication.py:51 #: users/templates/users/user_detail.html:96 msgid "Enabled" msgstr "启用" @@ -2028,7 +2028,7 @@ msgstr "启用" #: common/templates/common/ldap_setting.html:15 #: common/templates/common/security_setting.html:15 #: common/templates/common/terminal_setting.html:16 -#: common/templates/common/terminal_setting.html:46 common/views.py:22 +#: common/templates/common/terminal_setting.html:46 common/views.py:19 msgid "Basic setting" msgstr "基本设置" @@ -2036,7 +2036,7 @@ msgstr "基本设置" #: common/templates/common/email_setting.html:18 #: common/templates/common/ldap_setting.html:18 #: common/templates/common/security_setting.html:18 -#: common/templates/common/terminal_setting.html:20 common/views.py:48 +#: common/templates/common/terminal_setting.html:20 common/views.py:45 msgid "Email setting" msgstr "邮件设置" @@ -2044,7 +2044,7 @@ msgstr "邮件设置" #: common/templates/common/email_setting.html:21 #: common/templates/common/ldap_setting.html:21 #: common/templates/common/security_setting.html:21 -#: common/templates/common/terminal_setting.html:24 common/views.py:74 +#: common/templates/common/terminal_setting.html:24 common/views.py:71 msgid "LDAP setting" msgstr "LDAP设置" @@ -2052,7 +2052,7 @@ msgstr "LDAP设置" #: common/templates/common/email_setting.html:24 #: common/templates/common/ldap_setting.html:24 #: common/templates/common/security_setting.html:24 -#: common/templates/common/terminal_setting.html:28 common/views.py:105 +#: common/templates/common/terminal_setting.html:28 common/views.py:100 msgid "Terminal setting" msgstr "终端设置" @@ -2060,7 +2060,7 @@ msgstr "终端设置" #: common/templates/common/email_setting.html:27 #: common/templates/common/ldap_setting.html:27 #: common/templates/common/security_setting.html:27 -#: common/templates/common/terminal_setting.html:31 common/views.py:157 +#: common/templates/common/terminal_setting.html:31 common/views.py:152 msgid "Security setting" msgstr "安全设置" @@ -2168,22 +2168,22 @@ msgstr "您确定删除吗?" msgid "Special char not allowed" msgstr "不能包含特殊字符" -#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:104 -#: common/views.py:131 common/views.py:143 common/views.py:156 +#: common/views.py:18 common/views.py:44 common/views.py:70 common/views.py:99 +#: common/views.py:126 common/views.py:138 common/views.py:151 #: templates/_nav.html:116 msgid "Settings" msgstr "系统设置" -#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:117 -#: common/views.py:167 +#: common/views.py:29 common/views.py:55 common/views.py:81 common/views.py:112 +#: common/views.py:162 msgid "Update setting successfully, please restart program" msgstr "更新设置成功, 请手动重启程序" -#: common/views.py:132 +#: common/views.py:127 msgid "Create replay storage" msgstr "创建录像存储" -#: common/views.py:144 +#: common/views.py:139 msgid "Create command storage" msgstr "创建命令存储" @@ -2445,7 +2445,7 @@ msgstr "组织管理" #: perms/forms.py:31 perms/models.py:30 perms/models.py:80 #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:14 -#: users/forms.py:282 users/models/group.py:26 users/models/user.py:57 +#: users/forms.py:282 users/models/group.py:26 users/models/user.py:58 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:207 #: users/templates/users/user_list.html:26 @@ -2463,7 +2463,7 @@ msgstr "资产和节点至少选一个" #: perms/models.py:36 perms/models.py:83 #: perms/templates/perms/asset_permission_detail.html:90 -#: users/models/user.py:89 users/templates/users/user_detail.html:107 +#: users/models/user.py:90 users/templates/users/user_detail.html:107 #: users/templates/users/user_profile.html:112 msgid "Date expired" msgstr "失效日期" @@ -2594,7 +2594,7 @@ msgstr "商业支持" #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:367 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:366 msgid "Profile" msgstr "个人信息" @@ -2906,43 +2906,43 @@ msgstr "SSH端口" msgid "HTTP Port" msgstr "HTTP端口" -#: terminal/models.py:104 +#: terminal/models.py:105 msgid "Session Online" msgstr "在线会话" -#: terminal/models.py:105 +#: terminal/models.py:106 msgid "CPU Usage" msgstr "CPU使用" -#: terminal/models.py:106 +#: terminal/models.py:107 msgid "Memory Used" msgstr "内存使用" -#: terminal/models.py:107 +#: terminal/models.py:108 msgid "Connections" msgstr "连接数" -#: terminal/models.py:108 +#: terminal/models.py:109 msgid "Threads" msgstr "线程数" -#: terminal/models.py:109 +#: terminal/models.py:110 msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:138 terminal/templates/terminal/session_list.html:104 +#: terminal/models.py:139 terminal/templates/terminal/session_list.html:104 msgid "Replay" msgstr "回放" -#: terminal/models.py:142 +#: terminal/models.py:143 msgid "Date last active" msgstr "最后活跃日期" -#: terminal/models.py:144 +#: terminal/models.py:145 msgid "Date end" msgstr "结束日期" -#: terminal/models.py:161 +#: terminal/models.py:162 msgid "Args" msgstr "参数" @@ -3092,11 +3092,11 @@ msgstr "登录频繁, 稍后重试" msgid "Please carry seed value and conduct MFA secondary certification" msgstr "请携带seed值, 进行MFA二次认证" -#: users/api/auth.py:196 +#: users/api/auth.py:195 msgid "Please verify the user name and password first" msgstr "请先进行用户名和密码验证" -#: users/api/auth.py:208 +#: users/api/auth.py:207 msgid "MFA certification failed" msgstr "MFA认证失败" @@ -3159,7 +3159,7 @@ msgstr "" msgid "MFA code" msgstr "MFA 验证码" -#: users/forms.py:52 users/models/user.py:61 +#: users/forms.py:52 users/models/user.py:62 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:25 @@ -3247,7 +3247,7 @@ msgstr "自动配置并下载SSH密钥" msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:258 users/models/user.py:81 +#: users/forms.py:258 users/models/user.py:82 #: users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 #: users/templates/users/user_profile.html:68 @@ -3310,49 +3310,49 @@ msgstr "Agent" msgid "Date login" msgstr "登录日期" -#: users/models/user.py:32 users/models/user.py:359 +#: users/models/user.py:31 users/models/user.py:360 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:34 +#: users/models/user.py:33 msgid "Application" msgstr "应用程序" -#: users/models/user.py:37 users/templates/users/user_profile.html:92 +#: users/models/user.py:36 users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 msgid "Disable" msgstr "禁用" -#: users/models/user.py:38 users/templates/users/user_profile.html:90 +#: users/models/user.py:37 users/templates/users/user_profile.html:90 #: users/templates/users/user_profile.html:170 msgid "Enable" msgstr "启用" -#: users/models/user.py:39 users/templates/users/user_profile.html:88 +#: users/models/user.py:38 users/templates/users/user_profile.html:88 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:53 users/templates/users/user_detail.html:71 +#: users/models/user.py:54 users/templates/users/user_detail.html:71 #: users/templates/users/user_profile.html:59 msgid "Email" msgstr "邮件" -#: users/models/user.py:64 +#: users/models/user.py:65 msgid "Avatar" msgstr "头像" -#: users/models/user.py:67 users/templates/users/user_detail.html:82 +#: users/models/user.py:68 users/templates/users/user_detail.html:82 msgid "Wechat" msgstr "微信" -#: users/models/user.py:96 users/templates/users/user_detail.html:103 +#: users/models/user.py:97 users/templates/users/user_detail.html:103 #: users/templates/users/user_list.html:27 #: users/templates/users/user_profile.html:100 msgid "Source" msgstr "用户来源" -#: users/models/user.py:362 +#: users/models/user.py:363 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -3636,7 +3636,7 @@ msgid "Reset link will be generated and sent to the user. " msgstr "生成重置密码连接,通过邮件发送给用户" #: users/templates/users/user_detail.html:19 -#: users/templates/users/user_granted_asset.html:18 users/views/user.py:194 +#: users/templates/users/user_granted_asset.html:18 users/views/user.py:193 msgid "User detail" msgstr "用户详情" @@ -3764,20 +3764,20 @@ msgstr "用户组删除" msgid "UserGroup Deleting failed." msgstr "用户组删除失败" -#: users/templates/users/user_list.html:196 +#: users/templates/users/user_list.html:200 msgid "This will delete the selected users !!!" msgstr "删除选中用户 !!!" -#: users/templates/users/user_list.html:205 +#: users/templates/users/user_list.html:209 msgid "User Deleted." msgstr "已被删除" -#: users/templates/users/user_list.html:206 -#: users/templates/users/user_list.html:211 +#: users/templates/users/user_list.html:210 +#: users/templates/users/user_list.html:215 msgid "User Delete" msgstr "删除" -#: users/templates/users/user_list.html:210 +#: users/templates/users/user_list.html:214 msgid "User Deleting failed." msgstr "用户删除失败" @@ -3826,8 +3826,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接 msgid "Administrator Settings force MFA login" msgstr "管理员设置强制使用MFA登录" -#: users/templates/users/user_profile.html:116 users/views/user.py:230 -#: users/views/user.py:284 +#: users/templates/users/user_profile.html:116 users/views/user.py:229 +#: users/views/user.py:283 msgid "User groups" msgstr "用户组" @@ -4003,22 +4003,18 @@ msgstr "" "
\n" " " -#: users/utils.py:162 +#: users/utils.py:148 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:164 +#: users/utils.py:150 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:177 +#: users/utils.py:163 msgid "Password or SSH public key invalid" msgstr "密码或密钥不合法" -#: users/utils.py:300 users/utils.py:310 -msgid "Bit" -msgstr " 位" - #: users/views/group.py:29 msgid "User group list" msgstr "用户组列表" @@ -4031,11 +4027,11 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:70 +#: users/views/login.py:69 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:180 users/views/user.py:526 users/views/user.py:551 +#: users/views/login.py:179 users/views/user.py:524 users/views/user.py:549 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -4068,67 +4064,67 @@ msgstr "重置密码成功" msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:277 users/views/login.py:290 +#: users/views/login.py:272 users/views/login.py:288 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:286 +#: users/views/login.py:284 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:296 users/views/user.py:127 users/views/user.py:422 +#: users/views/login.py:294 users/views/user.py:126 users/views/user.py:420 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:334 +#: users/views/login.py:332 msgid "First login" msgstr "首次登陆" -#: users/views/user.py:144 +#: users/views/user.py:143 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:174 +#: users/views/user.py:173 msgid "Bulk update user" msgstr "批量更新用户" -#: users/views/user.py:259 +#: users/views/user.py:258 msgid "Invalid file." msgstr "文件不合法" -#: users/views/user.py:355 +#: users/views/user.py:354 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:386 +#: users/views/user.py:385 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:405 +#: users/views/user.py:404 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:440 +#: users/views/user.py:438 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:481 +#: users/views/user.py:479 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:581 +#: users/views/user.py:579 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:582 +#: users/views/user.py:580 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:584 +#: users/views/user.py:582 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:585 +#: users/views/user.py:583 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" @@ -4439,6 +4435,9 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "Bit" +#~ msgstr " 位" + #, fuzzy #~| msgid "Delete succeed" #~ msgid "Delete success" diff --git a/apps/locale/zh/LC_MESSAGES/djangojs.mo b/apps/locale/zh/LC_MESSAGES/djangojs.mo index 35d79b28e67454bf12381c9dc0d730a6488182cd..189ddbc8eb356af1918545cfeb20150f6b06aa3b 100644 GIT binary patch delta 922 zcmZwFO=uHA6u|LW6WiMOQCq9EmAV(P6cpDAQblMFB8ZlXH}T+hU7Ce_vAdyyl<1)a zrKN%dKM-j^(u2_ka#GP=^dRCzLGa?Ko3)o-_29w(i;EcSBa`1u-tNqsnG`!pU4?I* z@uDL3()Q6-X$NQ(jW1$klTw2?iu*Bz$8io@@GZ9D2kgYpcoyrJL?fouW_*YVTtr!? zg8fPr)O$KI@%PBKxKeG52XG4-sN+f8ftT?DrceragB|z|$MFkx<3U!H0#4xwn<--- zSIGCmx62BqLDDD&m8 z7pHMM-p4`KS4(uHfKMnBf5jmTQC{rh7_Q*}%B{#V9G2QclaSp?$gbGr-`Gu0PDX;; zXoQ@o1T{CVW0ccs#Lh_BfmAI8wbSHIC8QAQX;j`NKM?YtQMHd+Rx6y(d%kXEGrs9$ zbj!>+zM0Zfw(r}nr<#(f?3C?Vre|-Q&g9d!>sY##G+opB|8*~CTMl#olON0KN7ANm zB^5o_wzBS3HEw#|6dgV7WSn$9&0?8JKdE0Io7N{B->Z$q9%~81ICRv|jo}lIeUV&{Sov0xNMmv#{94{$PqN))kAVhf*e5UT?sE(Yk}66QoYQfD&8!Uk^P9%|xy%;N)Y;xkU- zl>L`Dz*P=&jYZabRM93raSmOQXr7h64fJ?#;sWkqo%(Xw-_XMmKD?tI=mYhK9~?!G zHuWZo$dWWr_cu`sMyUJG@eFT}^9koR9n)$7!tj=6fBd+Bl}vHbO\n" "Language-Team: LANGUAGE \n" @@ -17,58 +17,58 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: static/js/jumpserver.js:158 +#: static/js/jumpserver.js:168 msgid "Update is successful!" msgstr "更新成功" -#: static/js/jumpserver.js:160 +#: static/js/jumpserver.js:170 msgid "An unknown error occurred while updating.." msgstr "更新时发生未知错误" -#: static/js/jumpserver.js:205 static/js/jumpserver.js:247 -#: static/js/jumpserver.js:252 +#: static/js/jumpserver.js:236 static/js/jumpserver.js:273 +#: static/js/jumpserver.js:276 msgid "Error" msgstr "错误" -#: static/js/jumpserver.js:205 +#: static/js/jumpserver.js:236 msgid "Being used by the asset, please unbind the asset first." msgstr "正在被资产使用中,请先解除资产绑定" -#: static/js/jumpserver.js:212 static/js/jumpserver.js:260 +#: static/js/jumpserver.js:242 static/js/jumpserver.js:283 msgid "Delete the success" msgstr "删除成功" -#: static/js/jumpserver.js:219 +#: static/js/jumpserver.js:248 msgid "Are you sure about deleting it?" msgstr "你确定删除吗 ?" -#: static/js/jumpserver.js:224 static/js/jumpserver.js:273 +#: static/js/jumpserver.js:252 static/js/jumpserver.js:293 msgid "Cancel" msgstr "取消" -#: static/js/jumpserver.js:227 static/js/jumpserver.js:276 +#: static/js/jumpserver.js:254 static/js/jumpserver.js:295 msgid "Confirm" msgstr "确认" -#: static/js/jumpserver.js:247 +#: static/js/jumpserver.js:273 msgid "" "The organization contains undeleted information. Please try again after " "deleting" msgstr "组织中包含未删除信息,请删除后重试" -#: static/js/jumpserver.js:252 +#: static/js/jumpserver.js:276 msgid "" "Do not perform this operation under this organization. Try again after " "switching to another organization" msgstr "请勿在此组织下执行此操作,切换到其他组织后重试" -#: static/js/jumpserver.js:267 +#: static/js/jumpserver.js:289 msgid "" "Please ensure that the following information in the organization has been " "deleted" msgstr "请确保组织内的以下信息已删除" -#: static/js/jumpserver.js:269 +#: static/js/jumpserver.js:290 msgid "" "User list、User group、Asset list、Domain list、Admin user、System user、" "Labels、Asset permission" @@ -76,32 +76,52 @@ msgstr "" "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权" "规则" -#: static/js/jumpserver.js:311 +#: static/js/jumpserver.js:329 msgid "Loading ..." msgstr "加载中 ..." -#: static/js/jumpserver.js:313 +#: static/js/jumpserver.js:330 msgid "Search" msgstr "搜索" -#: static/js/jumpserver.js:317 +#: static/js/jumpserver.js:333 #, javascript-format msgid "Selected item %d" msgstr "选中 %d 项" -#: static/js/jumpserver.js:322 +#: static/js/jumpserver.js:337 msgid "Per page _MENU_" msgstr "每页 _MENU_" -#: static/js/jumpserver.js:324 +#: static/js/jumpserver.js:338 msgid "" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" -#: static/js/jumpserver.js:328 +#: static/js/jumpserver.js:341 msgid "No match" msgstr "没有匹配项" -#: static/js/jumpserver.js:330 +#: static/js/jumpserver.js:342 msgid "No record" msgstr "没有记录" + +#: static/js/jumpserver.js:701 +msgid "Password minimum length {N} bits" +msgstr "密码最小长度 {N} 位" + +#: static/js/jumpserver.js:702 +msgid "Must contain capital letters" +msgstr "必须包含大写字母" + +#: static/js/jumpserver.js:703 +msgid "Must contain lowercase letters" +msgstr "必须包含小写字母" + +#: static/js/jumpserver.js:704 +msgid "Must contain numeric characters" +msgstr "必须包含数字字符" + +#: static/js/jumpserver.js:705 +msgid "Must contain special characters" +msgstr "必须包含特殊字符" diff --git a/apps/perms/views.py b/apps/perms/views.py index 7782992d5..6b0a0d5d3 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -101,7 +101,7 @@ class AssetPermissionUserView(AdminUserRequiredMixin, ListView): template_name = 'perms/asset_permission_user.html' context_object_name = 'asset_permission' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE object = None def get(self, request, *args, **kwargs): @@ -133,7 +133,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin, ListView): template_name = 'perms/asset_permission_asset.html' context_object_name = 'asset_permission' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE object = None def get(self, request, *args, **kwargs): diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index a54313e97..466267c33 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -688,41 +688,69 @@ function setUrlParam(url, name, value) { return url } +// Password check rules +var rules_short_map_id = { + 'min': 'id_security_password_min_length', + 'upper': 'id_security_password_upper_case', + 'lower': 'id_security_password_lower_case', + 'number': 'id_security_password_number', + 'special': 'id_security_password_special_char' +}; + +var rules_id_map_label = { + 'id_security_password_min_length': gettext('Password minimum length {N} bits'), + 'id_security_password_upper_case': gettext('Must contain capital letters'), + 'id_security_password_lower_case': gettext('Must contain lowercase letters'), + 'id_security_password_number': gettext('Must contain numeric characters'), + 'id_security_password_special_char': gettext('Must contain special characters') +}; + +function getRuleLabel(rule){ + var label = ''; + if (rule.key === rules_short_map_id['min']){ + label = rules_id_map_label[rule.key].replace('{N}', rule.value) + } + else{ + label = rules_id_map_label[rule.key] + } + return label +} + // 校验密码-改变规则颜色 function checkPasswordRules(password, minLength) { if (wordMinLength(password, minLength)) { - $('#rule_SECURITY_PASSWORD_MIN_LENGTH').css('color', 'green') + $('#'+rules_short_map_id['min']).css('color', 'green') } else { - $('#rule_SECURITY_PASSWORD_MIN_LENGTH').css('color', '#908a8a') + $('#'+rules_short_map_id['min']).css('color', '#908a8a') } if (wordUpperCase(password)) { - $('#rule_SECURITY_PASSWORD_UPPER_CASE').css('color', 'green'); + $('#'+rules_short_map_id['upper']).css('color', 'green') } else { - $('#rule_SECURITY_PASSWORD_UPPER_CASE').css('color', '#908a8a') + $('#'+rules_short_map_id['upper']).css('color', '#908a8a') } if (wordLowerCase(password)) { - $('#rule_SECURITY_PASSWORD_LOWER_CASE').css('color', 'green') + $('#'+rules_short_map_id['lower']).css('color', 'green') } else { - $('#rule_SECURITY_PASSWORD_LOWER_CASE').css('color', '#908a8a') + $('#'+rules_short_map_id['lower']).css('color', '#908a8a') } if (wordNumber(password)) { - $('#rule_SECURITY_PASSWORD_NUMBER').css('color', 'green') + $('#'+rules_short_map_id['number']).css('color', 'green') } else { - $('#rule_SECURITY_PASSWORD_NUMBER').css('color', '#908a8a') + $('#'+rules_short_map_id['number']).css('color', '#908a8a') } if (wordSpecialChar(password)) { - $('#rule_SECURITY_PASSWORD_SPECIAL_CHAR').css('color', 'green') + $('#'+rules_short_map_id['special']).css('color', 'green') } else { - $('#rule_SECURITY_PASSWORD_SPECIAL_CHAR').css('color', '#908a8a') + $('#'+rules_short_map_id['special']).css('color', '#908a8a') } } @@ -749,11 +777,12 @@ function wordSpecialChar(word) { return word.match(/[`,~,!,@,#,\$,%,\^,&,\*,\(,\),\-,_,=,\+,\{,\},\[,\],\|,\\,;,',:,",\,,\.,<,>,\/,\?]+/) } + // 显示弹窗密码规则 function popoverPasswordRules(password_check_rules, $el) { var message = ""; - jQuery.each(password_check_rules, function (idx, rules) { - message += "
  • " + rules.label + "
  • "; + jQuery.each(password_check_rules, function (idx, rule) { + message += "
  • " + getRuleLabel(rule) + "
  • "; }); //$('#id_password_rules').html(message); $el.html(message) diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 111ea2867..27d5af402 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -9,7 +9,6 @@ from django.conf import settings from users.models import User from orgs.mixins import OrgModelMixin -from common.models import common_settings from common.utils import get_command_storage_setting, get_replay_storage_setting from .backends.command.models import AbstractSessionCommand @@ -61,11 +60,11 @@ class Terminal(models.Model): configs = {} for k in dir(settings): if k.startswith('TERMINAL'): - configs[k] = getattr(common_settings, k) + configs[k] = getattr(settings, k) configs.update(self.get_common_storage()) configs.update(self.get_replay_storage()) configs.update({ - 'SECURITY_MAX_IDLE_TIME': common_settings.SECURITY_MAX_IDLE_TIME + 'SECURITY_MAX_IDLE_TIME': settings.SECURITY_MAX_IDLE_TIME }) return configs diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 8192ae095..b7a2f7e6a 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -14,7 +14,6 @@ from django.utils import timezone from django.shortcuts import reverse from common.utils import get_signer, date_expired_default -from common.models import common_settings from orgs.mixins import OrgManager from orgs.utils import current_org @@ -284,7 +283,7 @@ class User(AbstractUser): @property def otp_force_enabled(self): - if common_settings.SECURITY_MFA_AUTH: + if settings.SECURITY_MFA_AUTH: return True return self.otp_level == 2 diff --git a/apps/users/templates/users/reset_password.html b/apps/users/templates/users/reset_password.html index 1e533ba89..9ca385ce4 100644 --- a/apps/users/templates/users/reset_password.html +++ b/apps/users/templates/users/reset_password.html @@ -99,7 +99,7 @@ container = $('#container'), progress = $('#id_progress'), password_check_rules = {{ password_check_rules|safe }}, - minLength = {{ min_length }}, + minLength = 6, top = 146, left = 170, i18n_fallback = { "veryWeak": "{% trans 'Very weak' %}", @@ -110,6 +110,12 @@ "veryStrong": "{% trans 'Very strong' %}" }; + jQuery.each(password_check_rules, function (idx, rules) { + if(rules.key === 'id_security_password_min_length'){ + minLength = rules.value + } + }); + // 初始化popover initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback); diff --git a/apps/users/templates/users/user_password_update.html b/apps/users/templates/users/user_password_update.html index e028992e9..8056edb87 100644 --- a/apps/users/templates/users/user_password_update.html +++ b/apps/users/templates/users/user_password_update.html @@ -92,7 +92,7 @@ container = $('#container'), progress = $('#id_progress'), password_check_rules = {{ password_check_rules|safe }}, - minLength = {{ min_length }}, + minLength = 6, top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34, left = 377, i18n_fallback = { @@ -104,6 +104,12 @@ "veryStrong": "{% trans 'Very strong' %}" }; + jQuery.each(password_check_rules, function (idx, rules) { + if(rules.key === 'id_security_password_min_length'){ + minLength = rules.value + } + }); + // 初始化popover initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback); diff --git a/apps/users/templates/users/user_update.html b/apps/users/templates/users/user_update.html index 5026b090d..e991af83b 100644 --- a/apps/users/templates/users/user_update.html +++ b/apps/users/templates/users/user_update.html @@ -27,7 +27,7 @@ container = $('#container'), progress = $('#id_progress'), password_check_rules = {{ password_check_rules|safe }}, - minLength = {{ min_length }}, + minLength = 6, top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34, left = 377, i18n_fallback = { @@ -39,6 +39,12 @@ "veryStrong": "{% trans 'Very strong' %}" }; + jQuery.each(password_check_rules, function (idx, rules) { + if(rules.key === 'id_security_password_min_length'){ + minLength = rules.value + } + }); + // 初始化popover initPopover(container, progress, idPassword, el, password_check_rules, i18n_fallback); diff --git a/apps/users/utils.py b/apps/users/utils.py index 0098f1b99..75c1c9b55 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -19,8 +19,8 @@ from django.core.cache import cache from common.tasks import send_mail_async from common.utils import reverse, get_object_or_none -from common.models import common_settings, Setting from common.forms import SecuritySettingForm +from common.models import Setting from .models import User, LoginLog @@ -275,56 +275,27 @@ def check_otp_code(otp_secret_key, otp_code): def get_password_check_rules(): check_rules = [] - min_length = settings.DEFAULT_PASSWORD_MIN_LENGTH - min_name = 'SECURITY_PASSWORD_MIN_LENGTH' - base_filed = SecuritySettingForm.base_fields - password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD') - - if not password_setting: - # 用户还没有设置过密码校验规则 - label = base_filed.get(min_name).label - label += ' ' + str(min_length) + _('Bit') - id = 'rule_' + min_name - rules = {'id': id, 'label': label} - check_rules.append(rules) - - for setting in password_setting: - if setting.cleaned_value: - id = 'rule_' + setting.name - label = base_filed.get(setting.name).label - if setting.name == min_name: - label += str(setting.cleaned_value) + _('Bit') - min_length = setting.cleaned_value - rules = {'id': id, 'label': label} - check_rules.append(rules) - - return check_rules, min_length + for rule in settings.SECURITY_PASSWORD_RULES: + key = "id_{}".format(rule.lower()) + value = getattr(settings, rule) + if not value: + continue + check_rules.append({'key': key, 'value': int(value)}) + return check_rules def check_password_rules(password): - min_field_name = 'SECURITY_PASSWORD_MIN_LENGTH' - upper_field_name = 'SECURITY_PASSWORD_UPPER_CASE' - lower_field_name = 'SECURITY_PASSWORD_LOWER_CASE' - number_field_name = 'SECURITY_PASSWORD_NUMBER' - special_field_name = 'SECURITY_PASSWORD_SPECIAL_CHAR' - min_length = getattr(common_settings, min_field_name) - - password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD') - if not password_setting: - pattern = r"^.{" + str(min_length) + ",}$" - else: - pattern = r"^" - for setting in password_setting: - if setting.cleaned_value and setting.name == upper_field_name: - pattern += '(?=.*[A-Z])' - elif setting.cleaned_value and setting.name == lower_field_name: - pattern += '(?=.*[a-z])' - elif setting.cleaned_value and setting.name == number_field_name: - pattern += '(?=.*\d)' - elif setting.cleaned_value and setting.name == special_field_name: - pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'",\.<>\/\?])' - pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'",\.<>\/\?]' - + pattern = r"^" + if settings.SECURITY_PASSWORD_UPPER_CASE: + pattern += '(?=.*[A-Z])' + if settings.SECURITY_PASSWORD_LOWER_CASE: + pattern += '(?=.*[a-z])' + if settings.SECURITY_PASSWORD_NUMBER: + pattern += '(?=.*\d)' + if settings.SECURITY_PASSWORD_SPECIAL_CHAR: + pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?])' + pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?]' + pattern += '.{' + str(settings.SECURITY_PASSWORD_MIN_LENGTH-1) + ',}$' match_obj = re.match(pattern, password) return bool(match_obj) @@ -339,7 +310,7 @@ def increase_login_failed_count(username, ip): count = cache.get(key_limit) count = count + 1 if count else 1 - limit_time = common_settings.SECURITY_LOGIN_LIMIT_TIME + limit_time = settings.SECURITY_LOGIN_LIMIT_TIME cache.set(key_limit, count, int(limit_time)*60) @@ -355,8 +326,8 @@ def is_block_login(username, ip): key_block = key_prefix_block.format(username) count = cache.get(key_limit, 0) - limit_count = common_settings.SECURITY_LOGIN_LIMIT_COUNT - limit_time = common_settings.SECURITY_LOGIN_LIMIT_TIME + limit_count = settings.SECURITY_LOGIN_LIMIT_COUNT + limit_time = settings.SECURITY_LOGIN_LIMIT_TIME if count >= limit_count: cache.set(key_block, 1, int(limit_time)*60) diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 971b1cf2d..3901696ab 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -17,11 +17,10 @@ from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters from django.views.generic.base import TemplateView from django.views.generic.edit import FormView -from formtools.wizard.views import SessionWizardView from django.conf import settings +from formtools.wizard.views import SessionWizardView from common.utils import get_object_or_none, get_request_ip -from common.models import common_settings from ..models import User, LoginLog from ..utils import send_reset_password_mail, check_otp_code, \ redirect_user_first_login_or_index, get_user_or_tmp_user, \ @@ -269,13 +268,11 @@ class UserResetPasswordView(TemplateView): def get(self, request, *args, **kwargs): token = request.GET.get('token') user = User.validate_reset_token(token) - - check_rules, min_length = get_password_check_rules() - password_rules = {'password_check_rules': check_rules, 'min_length': min_length} - kwargs.update(password_rules) - if not user: kwargs.update({'errors': _('Token invalid or expired')}) + else: + check_rules = get_password_check_rules() + kwargs.update({'password_check_rules': check_rules}) return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): @@ -326,7 +323,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): user.is_public_key_valid = True user.save() context = { - 'user_guide_url': common_settings.USER_GUIDE_URL + 'user_guide_url': settings.USER_GUIDE_URL } return render(self.request, 'users/first_login_done.html', context) diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 43d951ec1..93dc94020 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -14,6 +14,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth import authenticate, login as auth_login from django.contrib.messages.views import SuccessMessageMixin from django.core.cache import cache +from django.conf import settings from django.http import HttpResponse, JsonResponse from django.shortcuts import redirect from django.urls import reverse_lazy, reverse @@ -33,7 +34,6 @@ from django.contrib.auth import logout as auth_logout from common.const import create_success_msg, update_success_msg from common.mixins import JSONResponseMixin from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen -from common.models import Setting, common_settings from common.permissions import AdminUserRequiredMixin from orgs.utils import current_org from .. import forms @@ -106,12 +106,11 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): success_message = update_success_msg def get_context_data(self, **kwargs): - check_rules, min_length = get_password_check_rules() + check_rules = get_password_check_rules() context = { 'app': _('Users'), 'action': _('Update user'), 'password_check_rules': check_rules, - 'min_length': min_length } kwargs.update(context) return super().get_context_data(**kwargs) @@ -362,7 +361,7 @@ class UserProfileView(LoginRequiredMixin, TemplateView): template_name = 'users/user_profile.html' def get_context_data(self, **kwargs): - mfa_setting = common_settings.SECURITY_MFA_AUTH + mfa_setting = settings.SECURITY_MFA_AUTH context = { 'action': _('Profile'), 'mfa_setting': mfa_setting if mfa_setting is not None else False, @@ -399,12 +398,11 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView): return self.request.user def get_context_data(self, **kwargs): - check_rules, min_length = get_password_check_rules() + check_rules = get_password_check_rules() context = { 'app': _('Users'), 'action': _('Password update'), 'password_check_rules': check_rules, - 'min_length': min_length, } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/config_example.py b/config_example.py index d0fc4bff9..28341d442 100644 --- a/config_example.py +++ b/config_example.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- """ jumpserver.config ~~~~~~~~~~~~~~~~~ @@ -13,48 +15,63 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) class Config: - # Use it to encrypt or decrypt data - # SECURITY WARNING: keep the secret key used in production secret! - SECRET_KEY = os.environ.get('SECRET_KEY') or '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x' + """ + Jumpserver Config File + Jumpserver 配置文件 - # Django security setting, if your disable debug model, you should setting that - ALLOWED_HOSTS = ['*'] + Jumpserver use this config for drive django framework running, + You can set is value or set the same envirment value, + Jumpserver look for config order: file => env => default + + Jumpserver使用配置来驱动Django框架的运行, + 你可以在该文件中设置,或者设置同样名称的环境变量, + Jumpserver使用配置的顺序: 文件 => 环境变量 => 默认值 + """ + # SECURITY WARNING: keep the secret key used in production secret! + # 加密秘钥 生产环境中请修改为随机字符串,请勿外泄 + SECRET_KEY = '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x' # Development env open this, when error occur display the full process track, Production disable it - DEBUG = os.environ.get("DEBUG") or True + # DEBUG 模式 开启DEBUG后遇到错误时可以看到更多日志 + # DEBUG = True # DEBUG, INFO, WARNING, ERROR, CRITICAL can set. See https://docs.djangoproject.com/en/1.10/topics/logging/ - LOG_LEVEL = os.environ.get("LOG_LEVEL") or 'DEBUG' - LOG_DIR = os.path.join(BASE_DIR, 'logs') + # 日志级别 + # LOG_LEVEL = 'DEBUG' + # LOG_DIR = os.path.join(BASE_DIR, 'logs') # Database setting, Support sqlite3, mysql, postgres .... + # 数据库设置 # See https://docs.djangoproject.com/en/1.10/ref/settings/#databases # SQLite setting: - DB_ENGINE = 'sqlite3' - DB_NAME = os.path.join(BASE_DIR, 'data', 'db.sqlite3') + # 使用单文件sqlite数据库 + # DB_ENGINE = 'sqlite3' + # DB_NAME = os.path.join(BASE_DIR, 'data', 'db.sqlite3') # MySQL or postgres setting like: - # DB_ENGINE = os.environ.get("DB_ENGINE") or 'mysql' - # DB_HOST = os.environ.get("DB_HOST") or '127.0.0.1' - # DB_PORT = os.environ.get("DB_PORT") or 3306 - # DB_USER = os.environ.get("DB_USER") or 'jumpserver' - # DB_PASSWORD = os.environ.get("DB_PASSWORD") or 'weakPassword' - # DB_NAME = os.environ.get("DB_NAME") or 'jumpserver' + # 使用Mysql作为数据库 + DB_ENGINE = 'mysql' + DB_HOST = '127.0.0.1' + DB_PORT = 3306 + DB_USER = 'jumpserver' + DB_PASSWORD = '' + DB_NAME = 'jumpserver' # When Django start it will bind this host and port # ./manage.py runserver 127.0.0.1:8080 + # 运行时绑定端口 HTTP_BIND_HOST = '0.0.0.0' HTTP_LISTEN_PORT = 8080 # Use Redis as broker for celery and web socket - REDIS_HOST = os.environ.get("REDIS_HOST") or '127.0.0.1' - REDIS_PORT = os.environ.get("REDIS_PORT") or 6379 - REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD") or '' - REDIS_DB_CELERY = os.environ.get('REDIS_DB') or 3 - REDIS_DB_CACHE = os.environ.get('REDIS_DB') or 4 + # Redis配置 + REDIS_HOST = '127.0.0.1' + REDIS_PORT = 6379 + REDIS_PASSWORD = '' # Use OpenID authorization + # 使用OpenID 来进行认证设置 # BASE_SITE_URL = 'http://localhost:8080' # AUTH_OPENID = False # True or False # AUTH_OPENID_SERVER_URL = 'https://openid-auth-server.com/'