Unverified Commit 16cc4a0f authored by 老广's avatar 老广 Committed by GitHub

[Update] 修改settings配置 (#2067)

* [Update] 修改settings配置

* [Update] 修改settings

* [Update] 修改密码校验规则前后端逻辑

* [Update] 修改用户config机制

* [Update] 修改配置

* [Update] 修改config example增加翻译
parent 5931c5a0
......@@ -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)
......
......@@ -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):
......
......@@ -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()
......@@ -4,4 +4,3 @@
from django.dispatch import Signal
django_ready = Signal()
ldap_auth_enable = Signal(providing_args=["enabled"])
......@@ -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:
......
......@@ -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)
......
......@@ -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'),
]
......@@ -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
......
......@@ -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')
......
#!/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
......@@ -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 = ""
......
......@@ -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 <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\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 ""
" </br>\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"
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-08-08 14:48+0800\n"
"POT-Creation-Date: 2018-11-21 19:14+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 "必须包含特殊字符"
......@@ -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):
......
......@@ -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 += "<li id=" + rules.id + " style='list-style-type:none;'> <i class='fa fa-check-circle-o' style='margin-right:10px;' ></i>" + rules.label + "</li>";
jQuery.each(password_check_rules, function (idx, rule) {
message += "<li id=" + rule.key + " style='list-style-type:none;'> <i class='fa fa-check-circle-o' style='margin-right:10px;' ></i>" + getRuleLabel(rule) + "</li>";
});
//$('#id_password_rules').html(message);
$el.html(message)
......
......@@ -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
......
......@@ -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
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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)
......
......@@ -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)
......
......@@ -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)
......
#!/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
"""
Jumpserver Config File
Jumpserver 配置文件
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 = os.environ.get('SECRET_KEY') or '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x'
# Django security setting, if your disable debug model, you should setting that
ALLOWED_HOSTS = ['*']
# 加密秘钥 生产环境中请修改为随机字符串,请勿外泄
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/'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment