mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-24 04:52:39 +00:00
Compare commits
31 Commits
v3.10.13
...
v3.10.17-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25603e4758 | ||
|
|
3ae164d7e0 | ||
|
|
3ad64e142e | ||
|
|
0ff1413780 | ||
|
|
f5b64bed4e | ||
|
|
a559415b65 | ||
|
|
2e7bd076f4 | ||
|
|
11f6fe0bf9 | ||
|
|
ae94648e80 | ||
|
|
94e08f3d96 | ||
|
|
8bedef92f0 | ||
|
|
e5bb28231a | ||
|
|
b5aeb24ae9 | ||
|
|
674ea7142f | ||
|
|
5ab7b99b9d | ||
|
|
9cd163c99d | ||
|
|
e72073f0cc | ||
|
|
690f525afc | ||
|
|
6686afcec1 | ||
|
|
0918f5c6f6 | ||
|
|
891e3d5609 | ||
|
|
9fad591545 | ||
|
|
1ed1c3a536 | ||
|
|
63824d3491 | ||
|
|
96eadf060c | ||
|
|
2c9128b0e7 | ||
|
|
7d6fd0f881 | ||
|
|
4e996afd5e | ||
|
|
1ed745d042 | ||
|
|
39ebbfcf10 | ||
|
|
d0ec4f798b |
@@ -62,7 +62,7 @@ def create_accounts_activities(account, action='create'):
|
||||
|
||||
@receiver(post_save, sender=Account)
|
||||
def on_account_create_by_template(sender, instance, created=False, **kwargs):
|
||||
if not created or instance.source != Source.TEMPLATE:
|
||||
if not created:
|
||||
return
|
||||
push_accounts_if_need.delay(accounts=(instance,))
|
||||
create_accounts_activities(instance, action='create')
|
||||
|
||||
@@ -28,7 +28,7 @@ from orgs.utils import current_org, tmp_to_root_org
|
||||
from rbac.permissions import RBACPermission
|
||||
from terminal.models import default_storage
|
||||
from users.models import User
|
||||
from .backends import TYPE_ENGINE_MAPPING
|
||||
from .backends import get_operate_log_storage
|
||||
from .const import ActivityChoices
|
||||
from .filters import UserSessionFilterSet, OperateLogFilterSet
|
||||
from .models import (
|
||||
@@ -146,7 +146,9 @@ class MyLoginLogViewSet(UserLoginCommonMixin, OrgReadonlyModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
qs = qs.filter(username=self.request.user.username)
|
||||
username = self.request.user.username
|
||||
q = Q(username=username) | Q(username__icontains=f'({username})')
|
||||
qs = qs.filter(q)
|
||||
return qs
|
||||
|
||||
|
||||
@@ -187,9 +189,13 @@ class ResourceActivityAPIView(generics.ListAPIView):
|
||||
'id', 'datetime', 'r_detail', 'r_detail_id',
|
||||
'r_user', 'r_action', 'r_type'
|
||||
)
|
||||
org_q = Q(org_id=Organization.SYSTEM_ID) | Q(org_id=current_org.id)
|
||||
if resource_id:
|
||||
org_q |= Q(org_id='') | Q(org_id=Organization.ROOT_ID)
|
||||
|
||||
org_q = Q()
|
||||
if not current_org.is_root():
|
||||
org_q = Q(org_id=Organization.SYSTEM_ID) | Q(org_id=current_org.id)
|
||||
if resource_id:
|
||||
org_q |= Q(org_id='') | Q(org_id=Organization.ROOT_ID)
|
||||
|
||||
with tmp_to_root_org():
|
||||
qs1 = self.get_operate_log_qs(fields, limit, org_q, resource_id=resource_id)
|
||||
qs2 = self.get_activity_log_qs(fields, limit, org_q, resource_id=resource_id)
|
||||
@@ -222,13 +228,11 @@ class OperateLogViewSet(OrgReadonlyModelViewSet):
|
||||
if self.is_action_detail:
|
||||
with tmp_to_root_org():
|
||||
qs |= OperateLog.objects.filter(org_id=Organization.SYSTEM_ID)
|
||||
es_config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG
|
||||
if es_config:
|
||||
engine_mod = import_module(TYPE_ENGINE_MAPPING['es'])
|
||||
store = engine_mod.OperateLogStore(es_config)
|
||||
if store.ping(timeout=2):
|
||||
qs = ESQuerySet(store)
|
||||
qs.model = OperateLog
|
||||
|
||||
storage = get_operate_log_storage()
|
||||
if storage.get_type() == 'es':
|
||||
qs = ESQuerySet(storage)
|
||||
qs.model = OperateLog
|
||||
return qs
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,62 @@
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.utils import get_logger
|
||||
from .base import BaseOperateStorage
|
||||
from .es import OperateLogStore as ESOperateLogStore
|
||||
from .db import OperateLogStore as DBOperateLogStore
|
||||
|
||||
|
||||
TYPE_ENGINE_MAPPING = {
|
||||
'db': 'audits.backends.db',
|
||||
'es': 'audits.backends.es',
|
||||
logger = get_logger(__file__)
|
||||
|
||||
_global_op_log_storage: None | ESOperateLogStore | DBOperateLogStore = None
|
||||
op_log_type_mapping = {
|
||||
'server': DBOperateLogStore, 'es': ESOperateLogStore
|
||||
}
|
||||
|
||||
|
||||
def get_operate_log_storage(default=False):
|
||||
engine_mod = import_module(TYPE_ENGINE_MAPPING['db'])
|
||||
es_config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG
|
||||
if not default and es_config:
|
||||
engine_mod = import_module(TYPE_ENGINE_MAPPING['es'])
|
||||
storage = engine_mod.OperateLogStore(es_config)
|
||||
return storage
|
||||
def _send_es_unavailable_alarm_msg():
|
||||
from terminal.notifications import StorageConnectivityMessage
|
||||
from terminal.const import CommandStorageType
|
||||
|
||||
key = 'OPERATE_LOG_ES_UNAVAILABLE_KEY'
|
||||
if cache.get(key):
|
||||
return
|
||||
|
||||
cache.set(key, 1, 60)
|
||||
errors = [{
|
||||
'msg': _("Connect failed"), 'name': f"{_('Operate log')}",
|
||||
'type': CommandStorageType.es.label
|
||||
}]
|
||||
StorageConnectivityMessage(errors).publish_async()
|
||||
|
||||
|
||||
def refresh_log_storage():
|
||||
global _global_op_log_storage
|
||||
_global_op_log_storage = None
|
||||
|
||||
if settings.OPERATE_LOG_ELASTICSEARCH_CONFIG.get('HOSTS'):
|
||||
try:
|
||||
config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG
|
||||
log_storage = op_log_type_mapping['es'](config)
|
||||
_global_op_log_storage = log_storage
|
||||
except Exception as e:
|
||||
_send_es_unavailable_alarm_msg()
|
||||
logger.warning('Invalid logs storage type: es, error: %s' % str(e))
|
||||
|
||||
if not _global_op_log_storage:
|
||||
_global_op_log_storage = op_log_type_mapping['server']()
|
||||
|
||||
|
||||
def get_operate_log_storage():
|
||||
if _global_op_log_storage is None:
|
||||
refresh_log_storage()
|
||||
|
||||
log_storage = _global_op_log_storage
|
||||
if not log_storage.ping(timeout=3):
|
||||
if log_storage.get_type() == 'es':
|
||||
_send_es_unavailable_alarm_msg()
|
||||
logger.warning('Switch default operate log storage.')
|
||||
log_storage = op_log_type_mapping['server']()
|
||||
return log_storage
|
||||
|
||||
15
apps/audits/backends/base.py
Normal file
15
apps/audits/backends/base.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from perms.const import ActionChoices
|
||||
|
||||
|
||||
class BaseOperateStorage(object):
|
||||
@staticmethod
|
||||
def get_type():
|
||||
return 'base'
|
||||
|
||||
@staticmethod
|
||||
def _get_special_handler(resource_type):
|
||||
# 根据资源类型,处理特殊字段
|
||||
resource_map = {
|
||||
'Asset permission': lambda k, v: ActionChoices.display(int(v)) if k == 'Actions' else v
|
||||
}
|
||||
return resource_map.get(resource_type, lambda k, v: v)
|
||||
@@ -2,14 +2,14 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from audits.models import OperateLog
|
||||
from perms.const import ActionChoices
|
||||
from .base import BaseOperateStorage
|
||||
|
||||
|
||||
class OperateLogStore(object):
|
||||
class OperateLogStore(BaseOperateStorage):
|
||||
# 用不可见字符分割前后数据,节省存储-> diff: {'key': 'before\0after'}
|
||||
SEP = '\0'
|
||||
|
||||
def __init__(self, config):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.model = OperateLog
|
||||
self.max_length = 2048
|
||||
self.max_length_tip_msg = _(
|
||||
@@ -17,9 +17,13 @@ class OperateLogStore(object):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def ping(timeout=None):
|
||||
def ping(*args, **kwargs):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_type():
|
||||
return 'db'
|
||||
|
||||
@classmethod
|
||||
def convert_before_after_to_diff(cls, before, after):
|
||||
if not isinstance(before, dict):
|
||||
@@ -46,21 +50,13 @@ class OperateLogStore(object):
|
||||
before[k], after[k] = before_value, after_value
|
||||
return before, after
|
||||
|
||||
@staticmethod
|
||||
def _get_special_handler(resource_type):
|
||||
# 根据资源类型,处理特殊字段
|
||||
resource_map = {
|
||||
'Asset permission': lambda k, v: ActionChoices.display(int(v)) if k == 'Actions' else v
|
||||
}
|
||||
return resource_map.get(resource_type, lambda k, v: _(v))
|
||||
|
||||
@classmethod
|
||||
def convert_diff_friendly(cls, op_log):
|
||||
diff_list = list()
|
||||
handler = cls._get_special_handler(op_log.resource_type)
|
||||
# 标记翻译字符串
|
||||
labels = _("labels")
|
||||
operate_log_id = _("operate_log_id")
|
||||
handler = cls._get_special_handler(op_log.resource_type)
|
||||
for k, v in op_log.diff.items():
|
||||
before, after = v.split(cls.SEP, 1)
|
||||
diff_list.append({
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
#
|
||||
import uuid
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.utils.timezone import local_now_display
|
||||
from common.utils import get_logger
|
||||
from common.utils.encode import Singleton
|
||||
from common.plugins.es import ES
|
||||
|
||||
from .base import BaseOperateStorage
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class OperateLogStore(ES, metaclass=Singleton):
|
||||
class OperateLogStore(BaseOperateStorage, ES):
|
||||
def __init__(self, config):
|
||||
properties = {
|
||||
"id": {
|
||||
@@ -48,7 +49,26 @@ class OperateLogStore(ES, metaclass=Singleton):
|
||||
self.pre_use_check()
|
||||
|
||||
@staticmethod
|
||||
def make_data(data):
|
||||
def get_type():
|
||||
return 'es'
|
||||
|
||||
@classmethod
|
||||
def convert_diff_friendly(cls, op_log):
|
||||
diff_list = []
|
||||
handler = cls._get_special_handler(op_log.get('resource_type'))
|
||||
before = op_log.get('before') or {}
|
||||
after = op_log.get('after') or {}
|
||||
keys = set(before.keys()) | set(after.keys())
|
||||
for key in keys:
|
||||
before_v, after_v = before.get(key), after.get(key)
|
||||
diff_list.append({
|
||||
'field': _(key),
|
||||
'before': handler(key, before_v) if before_v else _('empty'),
|
||||
'after': handler(key, after_v) if after_v else _('empty'),
|
||||
})
|
||||
return diff_list
|
||||
|
||||
def make_data(self, data):
|
||||
op_id = data.get('id', str(uuid.uuid4()))
|
||||
datetime_param = data.get('datetime', local_now_display())
|
||||
data = {
|
||||
|
||||
@@ -7,7 +7,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.local import encrypted_field_set
|
||||
from common.utils import get_request_ip, get_logger
|
||||
from common.utils.encode import Singleton
|
||||
from common.utils.timezone import as_current_tz
|
||||
from jumpserver.utils import current_request
|
||||
from orgs.models import Organization
|
||||
@@ -21,17 +20,9 @@ from .backends import get_operate_log_storage
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class OperatorLogHandler(metaclass=Singleton):
|
||||
class OperatorLogHandler(object):
|
||||
CACHE_KEY = 'OPERATOR_LOG_CACHE_KEY'
|
||||
|
||||
def __init__(self):
|
||||
self.log_client = self.get_storage_client()
|
||||
|
||||
@staticmethod
|
||||
def get_storage_client():
|
||||
client = get_operate_log_storage()
|
||||
return client
|
||||
|
||||
@staticmethod
|
||||
def _consistent_type_to_str(value1, value2):
|
||||
if isinstance(value1, datetime):
|
||||
@@ -164,13 +155,8 @@ class OperatorLogHandler(metaclass=Singleton):
|
||||
'remote_addr': remote_addr, 'before': before, 'after': after,
|
||||
}
|
||||
with transaction.atomic():
|
||||
if self.log_client.ping(timeout=1):
|
||||
client = self.log_client
|
||||
else:
|
||||
logger.info('Switch default operate log storage save.')
|
||||
client = get_operate_log_storage(default=True)
|
||||
|
||||
try:
|
||||
client = get_operate_log_storage()
|
||||
client.save(**data)
|
||||
except Exception as e:
|
||||
error_msg = 'An error occurred saving OperateLog.' \
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from audits.backends.db import OperateLogStore
|
||||
from audits.backends import get_operate_log_storage
|
||||
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
|
||||
from common.utils import reverse, i18n_trans
|
||||
from common.utils.timezone import as_current_tz
|
||||
@@ -77,7 +77,7 @@ class OperateLogActionDetailSerializer(serializers.ModelSerializer):
|
||||
fields = ('diff',)
|
||||
|
||||
def to_representation(self, instance):
|
||||
return {'diff': OperateLogStore.convert_diff_friendly(instance)}
|
||||
return {'diff': get_operate_log_storage().convert_diff_friendly(instance)}
|
||||
|
||||
|
||||
class OperateLogSerializer(BulkOrgResourceModelSerializer):
|
||||
|
||||
@@ -14,7 +14,7 @@ from audits.handler import (
|
||||
create_or_update_operate_log, get_instance_dict_from_cache
|
||||
)
|
||||
from audits.utils import model_to_dict_for_operate_log as model_to_dict
|
||||
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR, SKIP_SIGNAL
|
||||
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR, OP_LOG_SKIP_SIGNAL
|
||||
from common.signals import django_ready
|
||||
from jumpserver.utils import current_request
|
||||
from ..const import MODELS_NEED_RECORD, ActionChoices
|
||||
@@ -77,7 +77,7 @@ def signal_of_operate_log_whether_continue(
|
||||
condition = True
|
||||
if not instance:
|
||||
condition = False
|
||||
if instance and getattr(instance, SKIP_SIGNAL, False):
|
||||
if instance and getattr(instance, OP_LOG_SKIP_SIGNAL, False):
|
||||
condition = False
|
||||
# 不记录组件的操作日志
|
||||
user = current_request.user if current_request else None
|
||||
@@ -189,7 +189,7 @@ def on_django_start_set_operate_log_monitor_models(sender, **kwargs):
|
||||
'ApplyCommandTicket', 'ApplyLoginAssetTicket',
|
||||
'FavoriteAsset', 'ChangeSecretRecord'
|
||||
}
|
||||
include_models = {'UserSession'}
|
||||
include_models = set()
|
||||
for i, app in enumerate(apps.get_models(), 1):
|
||||
app_name = app._meta.app_label
|
||||
model_name = app._meta.object_name
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import base64
|
||||
import requests
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -67,14 +68,6 @@ class OAuth2Backend(JMSModelBackend):
|
||||
response_data = response_data['data']
|
||||
return response_data
|
||||
|
||||
@staticmethod
|
||||
def get_query_dict(response_data, query_dict):
|
||||
query_dict.update({
|
||||
'uid': response_data.get('uid', ''),
|
||||
'access_token': response_data.get('access_token', '')
|
||||
})
|
||||
return query_dict
|
||||
|
||||
def authenticate(self, request, code=None, **kwargs):
|
||||
log_prompt = "Process authenticate [OAuth2Backend]: {}"
|
||||
logger.debug(log_prompt.format('Start'))
|
||||
@@ -83,29 +76,31 @@ class OAuth2Backend(JMSModelBackend):
|
||||
return None
|
||||
|
||||
query_dict = {
|
||||
'client_id': settings.AUTH_OAUTH2_CLIENT_ID,
|
||||
'client_secret': settings.AUTH_OAUTH2_CLIENT_SECRET,
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
'grant_type': 'authorization_code', 'code': code,
|
||||
'redirect_uri': build_absolute_uri(
|
||||
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
|
||||
)
|
||||
}
|
||||
if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT:
|
||||
separator = '&'
|
||||
else:
|
||||
separator = '?'
|
||||
separator = '&' if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT else '?'
|
||||
access_token_url = '{url}{separator}{query}'.format(
|
||||
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, separator=separator, query=urlencode(query_dict)
|
||||
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT,
|
||||
separator=separator, query=urlencode(query_dict)
|
||||
)
|
||||
# token_method -> get, post(post_data), post_json
|
||||
token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower()
|
||||
logger.debug(log_prompt.format('Call the access token endpoint[method: %s]' % token_method))
|
||||
encoded_credentials = base64.b64encode(
|
||||
f"{settings.AUTH_OAUTH2_CLIENT_ID}:{settings.AUTH_OAUTH2_CLIENT_SECRET}".encode()
|
||||
).decode()
|
||||
headers = {
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json', 'Authorization': f'Basic {encoded_credentials}'
|
||||
}
|
||||
if token_method.startswith('post'):
|
||||
body_key = 'json' if token_method.endswith('json') else 'data'
|
||||
query_dict.update({
|
||||
'client_id': settings.AUTH_OAUTH2_CLIENT_ID,
|
||||
'client_secret': settings.AUTH_OAUTH2_CLIENT_SECRET,
|
||||
})
|
||||
access_token_response = requests.post(
|
||||
access_token_url, headers=headers, **{body_key: query_dict}
|
||||
)
|
||||
@@ -121,22 +116,12 @@ class OAuth2Backend(JMSModelBackend):
|
||||
logger.error(log_prompt.format(error))
|
||||
return None
|
||||
|
||||
query_dict = self.get_query_dict(response_data, query_dict)
|
||||
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer {}'.format(response_data.get('access_token', ''))
|
||||
}
|
||||
|
||||
logger.debug(log_prompt.format('Get userinfo endpoint'))
|
||||
if '?' in settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT:
|
||||
separator = '&'
|
||||
else:
|
||||
separator = '?'
|
||||
userinfo_url = '{url}{separator}{query}'.format(
|
||||
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT, separator=separator,
|
||||
query=urlencode(query_dict)
|
||||
)
|
||||
userinfo_url = settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT
|
||||
userinfo_response = requests.get(userinfo_url, headers=headers)
|
||||
try:
|
||||
userinfo_response.raise_for_status()
|
||||
|
||||
@@ -107,7 +107,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
# parameters because we won't be able to get a valid token for the user in that case.
|
||||
if (state is None and settings.AUTH_OPENID_USE_STATE) or code is None:
|
||||
logger.debug(log_prompt.format('Authorization code or state value is missing'))
|
||||
raise SuspiciousOperation('Authorization code or state value is missing')
|
||||
return
|
||||
|
||||
# Prepares the token payload that will be used to request an authentication token to the
|
||||
# token endpoint of the OIDC provider.
|
||||
@@ -165,7 +165,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
error = "Json token response error, token response " \
|
||||
"content is: {}, error is: {}".format(token_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
raise ParseError(error)
|
||||
return
|
||||
|
||||
# Validates the token.
|
||||
logger.debug(log_prompt.format('Validate ID Token'))
|
||||
@@ -206,7 +206,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
error = "Json claims response error, claims response " \
|
||||
"content is: {}, error is: {}".format(claims_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
raise ParseError(error)
|
||||
return
|
||||
|
||||
logger.debug(log_prompt.format('Get or create user from claims'))
|
||||
user, created = self.get_or_create_user_from_claims(request, claims)
|
||||
|
||||
@@ -2,6 +2,7 @@ import base64
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect, reverse, render
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
@@ -116,23 +117,43 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
|
||||
|
||||
|
||||
class SessionCookieMiddleware(MiddlewareMixin):
|
||||
USER_LOGIN_ENCRYPTION_KEY_PAIR = 'user_login_encryption_key_pair'
|
||||
|
||||
@staticmethod
|
||||
def set_cookie_public_key(request, response):
|
||||
def set_cookie_public_key(self, request, response):
|
||||
if request.path.startswith('/api'):
|
||||
return
|
||||
pub_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME
|
||||
public_key = request.session.get(pub_key_name)
|
||||
cookie_key = request.COOKIES.get(pub_key_name)
|
||||
if public_key and public_key == cookie_key:
|
||||
|
||||
session_public_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME
|
||||
session_private_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME
|
||||
|
||||
session_public_key = request.session.get(session_public_key_name)
|
||||
cookie_public_key = request.COOKIES.get(session_public_key_name)
|
||||
|
||||
if session_public_key and session_public_key == cookie_public_key:
|
||||
return
|
||||
|
||||
pri_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME
|
||||
private_key, public_key = gen_key_pair()
|
||||
private_key, public_key = self.get_key_pair()
|
||||
|
||||
public_key_decode = base64.b64encode(public_key.encode()).decode()
|
||||
request.session[pub_key_name] = public_key_decode
|
||||
request.session[pri_key_name] = private_key
|
||||
response.set_cookie(pub_key_name, public_key_decode)
|
||||
|
||||
request.session[session_public_key_name] = public_key_decode
|
||||
request.session[session_private_key_name] = private_key
|
||||
response.set_cookie(session_public_key_name, public_key_decode)
|
||||
|
||||
def get_key_pair(self):
|
||||
key_pair = cache.get(self.USER_LOGIN_ENCRYPTION_KEY_PAIR)
|
||||
if key_pair:
|
||||
return key_pair['private_key'], key_pair['public_key']
|
||||
|
||||
private_key, public_key = gen_key_pair()
|
||||
|
||||
key_pair = {
|
||||
'private_key': private_key,
|
||||
'public_key': public_key
|
||||
}
|
||||
cache.set(self.USER_LOGIN_ENCRYPTION_KEY_PAIR, key_pair, None)
|
||||
|
||||
return private_key, public_key
|
||||
|
||||
@staticmethod
|
||||
def set_cookie_session_prefix(request, response):
|
||||
|
||||
@@ -319,20 +319,26 @@ class AuthPostCheckMixin:
|
||||
|
||||
@classmethod
|
||||
def _check_passwd_is_too_simple(cls, user: User, password):
|
||||
if user.is_superuser and password == 'admin':
|
||||
if not user.is_auth_backend_model():
|
||||
return
|
||||
if user.check_passwd_too_simple(password):
|
||||
message = _('Your password is too simple, please change it for security')
|
||||
url = cls.generate_reset_password_url_with_flash_msg(user, message=message)
|
||||
raise errors.PasswordTooSimple(url)
|
||||
|
||||
@classmethod
|
||||
def _check_passwd_need_update(cls, user: User):
|
||||
if user.need_update_password:
|
||||
if not user.is_auth_backend_model():
|
||||
return
|
||||
if user.check_need_update_password():
|
||||
message = _('You should to change your password before login')
|
||||
url = cls.generate_reset_password_url_with_flash_msg(user, message)
|
||||
raise errors.PasswordNeedUpdate(url)
|
||||
|
||||
@classmethod
|
||||
def _check_password_require_reset_or_not(cls, user: User):
|
||||
if not user.is_auth_backend_model():
|
||||
return
|
||||
if user.password_has_expired:
|
||||
message = _('Your password has expired, please reset before logging in')
|
||||
url = cls.generate_reset_password_url_with_flash_msg(user, message)
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
<p>
|
||||
<b>{% trans 'Username' %}:</b> {{ username }}<br>
|
||||
<b>{% trans 'Login time' %}:</b> {{ time }}<br>
|
||||
<b>{% trans 'Login city' %}:</b> {{ city }}({{ ip }})
|
||||
<b>{% trans 'Login city' %}:</b> {{ city }}({{ ip }})<br>
|
||||
</p>
|
||||
|
||||
-
|
||||
<p>
|
||||
{% trans 'If you suspect that the login behavior is abnormal, please modify the account password in time.' %}
|
||||
</p>
|
||||
@@ -10,8 +10,7 @@
|
||||
{% trans 'Click here reset password' %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
-
|
||||
<br>
|
||||
<p>
|
||||
{% trans 'This link is valid for 1 hour. After it expires' %}
|
||||
<a href="{{ forget_password_url }}?email={{ user.email }}">{% trans 'request new one' %}</a>
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
{% trans 'Your password has just been successfully updated' %}
|
||||
</p>
|
||||
<p>
|
||||
<b>{% trans 'IP' %}:</b> {{ ip_address }} <br />
|
||||
<b>{% trans 'Browser' %}:</b> {{ browser }}
|
||||
<b>{% trans 'IP' %}:</b> {{ ip_address }} <br/>
|
||||
<b>{% trans 'Browser' %}:</b> {{ browser }} <br>
|
||||
</p>
|
||||
-
|
||||
<p>
|
||||
{% trans 'If the password update was not initiated by you, your account may have security issues' %} <br />
|
||||
{% trans 'If the password update was not initiated by you, your account may have security issues' %} <br/>
|
||||
{% trans 'If you have any questions, you can contact the administrator' %}
|
||||
</p>
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
{% trans 'Your public key has just been successfully updated' %}
|
||||
</p>
|
||||
<p>
|
||||
<b>{% trans 'IP' %}:</b> {{ ip_address }} <br />
|
||||
<b>{% trans 'Browser' %}:</b> {{ browser }}
|
||||
<b>{% trans 'IP' %}:</b> {{ ip_address }} <br>
|
||||
<b>{% trans 'Browser' %}:</b> {{ browser }}<br>
|
||||
</p>
|
||||
-
|
||||
<p>
|
||||
{% trans 'If the public key update was not initiated by you, your account may have security issues' %} <br />
|
||||
{% trans 'If the public key update was not initiated by you, your account may have security issues' %} <br/>
|
||||
{% trans 'If you have any questions, you can contact the administrator' %}
|
||||
</p>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .utils import gen_key_pair, rsa_decrypt, rsa_encrypt
|
||||
from common.utils import gen_key_pair, rsa_decrypt, rsa_encrypt
|
||||
|
||||
|
||||
def test_rsa_encrypt_decrypt(message='test-password-$%^&*'):
|
||||
|
||||
@@ -16,4 +16,4 @@ POST_CLEAR = 'post_clear'
|
||||
POST_PREFIX = 'post'
|
||||
PRE_PREFIX = 'pre'
|
||||
|
||||
SKIP_SIGNAL = 'skip_signal'
|
||||
OP_LOG_SKIP_SIGNAL = 'operate_log_skip_signal'
|
||||
|
||||
@@ -17,7 +17,7 @@ from django.db.models import F, ExpressionWrapper, CASCADE
|
||||
from django.db.models import QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..const.signals import SKIP_SIGNAL
|
||||
from ..const.signals import OP_LOG_SKIP_SIGNAL
|
||||
|
||||
|
||||
class ChoicesMixin:
|
||||
@@ -82,7 +82,7 @@ def CASCADE_SIGNAL_SKIP(collector, field, sub_objs, using):
|
||||
# 级联删除时,操作日志标记不保存,以免用户混淆
|
||||
try:
|
||||
for obj in sub_objs:
|
||||
setattr(obj, SKIP_SIGNAL, True)
|
||||
setattr(obj, OP_LOG_SKIP_SIGNAL, True)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ class Config(dict):
|
||||
'SESSION_COOKIE_NAME_PREFIX': None,
|
||||
'SESSION_COOKIE_AGE': 3600 * 24,
|
||||
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
||||
'VIEW_ASSET_ONLINE_SESSION_INFO': True,
|
||||
'LOGIN_URL': reverse_lazy('authentication:login'),
|
||||
|
||||
'CONNECTION_TOKEN_ONETIME_EXPIRATION': 5 * 60, # 默认(new)
|
||||
@@ -600,7 +601,6 @@ class Config(dict):
|
||||
|
||||
# API 分页
|
||||
'MAX_LIMIT_PER_PAGE': 10000,
|
||||
'DEFAULT_PAGE_SIZE': None,
|
||||
|
||||
'LIMIT_SUPER_PRIV': False,
|
||||
|
||||
|
||||
@@ -236,6 +236,7 @@ SESSION_COOKIE_NAME = '{}sessionid'.format(SESSION_COOKIE_NAME_PREFIX)
|
||||
SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE
|
||||
SESSION_SAVE_EVERY_REQUEST = CONFIG.SESSION_SAVE_EVERY_REQUEST
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE
|
||||
VIEW_ASSET_ONLINE_SESSION_INFO = CONFIG.VIEW_ASSET_ONLINE_SESSION_INFO
|
||||
SESSION_ENGINE = "common.sessions.{}".format(CONFIG.SESSION_ENGINE)
|
||||
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
|
||||
@@ -208,7 +208,7 @@ SESSION_RSA_PUBLIC_KEY_NAME = 'jms_public_key'
|
||||
OPERATE_LOG_ELASTICSEARCH_CONFIG = CONFIG.OPERATE_LOG_ELASTICSEARCH_CONFIG
|
||||
|
||||
MAX_LIMIT_PER_PAGE = CONFIG.MAX_LIMIT_PER_PAGE
|
||||
DEFAULT_PAGE_SIZE = CONFIG.DEFAULT_PAGE_SIZE
|
||||
DEFAULT_PAGE_SIZE = CONFIG.MAX_LIMIT_PER_PAGE
|
||||
PERM_TREE_REGEN_INTERVAL = CONFIG.PERM_TREE_REGEN_INTERVAL
|
||||
|
||||
# Magnus DB Port
|
||||
|
||||
@@ -8610,7 +8610,7 @@ msgstr ""
|
||||
|
||||
#: xpack/plugins/cloud/models.py:100
|
||||
#: xpack/plugins/cloud/serializers/task.py:167
|
||||
msgid "Sync IP type"
|
||||
msgid "Preferred IP type"
|
||||
msgstr ""
|
||||
|
||||
#: xpack/plugins/cloud/models.py:103
|
||||
@@ -9112,3 +9112,7 @@ msgstr ""
|
||||
#: xpack/plugins/license/models.py:86
|
||||
msgid "Ultimate edition"
|
||||
msgstr ""
|
||||
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:74
|
||||
msgid "Auto node classification"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:607f8df8c6cea454283e0bc371db3be0c81f7cfa981a5f00c20b5be139e16aac
|
||||
size 178618
|
||||
oid sha256:2dd9ffcfe15b130a5b3d7b4fcfe806eaae979973e8bd29ad9a473b9215424c57
|
||||
size 178725
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-08-20 14:54+0800\n"
|
||||
"POT-Creation-Date: 2024-10-22 17:33+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"
|
||||
@@ -798,8 +798,8 @@ msgstr "カテゴリ"
|
||||
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
|
||||
#: terminal/models/component/storage.py:57
|
||||
#: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29
|
||||
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:271
|
||||
#: terminal/serializers/storage.py:283 tickets/models/comment.py:26
|
||||
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:274
|
||||
#: terminal/serializers/storage.py:286 tickets/models/comment.py:26
|
||||
#: tickets/models/flow.py:56 tickets/models/ticket/apply_application.py:16
|
||||
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:53
|
||||
#: tickets/serializers/ticket/ticket.py:19
|
||||
@@ -1425,12 +1425,13 @@ msgid "Unable to connect to port {port} on {address}"
|
||||
msgstr "{port} のポート {address} に接続できません"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:58
|
||||
#: authentication/middleware.py:93 xpack/plugins/cloud/providers/fc.py:47
|
||||
#: authentication/middleware.py:94 xpack/plugins/cloud/providers/fc.py:47
|
||||
msgid "Authentication failed"
|
||||
msgstr "認証に失敗しました"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:60
|
||||
#: assets/automations/ping_gateway/manager.py:86 terminal/const.py:102
|
||||
#: assets/automations/ping_gateway/manager.py:86 audits/backends/__init__.py:29
|
||||
#: terminal/const.py:102
|
||||
msgid "Connect failed"
|
||||
msgstr "接続に失敗しました"
|
||||
|
||||
@@ -1978,7 +1979,8 @@ msgstr "ラベル"
|
||||
msgid "New node"
|
||||
msgstr "新しいノード"
|
||||
|
||||
#: assets/models/node.py:467 audits/backends/db.py:68 audits/backends/db.py:69
|
||||
#: assets/models/node.py:467 audits/backends/db.py:64 audits/backends/db.py:65
|
||||
#: audits/backends/es.py:66 audits/backends/es.py:67
|
||||
msgid "empty"
|
||||
msgstr "空"
|
||||
|
||||
@@ -2407,15 +2409,15 @@ msgstr "監査"
|
||||
msgid "The text content is too long. Use Elasticsearch to store operation logs"
|
||||
msgstr "文章の内容が長すぎる。Elasticsearchで操作履歴を保存する"
|
||||
|
||||
#: audits/backends/db.py:61
|
||||
#: audits/backends/db.py:58
|
||||
msgid "labels"
|
||||
msgstr "ラベル"
|
||||
msgstr "タグ"
|
||||
|
||||
#: audits/backends/db.py:62
|
||||
#: audits/backends/db.py:59
|
||||
msgid "operate_log_id"
|
||||
msgstr "操作ログID"
|
||||
|
||||
#: audits/backends/db.py:94
|
||||
#: audits/backends/db.py:90
|
||||
msgid "Tips"
|
||||
msgstr "謎々"
|
||||
|
||||
@@ -2498,7 +2500,7 @@ msgstr "終了"
|
||||
#: audits/const.py:46 settings/serializers/terminal.py:6
|
||||
#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:175
|
||||
#: terminal/models/virtualapp/provider.py:14 terminal/serializers/session.py:55
|
||||
#: terminal/serializers/session.py:69
|
||||
#: terminal/serializers/session.py:79
|
||||
msgid "Terminal"
|
||||
msgstr "ターミナル"
|
||||
|
||||
@@ -2523,11 +2525,11 @@ msgstr "タスク"
|
||||
msgid "-"
|
||||
msgstr "-"
|
||||
|
||||
#: audits/handler.py:116
|
||||
#: audits/handler.py:107
|
||||
msgid "Yes"
|
||||
msgstr "是"
|
||||
|
||||
#: audits/handler.py:116
|
||||
#: audits/handler.py:107
|
||||
msgid "No"
|
||||
msgstr "否"
|
||||
|
||||
@@ -3140,7 +3142,7 @@ msgstr "電話番号を設定して有効にする"
|
||||
msgid "Clear phone number to disable"
|
||||
msgstr "無効にする電話番号をクリアする"
|
||||
|
||||
#: authentication/middleware.py:94 settings/utils/ldap.py:679
|
||||
#: authentication/middleware.py:95 settings/utils/ldap.py:679
|
||||
msgid "Authentication failed (before login check failed): {}"
|
||||
msgstr "認証に失敗しました (ログインチェックが失敗する前): {}"
|
||||
|
||||
@@ -3421,7 +3423,7 @@ msgstr "アカウントにリモートログイン動作があります。注意
|
||||
msgid "Login time"
|
||||
msgstr "ログイン時間"
|
||||
|
||||
#: authentication/templates/authentication/_msg_different_city.html:16
|
||||
#: authentication/templates/authentication/_msg_different_city.html:14
|
||||
msgid ""
|
||||
"If you suspect that the login behavior is abnormal, please modify the "
|
||||
"account password in time."
|
||||
@@ -3449,13 +3451,13 @@ msgstr ""
|
||||
msgid "Click here reset password"
|
||||
msgstr "ここをクリックしてパスワードをリセット"
|
||||
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:16
|
||||
#: users/templates/users/_msg_user_created.html:22
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:15
|
||||
#: users/templates/users/_msg_user_created.html:20
|
||||
msgid "This link is valid for 1 hour. After it expires"
|
||||
msgstr "このリンクは1時間有効です。有効期限が切れた後"
|
||||
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:17
|
||||
#: users/templates/users/_msg_user_created.html:23
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:16
|
||||
#: users/templates/users/_msg_user_created.html:21
|
||||
msgid "request new one"
|
||||
msgstr "新しいものを要求する"
|
||||
|
||||
@@ -3484,7 +3486,7 @@ msgstr "パスワードが正常に更新されました"
|
||||
msgid "Browser"
|
||||
msgstr "ブラウザ"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:12
|
||||
msgid ""
|
||||
"If the password update was not initiated by you, your account may have "
|
||||
"security issues"
|
||||
@@ -3492,8 +3494,8 @@ msgstr ""
|
||||
"パスワードの更新が開始されなかった場合、アカウントにセキュリティ上の問題があ"
|
||||
"る可能性があります"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:14
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:14
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:13
|
||||
msgid "If you have any questions, you can contact the administrator"
|
||||
msgstr "質問があれば、管理者に連絡できます"
|
||||
|
||||
@@ -3501,7 +3503,7 @@ msgstr "質問があれば、管理者に連絡できます"
|
||||
msgid "Your public key has just been successfully updated"
|
||||
msgstr "公開鍵が正常に更新されました"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:12
|
||||
msgid ""
|
||||
"If the public key update was not initiated by you, your account may have "
|
||||
"security issues"
|
||||
@@ -3580,7 +3582,7 @@ msgid "LAN"
|
||||
msgstr "ローカルエリアネットワーク"
|
||||
|
||||
#: authentication/views/base.py:70
|
||||
#: perms/templates/perms/_msg_permed_items_expire.html:21
|
||||
#: perms/templates/perms/_msg_permed_items_expire.html:20
|
||||
msgid "If you have any question, please contact the administrator"
|
||||
msgstr "質問があったら、管理者に連絡して下さい"
|
||||
|
||||
@@ -4467,7 +4469,7 @@ msgstr "Material"
|
||||
msgid "Material Type"
|
||||
msgstr "Material を選択してオプションを設定します。"
|
||||
|
||||
#: ops/models/job.py:545
|
||||
#: ops/models/job.py:555
|
||||
msgid "Job Execution"
|
||||
msgstr "ジョブ実行"
|
||||
|
||||
@@ -6617,7 +6619,7 @@ msgstr "使用中のストレージを削除できません"
|
||||
msgid "Command storages"
|
||||
msgstr "コマンドストア"
|
||||
|
||||
#: terminal/api/component/storage.py:82
|
||||
#: terminal/api/component/storage.py:82 xpack/plugins/cloud/manager.py:83
|
||||
msgid "Invalid"
|
||||
msgstr "無効"
|
||||
|
||||
@@ -7011,7 +7013,7 @@ msgstr "ログイン元"
|
||||
msgid "Replay"
|
||||
msgstr "リプレイ"
|
||||
|
||||
#: terminal/models/session/session.py:48 terminal/serializers/session.py:68
|
||||
#: terminal/models/session/session.py:48 terminal/serializers/session.py:78
|
||||
msgid "Command amount"
|
||||
msgstr "コマンド量"
|
||||
|
||||
@@ -7396,7 +7398,8 @@ msgstr "アクセスキー"
|
||||
msgid "Access key secret"
|
||||
msgstr "アクセスキーシークレット"
|
||||
|
||||
#: terminal/serializers/storage.py:68 xpack/plugins/cloud/models.py:258
|
||||
#: terminal/serializers/storage.py:68 xpack/plugins/cloud/manager.py:83
|
||||
#: xpack/plugins/cloud/models.py:258
|
||||
msgid "Region"
|
||||
msgstr "リージョン"
|
||||
|
||||
@@ -7453,6 +7456,14 @@ msgstr "インデックス"
|
||||
msgid "Doc type"
|
||||
msgstr "Docタイプ"
|
||||
|
||||
#: terminal/serializers/storage.py:258
|
||||
msgid "Store locally"
|
||||
msgstr "ローカルに保存する"
|
||||
|
||||
#: terminal/serializers/storage.py:259
|
||||
msgid "Do not save"
|
||||
msgstr "保存しない"
|
||||
|
||||
#: terminal/serializers/task.py:9
|
||||
msgid "Session id"
|
||||
msgstr "セッション"
|
||||
@@ -8216,7 +8227,7 @@ msgid "User password history"
|
||||
msgstr "ユーザーパスワード履歴"
|
||||
|
||||
#: users/notifications.py:55
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:17
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:16
|
||||
#: users/templates/users/reset_password.html:5
|
||||
#: users/templates/users/reset_password.html:6
|
||||
msgid "Reset password"
|
||||
@@ -8477,7 +8488,7 @@ msgstr ""
|
||||
msgid "Click here update password"
|
||||
msgstr "ここをクリック更新パスワード"
|
||||
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:16
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:15
|
||||
msgid "If your password has expired, please click the link below to"
|
||||
msgstr ""
|
||||
"パスワードの有効期限が切れている場合は、以下のリンクをクリックしてください"
|
||||
@@ -8861,11 +8872,6 @@ msgstr "そして"
|
||||
msgid "Or"
|
||||
msgstr "または"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:55 xpack/plugins/cloud/providers/gcp.py:64
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:34
|
||||
msgid "Account unavailable"
|
||||
msgstr "利用できないアカウント"
|
||||
|
||||
#: xpack/plugins/cloud/meta.py:9
|
||||
msgid "Cloud center"
|
||||
msgstr "クラウドセンター"
|
||||
@@ -9161,6 +9167,11 @@ msgstr "華東-上海"
|
||||
msgid "AP-Singapore"
|
||||
msgstr "アジア太平洋-シンガポール"
|
||||
|
||||
#: xpack/plugins/cloud/providers/gcp.py:64
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:34
|
||||
msgid "Account unavailable"
|
||||
msgstr "利用できないアカウント"
|
||||
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:44
|
||||
msgid "CN North-Beijing1"
|
||||
msgstr "華北-北京1"
|
||||
@@ -9413,10 +9424,6 @@ msgstr "エンタープライズプロフェッショナル版"
|
||||
msgid "Ultimate edition"
|
||||
msgstr "エンタープライズ・フラッグシップ・エディション"
|
||||
|
||||
#~ msgid "Account history"
|
||||
#~ msgstr "アカウント履歴"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Operate log"
|
||||
#~ msgid "Operate log id"
|
||||
#~ msgstr "ログの操作"
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:74
|
||||
msgid "Auto node classification"
|
||||
msgstr "オートノード分類"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fb60a2ebb525e5aaa2d2c64fb4956fcc56f1b608a6dfb186513e743e226af261
|
||||
size 146172
|
||||
oid sha256:4517c6a7464c68f949912b97c8a9abcc766ca19e32267a1d1da3f0e012471c1a
|
||||
size 146255
|
||||
|
||||
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-08-20 14:54+0800\n"
|
||||
"POT-Creation-Date: 2024-10-22 17:33+0800\n"
|
||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||
@@ -796,8 +796,8 @@ msgstr "类别"
|
||||
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
|
||||
#: terminal/models/component/storage.py:57
|
||||
#: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29
|
||||
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:271
|
||||
#: terminal/serializers/storage.py:283 tickets/models/comment.py:26
|
||||
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:274
|
||||
#: terminal/serializers/storage.py:286 tickets/models/comment.py:26
|
||||
#: tickets/models/flow.py:56 tickets/models/ticket/apply_application.py:16
|
||||
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:53
|
||||
#: tickets/serializers/ticket/ticket.py:19
|
||||
@@ -1415,12 +1415,13 @@ msgid "Unable to connect to port {port} on {address}"
|
||||
msgstr "无法连接到 {port} 上的端口 {address}"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:58
|
||||
#: authentication/middleware.py:93 xpack/plugins/cloud/providers/fc.py:47
|
||||
#: authentication/middleware.py:94 xpack/plugins/cloud/providers/fc.py:47
|
||||
msgid "Authentication failed"
|
||||
msgstr "认证失败"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:60
|
||||
#: assets/automations/ping_gateway/manager.py:86 terminal/const.py:102
|
||||
#: assets/automations/ping_gateway/manager.py:86 audits/backends/__init__.py:29
|
||||
#: terminal/const.py:102
|
||||
msgid "Connect failed"
|
||||
msgstr "连接失败"
|
||||
|
||||
@@ -1968,7 +1969,8 @@ msgstr "标签"
|
||||
msgid "New node"
|
||||
msgstr "新节点"
|
||||
|
||||
#: assets/models/node.py:467 audits/backends/db.py:68 audits/backends/db.py:69
|
||||
#: assets/models/node.py:467 audits/backends/db.py:64 audits/backends/db.py:65
|
||||
#: audits/backends/es.py:66 audits/backends/es.py:67
|
||||
msgid "empty"
|
||||
msgstr "空"
|
||||
|
||||
@@ -2388,15 +2390,15 @@ msgstr "日志审计"
|
||||
msgid "The text content is too long. Use Elasticsearch to store operation logs"
|
||||
msgstr "文字内容太长。请使用 Elasticsearch 存储操作日志"
|
||||
|
||||
#: audits/backends/db.py:61
|
||||
#: audits/backends/db.py:58
|
||||
msgid "labels"
|
||||
msgstr "标签"
|
||||
|
||||
#: audits/backends/db.py:62
|
||||
#: audits/backends/db.py:59
|
||||
msgid "operate_log_id"
|
||||
msgstr "操作日志ID"
|
||||
|
||||
#: audits/backends/db.py:94
|
||||
#: audits/backends/db.py:90
|
||||
msgid "Tips"
|
||||
msgstr "提示"
|
||||
|
||||
@@ -2479,7 +2481,7 @@ msgstr "结束"
|
||||
#: audits/const.py:46 settings/serializers/terminal.py:6
|
||||
#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:175
|
||||
#: terminal/models/virtualapp/provider.py:14 terminal/serializers/session.py:55
|
||||
#: terminal/serializers/session.py:69
|
||||
#: terminal/serializers/session.py:79
|
||||
msgid "Terminal"
|
||||
msgstr "终端"
|
||||
|
||||
@@ -2504,11 +2506,11 @@ msgstr "任务"
|
||||
msgid "-"
|
||||
msgstr "-"
|
||||
|
||||
#: audits/handler.py:116
|
||||
#: audits/handler.py:107
|
||||
msgid "Yes"
|
||||
msgstr "是"
|
||||
|
||||
#: audits/handler.py:116
|
||||
#: audits/handler.py:107
|
||||
msgid "No"
|
||||
msgstr "否"
|
||||
|
||||
@@ -3108,7 +3110,7 @@ msgstr "设置手机号码启用"
|
||||
msgid "Clear phone number to disable"
|
||||
msgstr "清空手机号码禁用"
|
||||
|
||||
#: authentication/middleware.py:94 settings/utils/ldap.py:679
|
||||
#: authentication/middleware.py:95 settings/utils/ldap.py:679
|
||||
msgid "Authentication failed (before login check failed): {}"
|
||||
msgstr "认证失败 (登录前检查失败): {}"
|
||||
|
||||
@@ -3387,7 +3389,7 @@ msgstr "你的账号存在异地登录行为,请关注。"
|
||||
msgid "Login time"
|
||||
msgstr "登录日期"
|
||||
|
||||
#: authentication/templates/authentication/_msg_different_city.html:16
|
||||
#: authentication/templates/authentication/_msg_different_city.html:14
|
||||
msgid ""
|
||||
"If you suspect that the login behavior is abnormal, please modify the "
|
||||
"account password in time."
|
||||
@@ -3411,13 +3413,13 @@ msgstr "请点击下面链接重置密码, 如果不是您申请的,请关
|
||||
msgid "Click here reset password"
|
||||
msgstr "点击这里重置密码"
|
||||
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:16
|
||||
#: users/templates/users/_msg_user_created.html:22
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:15
|
||||
#: users/templates/users/_msg_user_created.html:20
|
||||
msgid "This link is valid for 1 hour. After it expires"
|
||||
msgstr "这个链接有效期1小时, 超过时间您可以"
|
||||
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:17
|
||||
#: users/templates/users/_msg_user_created.html:23
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:16
|
||||
#: users/templates/users/_msg_user_created.html:21
|
||||
msgid "request new one"
|
||||
msgstr "重新申请"
|
||||
|
||||
@@ -3446,14 +3448,14 @@ msgstr "你的密码刚刚成功更新"
|
||||
msgid "Browser"
|
||||
msgstr "浏览器"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:12
|
||||
msgid ""
|
||||
"If the password update was not initiated by you, your account may have "
|
||||
"security issues"
|
||||
msgstr "如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:14
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:14
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:13
|
||||
msgid "If you have any questions, you can contact the administrator"
|
||||
msgstr "如果有疑问或需求,请联系系统管理员"
|
||||
|
||||
@@ -3461,7 +3463,7 @@ msgstr "如果有疑问或需求,请联系系统管理员"
|
||||
msgid "Your public key has just been successfully updated"
|
||||
msgstr "你的公钥刚刚成功更新"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:12
|
||||
msgid ""
|
||||
"If the public key update was not initiated by you, your account may have "
|
||||
"security issues"
|
||||
@@ -3534,7 +3536,7 @@ msgid "LAN"
|
||||
msgstr "局域网"
|
||||
|
||||
#: authentication/views/base.py:70
|
||||
#: perms/templates/perms/_msg_permed_items_expire.html:21
|
||||
#: perms/templates/perms/_msg_permed_items_expire.html:20
|
||||
msgid "If you have any question, please contact the administrator"
|
||||
msgstr "如果有疑问或需求,请联系系统管理员"
|
||||
|
||||
@@ -4408,7 +4410,7 @@ msgstr "Material"
|
||||
msgid "Material Type"
|
||||
msgstr "Material 类型"
|
||||
|
||||
#: ops/models/job.py:545
|
||||
#: ops/models/job.py:555
|
||||
msgid "Job Execution"
|
||||
msgstr "作业执行"
|
||||
|
||||
@@ -6514,7 +6516,7 @@ msgstr "不允许删除正在使用的存储配置"
|
||||
msgid "Command storages"
|
||||
msgstr "命令存储"
|
||||
|
||||
#: terminal/api/component/storage.py:82
|
||||
#: terminal/api/component/storage.py:82 xpack/plugins/cloud/manager.py:83
|
||||
msgid "Invalid"
|
||||
msgstr "无效"
|
||||
|
||||
@@ -6908,7 +6910,7 @@ msgstr "登录来源"
|
||||
msgid "Replay"
|
||||
msgstr "回放"
|
||||
|
||||
#: terminal/models/session/session.py:48 terminal/serializers/session.py:68
|
||||
#: terminal/models/session/session.py:48 terminal/serializers/session.py:78
|
||||
msgid "Command amount"
|
||||
msgstr "命令数量"
|
||||
|
||||
@@ -7286,7 +7288,8 @@ msgstr "Access key ID(AK)"
|
||||
msgid "Access key secret"
|
||||
msgstr "Access key secret(SK)"
|
||||
|
||||
#: terminal/serializers/storage.py:68 xpack/plugins/cloud/models.py:258
|
||||
#: terminal/serializers/storage.py:68 xpack/plugins/cloud/manager.py:83
|
||||
#: xpack/plugins/cloud/models.py:258
|
||||
msgid "Region"
|
||||
msgstr "地域"
|
||||
|
||||
@@ -7343,6 +7346,14 @@ msgstr "索引"
|
||||
msgid "Doc type"
|
||||
msgstr "文档类型"
|
||||
|
||||
#: terminal/serializers/storage.py:258
|
||||
msgid "Store locally"
|
||||
msgstr "本地存储"
|
||||
|
||||
#: terminal/serializers/storage.py:259
|
||||
msgid "Do not save"
|
||||
msgstr "不保存"
|
||||
|
||||
#: terminal/serializers/task.py:9
|
||||
msgid "Session id"
|
||||
msgstr "会话 ID"
|
||||
@@ -8100,7 +8111,7 @@ msgid "User password history"
|
||||
msgstr "用户密码历史"
|
||||
|
||||
#: users/notifications.py:55
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:17
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:16
|
||||
#: users/templates/users/reset_password.html:5
|
||||
#: users/templates/users/reset_password.html:6
|
||||
msgid "Reset password"
|
||||
@@ -8355,7 +8366,7 @@ msgstr "为了您的账号安全,请点击下面的链接及时更新密码"
|
||||
msgid "Click here update password"
|
||||
msgstr "点击这里更新密码"
|
||||
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:16
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:15
|
||||
msgid "If your password has expired, please click the link below to"
|
||||
msgstr "如果你的密码已过期,请点击"
|
||||
|
||||
@@ -8728,11 +8739,6 @@ msgstr "与"
|
||||
msgid "Or"
|
||||
msgstr "或"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:55 xpack/plugins/cloud/providers/gcp.py:64
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:34
|
||||
msgid "Account unavailable"
|
||||
msgstr "账号无效"
|
||||
|
||||
#: xpack/plugins/cloud/meta.py:9
|
||||
msgid "Cloud center"
|
||||
msgstr "云管中心"
|
||||
@@ -8768,7 +8774,7 @@ msgstr "IP网段组"
|
||||
#: xpack/plugins/cloud/models.py:100
|
||||
#: xpack/plugins/cloud/serializers/task.py:167
|
||||
msgid "Sync IP type"
|
||||
msgstr "同步IP类型"
|
||||
msgstr "同步 IP 类型"
|
||||
|
||||
#: xpack/plugins/cloud/models.py:103
|
||||
#: xpack/plugins/cloud/serializers/task.py:185
|
||||
@@ -9028,6 +9034,11 @@ msgstr "华东-上海"
|
||||
msgid "AP-Singapore"
|
||||
msgstr "亚太-新加坡"
|
||||
|
||||
#: xpack/plugins/cloud/providers/gcp.py:64
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:34
|
||||
msgid "Account unavailable"
|
||||
msgstr "账号无效"
|
||||
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:44
|
||||
msgid "CN North-Beijing1"
|
||||
msgstr "华北-北京1"
|
||||
@@ -9277,5 +9288,6 @@ msgstr "企业专业版"
|
||||
msgid "Ultimate edition"
|
||||
msgstr "企业旗舰版"
|
||||
|
||||
#~ msgid "Account history"
|
||||
#~ msgstr "账号历史"
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:74
|
||||
msgid "Auto node classification"
|
||||
msgstr "自动节点分类"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4aaedb4c31b1d11ddf63e5d0a27958ee5d42e78e2ce255746b8134d18ffc990f
|
||||
size 146283
|
||||
oid sha256:7d4d2709e597e055072474f08be2f363d43df239240051024a77213ba48ecfac
|
||||
size 146366
|
||||
|
||||
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-08-20 14:54+0800\n"
|
||||
"POT-Creation-Date: 2024-10-22 17:33+0800\n"
|
||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||
@@ -798,8 +798,8 @@ msgstr "類別"
|
||||
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
|
||||
#: terminal/models/component/storage.py:57
|
||||
#: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29
|
||||
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:271
|
||||
#: terminal/serializers/storage.py:283 tickets/models/comment.py:26
|
||||
#: terminal/serializers/session.py:23 terminal/serializers/storage.py:274
|
||||
#: terminal/serializers/storage.py:286 tickets/models/comment.py:26
|
||||
#: tickets/models/flow.py:56 tickets/models/ticket/apply_application.py:16
|
||||
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:53
|
||||
#: tickets/serializers/ticket/ticket.py:19
|
||||
@@ -1417,12 +1417,13 @@ msgid "Unable to connect to port {port} on {address}"
|
||||
msgstr "無法連接到 {port} 上的埠 {address}"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:58
|
||||
#: authentication/middleware.py:93 xpack/plugins/cloud/providers/fc.py:47
|
||||
#: authentication/middleware.py:94 xpack/plugins/cloud/providers/fc.py:47
|
||||
msgid "Authentication failed"
|
||||
msgstr "認證失敗"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:60
|
||||
#: assets/automations/ping_gateway/manager.py:86 terminal/const.py:102
|
||||
#: assets/automations/ping_gateway/manager.py:86 audits/backends/__init__.py:29
|
||||
#: terminal/const.py:102
|
||||
msgid "Connect failed"
|
||||
msgstr "連接失敗"
|
||||
|
||||
@@ -1970,7 +1971,8 @@ msgstr "標籤"
|
||||
msgid "New node"
|
||||
msgstr "新節點"
|
||||
|
||||
#: assets/models/node.py:467 audits/backends/db.py:68 audits/backends/db.py:69
|
||||
#: assets/models/node.py:467 audits/backends/db.py:64 audits/backends/db.py:65
|
||||
#: audits/backends/es.py:66 audits/backends/es.py:67
|
||||
msgid "empty"
|
||||
msgstr "空"
|
||||
|
||||
@@ -2390,15 +2392,15 @@ msgstr "日誌審計"
|
||||
msgid "The text content is too long. Use Elasticsearch to store operation logs"
|
||||
msgstr "文字內容太長。請使用 Elasticsearch 儲存操作日誌"
|
||||
|
||||
#: audits/backends/db.py:61
|
||||
#: audits/backends/db.py:58
|
||||
msgid "labels"
|
||||
msgstr "標籤"
|
||||
|
||||
#: audits/backends/db.py:62
|
||||
#: audits/backends/db.py:59
|
||||
msgid "operate_log_id"
|
||||
msgstr "操作日誌ID"
|
||||
|
||||
#: audits/backends/db.py:94
|
||||
#: audits/backends/db.py:90
|
||||
msgid "Tips"
|
||||
msgstr "提示"
|
||||
|
||||
@@ -2481,7 +2483,7 @@ msgstr "結束"
|
||||
#: audits/const.py:46 settings/serializers/terminal.py:6
|
||||
#: terminal/models/applet/host.py:26 terminal/models/component/terminal.py:175
|
||||
#: terminal/models/virtualapp/provider.py:14 terminal/serializers/session.py:55
|
||||
#: terminal/serializers/session.py:69
|
||||
#: terminal/serializers/session.py:79
|
||||
msgid "Terminal"
|
||||
msgstr "終端"
|
||||
|
||||
@@ -2506,11 +2508,11 @@ msgstr "任務"
|
||||
msgid "-"
|
||||
msgstr "-"
|
||||
|
||||
#: audits/handler.py:116
|
||||
#: audits/handler.py:107
|
||||
msgid "Yes"
|
||||
msgstr "是"
|
||||
|
||||
#: audits/handler.py:116
|
||||
#: audits/handler.py:107
|
||||
msgid "No"
|
||||
msgstr "否"
|
||||
|
||||
@@ -3110,7 +3112,7 @@ msgstr "設置手機號碼啟用"
|
||||
msgid "Clear phone number to disable"
|
||||
msgstr "清空手機號碼禁用"
|
||||
|
||||
#: authentication/middleware.py:94 settings/utils/ldap.py:679
|
||||
#: authentication/middleware.py:95 settings/utils/ldap.py:679
|
||||
msgid "Authentication failed (before login check failed): {}"
|
||||
msgstr "認證失敗 (登錄前檢查失敗): {}"
|
||||
|
||||
@@ -3389,7 +3391,7 @@ msgstr "你的帳號存在異地登入行為,請關注。"
|
||||
msgid "Login time"
|
||||
msgstr "登錄日期"
|
||||
|
||||
#: authentication/templates/authentication/_msg_different_city.html:16
|
||||
#: authentication/templates/authentication/_msg_different_city.html:14
|
||||
msgid ""
|
||||
"If you suspect that the login behavior is abnormal, please modify the "
|
||||
"account password in time."
|
||||
@@ -3413,13 +3415,13 @@ msgstr "請點擊下面連結重設密碼, 如果不是您申請的,請關
|
||||
msgid "Click here reset password"
|
||||
msgstr "點擊這裡重設密碼"
|
||||
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:16
|
||||
#: users/templates/users/_msg_user_created.html:22
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:15
|
||||
#: users/templates/users/_msg_user_created.html:20
|
||||
msgid "This link is valid for 1 hour. After it expires"
|
||||
msgstr "這個連結有效期1小時, 超過時間您可以"
|
||||
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:17
|
||||
#: users/templates/users/_msg_user_created.html:23
|
||||
#: authentication/templates/authentication/_msg_reset_password.html:16
|
||||
#: users/templates/users/_msg_user_created.html:21
|
||||
msgid "request new one"
|
||||
msgstr "重新申請"
|
||||
|
||||
@@ -3448,14 +3450,14 @@ msgstr "你的密碼剛剛成功更新"
|
||||
msgid "Browser"
|
||||
msgstr "瀏覽器"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:12
|
||||
msgid ""
|
||||
"If the password update was not initiated by you, your account may have "
|
||||
"security issues"
|
||||
msgstr "如果這次密碼更新不是由你發起的,那麼你的帳號可能存在安全問題"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:14
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:14
|
||||
#: authentication/templates/authentication/_msg_rest_password_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:13
|
||||
msgid "If you have any questions, you can contact the administrator"
|
||||
msgstr "如果有疑問或需求,請聯絡系統管理員"
|
||||
|
||||
@@ -3463,7 +3465,7 @@ msgstr "如果有疑問或需求,請聯絡系統管理員"
|
||||
msgid "Your public key has just been successfully updated"
|
||||
msgstr "你的公鑰剛剛成功更新"
|
||||
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:13
|
||||
#: authentication/templates/authentication/_msg_rest_public_key_success.html:12
|
||||
msgid ""
|
||||
"If the public key update was not initiated by you, your account may have "
|
||||
"security issues"
|
||||
@@ -3536,7 +3538,7 @@ msgid "LAN"
|
||||
msgstr "區域網路"
|
||||
|
||||
#: authentication/views/base.py:70
|
||||
#: perms/templates/perms/_msg_permed_items_expire.html:21
|
||||
#: perms/templates/perms/_msg_permed_items_expire.html:20
|
||||
msgid "If you have any question, please contact the administrator"
|
||||
msgstr "如果有疑問或需求,請聯絡系統管理員"
|
||||
|
||||
@@ -4409,7 +4411,7 @@ msgstr "Material"
|
||||
msgid "Material Type"
|
||||
msgstr "Material 類型"
|
||||
|
||||
#: ops/models/job.py:545
|
||||
#: ops/models/job.py:555
|
||||
msgid "Job Execution"
|
||||
msgstr "作業執行"
|
||||
|
||||
@@ -6515,7 +6517,7 @@ msgstr "不允許刪除正在使用的儲存配置"
|
||||
msgid "Command storages"
|
||||
msgstr "命令儲存"
|
||||
|
||||
#: terminal/api/component/storage.py:82
|
||||
#: terminal/api/component/storage.py:82 xpack/plugins/cloud/manager.py:83
|
||||
msgid "Invalid"
|
||||
msgstr "無效"
|
||||
|
||||
@@ -6909,7 +6911,7 @@ msgstr "登錄來源"
|
||||
msgid "Replay"
|
||||
msgstr "重播"
|
||||
|
||||
#: terminal/models/session/session.py:48 terminal/serializers/session.py:68
|
||||
#: terminal/models/session/session.py:48 terminal/serializers/session.py:78
|
||||
msgid "Command amount"
|
||||
msgstr "命令數量"
|
||||
|
||||
@@ -7287,7 +7289,8 @@ msgstr "Access key ID(AK)"
|
||||
msgid "Access key secret"
|
||||
msgstr "Access key secret(SK)"
|
||||
|
||||
#: terminal/serializers/storage.py:68 xpack/plugins/cloud/models.py:258
|
||||
#: terminal/serializers/storage.py:68 xpack/plugins/cloud/manager.py:83
|
||||
#: xpack/plugins/cloud/models.py:258
|
||||
msgid "Region"
|
||||
msgstr "地域"
|
||||
|
||||
@@ -7344,6 +7347,14 @@ msgstr "索引"
|
||||
msgid "Doc type"
|
||||
msgstr "文件類型"
|
||||
|
||||
#: terminal/serializers/storage.py:258
|
||||
msgid "Store locally"
|
||||
msgstr "本地儲存"
|
||||
|
||||
#: terminal/serializers/storage.py:259
|
||||
msgid "Do not save"
|
||||
msgstr "不儲存"
|
||||
|
||||
#: terminal/serializers/task.py:9
|
||||
msgid "Session id"
|
||||
msgstr "會話 ID"
|
||||
@@ -8101,7 +8112,7 @@ msgid "User password history"
|
||||
msgstr "用戶密碼歷史"
|
||||
|
||||
#: users/notifications.py:55
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:17
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:16
|
||||
#: users/templates/users/reset_password.html:5
|
||||
#: users/templates/users/reset_password.html:6
|
||||
msgid "Reset password"
|
||||
@@ -8356,7 +8367,7 @@ msgstr "為了您的帳號安全,請點擊下面的連結及時更新密碼"
|
||||
msgid "Click here update password"
|
||||
msgstr "點擊這裡更新密碼"
|
||||
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:16
|
||||
#: users/templates/users/_msg_password_expire_reminder.html:15
|
||||
msgid "If your password has expired, please click the link below to"
|
||||
msgstr "如果你的密碼已過期,請點擊"
|
||||
|
||||
@@ -8729,11 +8740,6 @@ msgstr "與"
|
||||
msgid "Or"
|
||||
msgstr "或"
|
||||
|
||||
#: xpack/plugins/cloud/manager.py:55 xpack/plugins/cloud/providers/gcp.py:64
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:34
|
||||
msgid "Account unavailable"
|
||||
msgstr "帳號無效"
|
||||
|
||||
#: xpack/plugins/cloud/meta.py:9
|
||||
msgid "Cloud center"
|
||||
msgstr "雲管中心"
|
||||
@@ -8769,7 +8775,7 @@ msgstr "IP網段組"
|
||||
#: xpack/plugins/cloud/models.py:100
|
||||
#: xpack/plugins/cloud/serializers/task.py:167
|
||||
msgid "Sync IP type"
|
||||
msgstr "同步IP類型"
|
||||
msgstr "同步 IP 類型"
|
||||
|
||||
#: xpack/plugins/cloud/models.py:103
|
||||
#: xpack/plugins/cloud/serializers/task.py:185
|
||||
@@ -9029,6 +9035,11 @@ msgstr "華東-上海"
|
||||
msgid "AP-Singapore"
|
||||
msgstr "亞太-新加坡"
|
||||
|
||||
#: xpack/plugins/cloud/providers/gcp.py:64
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:34
|
||||
msgid "Account unavailable"
|
||||
msgstr "帳號無效"
|
||||
|
||||
#: xpack/plugins/cloud/providers/huaweicloud.py:44
|
||||
msgid "CN North-Beijing1"
|
||||
msgstr "華北-北京1"
|
||||
@@ -9278,5 +9289,6 @@ msgstr "企業專業版"
|
||||
msgid "Ultimate edition"
|
||||
msgstr "企業旗艦版"
|
||||
|
||||
#~ msgid "Account history"
|
||||
#~ msgstr "帳號歷史"
|
||||
#: xpack/plugins/cloud/serializers/account_attrs.py:74
|
||||
msgid "Auto node classification"
|
||||
msgstr "自動節點分類"
|
||||
|
||||
@@ -14,7 +14,8 @@ class SiteMessageUtil:
|
||||
def send_msg(cls, subject, message, user_ids=(), group_ids=(),
|
||||
sender=None, is_broadcast=False):
|
||||
if not any((user_ids, group_ids, is_broadcast)):
|
||||
raise ValueError('No recipient is specified')
|
||||
logger.warning(f'No recipient is specified, message subject: {subject}')
|
||||
return
|
||||
|
||||
with transaction.atomic():
|
||||
site_msg = SiteMessageModel.objects.create(
|
||||
|
||||
@@ -39,9 +39,10 @@ class AdHocRunner:
|
||||
def check_module(self):
|
||||
if self.module not in self.cmd_modules_choices:
|
||||
return
|
||||
if self.module_args and self.module_args.split()[0] in settings.SECURITY_COMMAND_BLACKLIST:
|
||||
command = self.module_args
|
||||
if command and set(command.split()).intersection(set(settings.SECURITY_COMMAND_BLACKLIST)):
|
||||
raise CommandInBlackListException(
|
||||
"Command is rejected by black list: {}".format(self.module_args.split()[0]))
|
||||
"Command is rejected by black list: {}".format(self.module_args))
|
||||
|
||||
def set_local_connection(self):
|
||||
if self.job_module in self.need_local_connection_modules_choices:
|
||||
|
||||
@@ -478,6 +478,16 @@ class JobExecution(JMSOrgBaseModel):
|
||||
for acl in acls:
|
||||
if self.match_command_group(acl, asset):
|
||||
break
|
||||
command = self.current_job.args
|
||||
if command and set(command.split()).intersection(set(settings.SECURITY_COMMAND_BLACKLIST)):
|
||||
CommandExecutionAlert({
|
||||
"assets": self.current_job.assets.all(),
|
||||
"input": self.material,
|
||||
"risk_level": RiskLevelChoices.reject,
|
||||
"user": self.creator,
|
||||
}).publish_async()
|
||||
raise CommandInBlackListException(
|
||||
"Command is rejected by black list: {}".format(self.current_job.args))
|
||||
|
||||
def check_danger_keywords(self):
|
||||
lines = self.job.playbook.check_dangerous_keywords()
|
||||
|
||||
@@ -7,10 +7,12 @@ from celery.exceptions import SoftTimeLimitExceeded
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
from django.conf import settings
|
||||
|
||||
from common.const.crontab import CRONTAB_AT_AM_TWO
|
||||
from common.utils import get_logger, get_object_or_none, get_log_keep_day
|
||||
from ops.celery import app
|
||||
from ops.const import Types
|
||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
from .celery.decorator import (
|
||||
register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic
|
||||
@@ -56,7 +58,8 @@ def run_ops_job(job_id):
|
||||
if not job:
|
||||
logger.error("Did not get the execution: {}".format(job_id))
|
||||
return
|
||||
|
||||
if not settings.SECURITY_COMMAND_EXECUTION and job.type != Types.upload_file:
|
||||
return
|
||||
with tmp_to_org(job.org):
|
||||
execution = job.create_execution()
|
||||
execution.creator = job.creator
|
||||
@@ -83,6 +86,8 @@ def run_ops_job_execution(execution_id, **kwargs):
|
||||
if not execution:
|
||||
logger.error("Did not get the execution: {}".format(execution_id))
|
||||
return
|
||||
if not settings.SECURITY_COMMAND_EXECUTION and execution.job.type != Types.upload_file:
|
||||
return
|
||||
_run_ops_job_execution(execution)
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class OrgSerializer(ModelSerializer):
|
||||
class CurrentOrgSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = ['id', 'name', 'is_default', 'is_root', 'comment']
|
||||
fields = ['id', 'name', 'is_default', 'is_root', 'is_system', 'comment']
|
||||
|
||||
|
||||
class CurrentOrgDefault:
|
||||
|
||||
@@ -24,13 +24,13 @@ def get_org_from_request(request):
|
||||
# 其次session
|
||||
if not oid:
|
||||
oid = request.session.get("oid")
|
||||
|
||||
|
||||
if oid and oid.lower() == 'default':
|
||||
return Organization.default()
|
||||
|
||||
if oid and oid.lower() == 'root':
|
||||
return Organization.root()
|
||||
|
||||
|
||||
if oid and oid.lower() == 'system':
|
||||
return Organization.system()
|
||||
|
||||
@@ -39,14 +39,14 @@ def get_org_from_request(request):
|
||||
if org and org.internal:
|
||||
# 内置组织直接返回
|
||||
return org
|
||||
|
||||
|
||||
if not settings.XPACK_ENABLED:
|
||||
# 社区版用户只能使用默认组织
|
||||
return Organization.default()
|
||||
|
||||
|
||||
if not org and request.user.is_authenticated:
|
||||
# 企业版用户优先从自己有权限的组织中获取
|
||||
org = request.user.orgs.first()
|
||||
org = request.user.orgs.exclude(id=Organization.SYSTEM_ID).first()
|
||||
|
||||
if not org:
|
||||
org = Organization.default()
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.request import Request
|
||||
from django.core.cache import cache
|
||||
|
||||
from common.exceptions import JMSObjectDoesNotExist
|
||||
from common.utils import is_uuid
|
||||
from common.utils.http import is_true
|
||||
from common.utils import lazyproperty, is_uuid
|
||||
from rbac.permissions import RBACPermission
|
||||
from users.models import User
|
||||
from perms.utils import UserPermTreeRefreshUtil
|
||||
|
||||
__all__ = ['SelfOrPKUserMixin']
|
||||
|
||||
__all__ = ['SelfOrPKUserMixin', 'RebuildTreeMixin']
|
||||
|
||||
|
||||
class SelfOrPKUserMixin:
|
||||
@@ -57,3 +61,33 @@ class SelfOrPKUserMixin:
|
||||
|
||||
def request_user_is_self(self):
|
||||
return self.kwargs.get('user') in ['my', 'self']
|
||||
|
||||
|
||||
class RebuildTreeMixin:
|
||||
user: User
|
||||
request: Request
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
UserPermTreeRefreshUtil(self.user).refresh_if_need(force=self.is_force_refresh_tree)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@lazyproperty
|
||||
def is_force_refresh_tree(self):
|
||||
force = is_true(self.request.query_params.get('rebuild_tree'))
|
||||
if not force:
|
||||
force = self.compute_is_force_refresh()
|
||||
return force
|
||||
|
||||
def compute_is_force_refresh(self):
|
||||
""" 5s 内连续刷新三次转为强制刷新 """
|
||||
force_timeout = 5
|
||||
force_max_count = 3
|
||||
force_cache_key = '{user_id}:{path}'.format(user_id=self.user.id, path=self.request.path)
|
||||
count = cache.get(force_cache_key, 1)
|
||||
if count >= force_max_count:
|
||||
force = True
|
||||
cache.delete(force_cache_key)
|
||||
else:
|
||||
force = False
|
||||
cache.set(force_cache_key, count + 1, force_timeout)
|
||||
return force
|
||||
|
||||
@@ -8,7 +8,7 @@ from assets.models import Node
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from perms import serializers
|
||||
from perms.utils import UserPermNodeUtil
|
||||
from .mixin import SelfOrPKUserMixin
|
||||
from .mixin import SelfOrPKUserMixin, RebuildTreeMixin
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -18,7 +18,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView):
|
||||
class BaseUserPermedNodesApi(SelfOrPKUserMixin, RebuildTreeMixin, ListAPIView):
|
||||
serializer_class = serializers.NodePermedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
@@ -4,7 +4,7 @@ from rest_framework.response import Response
|
||||
from assets.api import SerializeToTreeNodeMixin
|
||||
from assets.models import Asset
|
||||
from common.utils import get_logger
|
||||
from .mixin import RebuildTreeMixin
|
||||
from ..mixin import RebuildTreeMixin
|
||||
from ..assets import UserAllPermedAssetsApi
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
from django.core.cache import cache
|
||||
|
||||
from rest_framework.request import Request
|
||||
|
||||
from common.utils.http import is_true
|
||||
from common.utils import lazyproperty
|
||||
from perms.utils import UserPermTreeRefreshUtil
|
||||
from users.models import User
|
||||
|
||||
__all__ = ['RebuildTreeMixin']
|
||||
|
||||
|
||||
class RebuildTreeMixin:
|
||||
user: User
|
||||
request: Request
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
UserPermTreeRefreshUtil(self.user).refresh_if_need(force=self.is_force_refresh_tree)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@lazyproperty
|
||||
def is_force_refresh_tree(self):
|
||||
force = is_true(self.request.query_params.get('rebuild_tree'))
|
||||
if not force:
|
||||
force = self.compute_is_force_refresh()
|
||||
return force
|
||||
|
||||
def compute_is_force_refresh(self):
|
||||
""" 5s 内连续刷新三次转为强制刷新 """
|
||||
force_timeout = 5
|
||||
force_max_count = 3
|
||||
force_cache_key = '{user_id}:{path}'.format(user_id=self.user.id, path=self.request.path)
|
||||
count = cache.get(force_cache_key, 1)
|
||||
if count >= force_max_count:
|
||||
force = True
|
||||
cache.delete(force_cache_key)
|
||||
else:
|
||||
force = False
|
||||
cache.set(force_cache_key, count + 1, force_timeout)
|
||||
return force
|
||||
@@ -3,7 +3,6 @@ from rest_framework.response import Response
|
||||
from assets.api import SerializeToTreeNodeMixin
|
||||
from common.utils import get_logger
|
||||
|
||||
from .mixin import RebuildTreeMixin
|
||||
from ..nodes import (
|
||||
UserAllPermedNodesApi,
|
||||
UserPermedNodeChildrenApi,
|
||||
@@ -17,7 +16,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
class NodeTreeMixin(RebuildTreeMixin, SerializeToTreeNodeMixin):
|
||||
class NodeTreeMixin(SerializeToTreeNodeMixin):
|
||||
filter_queryset: callable
|
||||
get_queryset: callable
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ from perms.hands import Node
|
||||
from perms.models import PermNode
|
||||
from perms.utils import PermAssetDetailUtil, UserPermNodeUtil
|
||||
from perms.utils import UserPermAssetUtil
|
||||
from .mixin import RebuildTreeMixin
|
||||
from ..mixin import SelfOrPKUserMixin
|
||||
from ..mixin import SelfOrPKUserMixin, RebuildTreeMixin
|
||||
|
||||
__all__ = [
|
||||
'UserGrantedK8sAsTreeApi',
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<br/>
|
||||
-
|
||||
<br>
|
||||
<p>
|
||||
{% trans 'If you have any question, please contact the administrator' %}
|
||||
</p>
|
||||
|
||||
@@ -9,7 +9,8 @@ from django.db.utils import ProgrammingError, OperationalError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.db.models import JMSBaseModel
|
||||
from common.utils import signer, get_logger
|
||||
from common.utils import get_logger
|
||||
from common.db.utils import Encryptor
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -51,7 +52,7 @@ class Setting(models.Model):
|
||||
try:
|
||||
value = self.value
|
||||
if self.encrypted:
|
||||
value = signer.unsign(value)
|
||||
value = Encryptor(value).decrypt()
|
||||
if not value:
|
||||
return None
|
||||
value = json.loads(value)
|
||||
@@ -64,7 +65,7 @@ class Setting(models.Model):
|
||||
try:
|
||||
v = json.dumps(item)
|
||||
if self.encrypted:
|
||||
v = signer.sign(v)
|
||||
v = Encryptor(v).encrypt()
|
||||
self.value = v
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError("Json dump error: {}".format(str(e)))
|
||||
|
||||
@@ -30,6 +30,7 @@ class PrivateSettingSerializer(PublicSettingSerializer):
|
||||
SECURITY_LUNA_REMEMBER_AUTH = serializers.BooleanField()
|
||||
SECURITY_WATERMARK_ENABLED = serializers.BooleanField()
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = serializers.BooleanField()
|
||||
VIEW_ASSET_ONLINE_SESSION_INFO = serializers.BooleanField()
|
||||
PASSWORD_RULE = serializers.DictField()
|
||||
SECURITY_SESSION_SHARE = serializers.BooleanField()
|
||||
XPACK_LICENSE_IS_VALID = serializers.BooleanField()
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import logging
|
||||
from django.db.models import Q
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_filters import rest_framework as filters
|
||||
from rest_framework import generics
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView, Response
|
||||
from django_filters import rest_framework as filters
|
||||
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.api import JMSBulkModelViewSet
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.exceptions import JMSException
|
||||
from common.permissions import WithBootstrapToken
|
||||
from common.permissions import WithBootstrapToken, IsServiceAccount
|
||||
from jumpserver.conf import ConfigCrypto
|
||||
from terminal import serializers
|
||||
from terminal.models import Terminal
|
||||
|
||||
__all__ = [
|
||||
'TerminalViewSet', 'TerminalConfig',
|
||||
'TerminalRegistrationApi',
|
||||
'TerminalRegistrationApi', 'EncryptedTerminalConfig'
|
||||
]
|
||||
logger = logging.getLogger(__file__)
|
||||
|
||||
@@ -89,3 +91,17 @@ class TerminalRegistrationApi(generics.CreateAPIView):
|
||||
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
|
||||
class EncryptedTerminalConfig(generics.CreateAPIView):
|
||||
serializer_class = serializers.EncryptedConfigSerializer
|
||||
permission_classes = [IsServiceAccount]
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
encrypt_key = serializer.validated_data['secret_encrypt_key']
|
||||
encrypted_value = serializer.validated_data['encrypted_value']
|
||||
config_crypto = ConfigCrypto(encrypt_key)
|
||||
value = config_crypto.decrypt(encrypted_value)
|
||||
return Response(data={'value': value}, status=200)
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import tarfile
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
from django.conf import settings
|
||||
from django.db.models import F
|
||||
from django.http import FileResponse
|
||||
from django.shortcuts import get_object_or_404, reverse
|
||||
@@ -156,6 +157,8 @@ class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
|
||||
|
||||
@action(methods=[GET], detail=False, permission_classes=[IsAuthenticated], url_path='online-info', )
|
||||
def online_info(self, request, *args, **kwargs):
|
||||
if not settings.VIEW_ASSET_ONLINE_SESSION_INFO:
|
||||
return self.permission_denied(request, "view asset online session info disabled")
|
||||
asset = self.request.query_params.get('asset_id')
|
||||
account = self.request.query_params.get('account')
|
||||
if asset is None or account is None:
|
||||
|
||||
@@ -105,6 +105,8 @@ class AppletMethod:
|
||||
if not has_applet_hosts:
|
||||
return methods
|
||||
applets = Applet.objects.filter(is_active=True)
|
||||
if not settings.XPACK_LICENSE_IS_VALID:
|
||||
applets = applets.filter(builtin=True)
|
||||
for applet in applets:
|
||||
for protocol in applet.protocols:
|
||||
methods[protocol].append({
|
||||
@@ -125,6 +127,8 @@ class VirtualAppMethod:
|
||||
methods = defaultdict(list)
|
||||
if not getattr(settings, 'VIRTUAL_APP_ENABLED'):
|
||||
return methods
|
||||
if not settings.XPACK_LICENSE_IS_VALID:
|
||||
return methods
|
||||
virtual_apps = VirtualApp.objects.filter(is_active=True)
|
||||
for virtual_app in virtual_apps:
|
||||
for protocol in virtual_app.protocols:
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.const.signals import SKIP_SIGNAL
|
||||
from common.const.signals import OP_LOG_SKIP_SIGNAL
|
||||
from common.db.models import JMSBaseModel
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from orgs.utils import tmp_to_root_org
|
||||
@@ -152,7 +152,7 @@ class Terminal(StorageMixin, TerminalStatusMixin, JMSBaseModel):
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.user:
|
||||
setattr(self.user, SKIP_SIGNAL, True)
|
||||
setattr(self.user, OP_LOG_SKIP_SIGNAL, True)
|
||||
self.user.delete()
|
||||
self.name = self.name + '_' + uuid.uuid4().hex[:8]
|
||||
self.user = None
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from assets.models import Asset
|
||||
from common.const import OP_LOG_SKIP_SIGNAL
|
||||
from common.utils import get_object_or_none, lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from terminal.backends import get_multi_command_storage
|
||||
@@ -203,6 +204,7 @@ class Session(OrgModelMixin):
|
||||
if self.need_update_cmd_amount:
|
||||
cmd_amount = self.compute_command_amount()
|
||||
self.cmd_amount = cmd_amount
|
||||
setattr(self, OP_LOG_SKIP_SIGNAL, True)
|
||||
self.save()
|
||||
elif self.need_compute_cmd_amount:
|
||||
cmd_amount = self.compute_command_amount()
|
||||
|
||||
@@ -58,6 +58,16 @@ class SessionSerializer(BulkOrgResourceModelSerializer):
|
||||
'terminal_display': {'label': _('Terminal display')},
|
||||
}
|
||||
|
||||
def get_fields(self):
|
||||
fields = super().get_fields()
|
||||
self.pop_fields_if_need(fields)
|
||||
return fields
|
||||
|
||||
def pop_fields_if_need(self, fields):
|
||||
request = self.context.get('request')
|
||||
if request and request.method != 'GET':
|
||||
fields.pop("command_amount", None)
|
||||
|
||||
def validate_asset(self, value):
|
||||
max_length = self.Meta.model.asset.field.max_length
|
||||
value = pretty_string(value, max_length=max_length)
|
||||
|
||||
@@ -220,7 +220,6 @@ command_storage_type_serializer_classes_mapping = {
|
||||
class BaseStorageSerializer(serializers.ModelSerializer):
|
||||
storage_type_serializer_classes_mapping = {}
|
||||
meta = MethodSerializer()
|
||||
comment = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = None
|
||||
@@ -253,11 +252,15 @@ class BaseStorageSerializer(serializers.ModelSerializer):
|
||||
serializer = serializer_class
|
||||
return serializer
|
||||
|
||||
@staticmethod
|
||||
def get_comment(obj):
|
||||
need_translate_comments = ['Store locally', 'Do not save']
|
||||
comment = obj.comment
|
||||
return _(comment) if comment in need_translate_comments else comment
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
need_translate_comments = {
|
||||
'Store locally': _('Store locally'),
|
||||
'Do not save': _('Do not save')
|
||||
}
|
||||
comment = instance.comment
|
||||
data['comment'] = need_translate_comments.get(comment, comment)
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
|
||||
@@ -147,3 +147,8 @@ class ConnectMethodSerializer(serializers.Serializer):
|
||||
type = serializers.CharField(max_length=128)
|
||||
endpoint_protocol = serializers.CharField(max_length=128)
|
||||
component = serializers.CharField(max_length=128)
|
||||
|
||||
|
||||
class EncryptedConfigSerializer(serializers.Serializer):
|
||||
secret_encrypt_key = serializers.CharField(max_length=128)
|
||||
encrypted_value = serializers.CharField(max_length=128)
|
||||
|
||||
@@ -54,6 +54,7 @@ urlpatterns = [
|
||||
# components
|
||||
path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'),
|
||||
path('components/connect-methods/', api.ConnectMethodListApi.as_view(), name='connect-methods'),
|
||||
path('encrypted-config/', api.EncryptedTerminalConfig.as_view(), name='encrypted-terminal-config'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
||||
@@ -97,10 +97,10 @@ class ComponentsPrometheusMetricsUtil(TypedComponentsStatusMetricsUtil):
|
||||
def convert_status_metrics(metrics):
|
||||
return {
|
||||
'any': metrics['total'],
|
||||
'normal': metrics['normal'],
|
||||
'high': metrics['high'],
|
||||
'critical': metrics['critical'],
|
||||
'offline': metrics['offline']
|
||||
'normal': len(metrics['normal']),
|
||||
'high': len(metrics['high']),
|
||||
'critical': len(metrics['critical']),
|
||||
'offline': len(metrics['offline'])
|
||||
}
|
||||
|
||||
def get_component_status_metrics(self):
|
||||
@@ -112,8 +112,8 @@ class ComponentsPrometheusMetricsUtil(TypedComponentsStatusMetricsUtil):
|
||||
tp = metric['type']
|
||||
prometheus_metrics.append(f'## 组件: {tp}')
|
||||
status_metrics = self.convert_status_metrics(metric)
|
||||
for status, value in status_metrics.items():
|
||||
metric_text = status_metric_text % (tp, status, value)
|
||||
for status, count in status_metrics.items():
|
||||
metric_text = status_metric_text % (tp, status, count)
|
||||
prometheus_metrics.append(metric_text)
|
||||
return prometheus_metrics
|
||||
|
||||
|
||||
@@ -423,6 +423,9 @@ class Ticket(StatusMixin, JMSBaseModel):
|
||||
new_value = alias if alias else account
|
||||
new_values.append(str(new_value))
|
||||
value = ', '.join(new_values)
|
||||
elif name == 'org_id':
|
||||
org = Organization.get_instance(value)
|
||||
value = org.name if org else ''
|
||||
elif isinstance(value, list):
|
||||
value = ', '.join(value)
|
||||
return value
|
||||
|
||||
@@ -83,7 +83,7 @@ class BaseTicketMessage(UserMessage):
|
||||
|
||||
@property
|
||||
def basic_items(self):
|
||||
item_names = ['serial_num', 'title', 'type', 'state', 'applicant', 'comment']
|
||||
item_names = ['serial_num', 'title', 'type', 'state', 'org_id', 'applicant', 'comment']
|
||||
return self._get_fields_items(item_names)
|
||||
|
||||
@property
|
||||
|
||||
@@ -69,7 +69,7 @@ class AuthMixin:
|
||||
if self.username:
|
||||
self.date_password_last_updated = timezone.now()
|
||||
post_user_change_password.send(self.__class__, user=self)
|
||||
super().set_password(raw_password) # noqa
|
||||
super().set_password(raw_password) # noqa
|
||||
|
||||
def set_public_key(self, public_key):
|
||||
if self.can_update_ssh_key():
|
||||
@@ -160,6 +160,22 @@ class AuthMixin:
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_need_update_password(self):
|
||||
if self.is_local and self.need_update_password:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_passwd_too_simple(password):
|
||||
simple_passwords = ['admin', 'ChangeMe']
|
||||
if password in simple_passwords:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_auth_backend_model(self):
|
||||
backend = getattr(self, 'backend', None)
|
||||
return backend == settings.AUTH_BACKEND_MODEL
|
||||
|
||||
@staticmethod
|
||||
def get_public_key_md5(key):
|
||||
try:
|
||||
|
||||
@@ -12,6 +12,7 @@ class UserOrgSerializer(serializers.Serializer):
|
||||
id = serializers.CharField()
|
||||
name = serializers.CharField()
|
||||
is_default = serializers.BooleanField(read_only=True)
|
||||
is_system = serializers.BooleanField(read_only=True)
|
||||
is_root = serializers.BooleanField(read_only=True)
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
<br />
|
||||
<br />
|
||||
<a href="{{ update_password_url }}">{% trans 'Click here update password' %}</a>
|
||||
<br />
|
||||
<br/>
|
||||
</p>
|
||||
-
|
||||
<p>
|
||||
{% trans 'If your password has expired, please click the link below to' %}
|
||||
<a href="{{ forget_password_url }}?email={{ email }}">{% trans 'Reset password' %}</a>
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
{% trans 'click here to set your password' %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
-
|
||||
<br>
|
||||
<p>
|
||||
{% trans 'This link is valid for 1 hour. After it expires' %}
|
||||
<a href="{{ forget_password_url }}?email={{ user.email }}">{% trans 'request new one' %}</a>
|
||||
|
||||
@@ -227,7 +227,7 @@ class MFABlockUtils(BlockUtilBase):
|
||||
|
||||
class LoginIpBlockUtil(BlockGlobalIpUtilBase):
|
||||
LIMIT_KEY_TMPL = "_LOGIN_LIMIT_{}"
|
||||
BLOCK_KEY_TMPL = "_LOGIN_BLOCK_{}"
|
||||
BLOCK_KEY_TMPL = "_LOGIN_BLOCK_IP_{}"
|
||||
|
||||
|
||||
def validate_emails(emails):
|
||||
|
||||
Reference in New Issue
Block a user