Compare commits

...

31 Commits

Author SHA1 Message Date
Bai
25603e4758 fix: setting field encrypt issue 2025-02-06 17:11:43 +08:00
Bai
3ae164d7e0 fix: circle imported for perms-api 2025-01-08 10:34:46 +08:00
wangruidong
3ad64e142e fix: circular import 2025-01-07 14:08:49 +08:00
wangruidong
0ff1413780 perf: ticket info add org name 2025-01-06 14:09:49 +08:00
jiangweidong
f5b64bed4e feat: VMware 自动同步文件夹到节点-翻译 2025-01-03 18:50:30 +08:00
Bai
a559415b65 fix: koko press r dont refresh user perm-nodes 2025-01-03 17:13:49 +08:00
Bai
2e7bd076f4 fix: limit connect method xpack 2024-12-25 16:27:51 +08:00
Bai
11f6fe0bf9 fix: system org 2024-12-25 15:34:19 +08:00
wangruidong
ae94648e80 fix: Add type check for secure command execution 2024-12-24 15:58:15 +08:00
jiangweidong
94e08f3d96 perf: The command amount does not record operation logs 2024-12-20 14:59:53 +08:00
Bai
8bedef92f0 fix: api prometheus count 2024-12-20 10:57:42 +08:00
jiangweidong
e5bb28231a perf: Oauth2.0 support two methods for passing authentication credentials. 2024-12-19 14:27:29 +08:00
jiangweidong
b5aeb24ae9 perf: create account add activity log 2024-12-18 15:53:08 +08:00
feng
674ea7142f perf: The entire organization can view activity log 2024-12-11 16:21:39 +08:00
fit2bot
5ab7b99b9d perf: add encrypted configuration API (#14633)
* perf: 添加加密配置API

* perf: modify url

---------

Co-authored-by: Eric <xplzv@126.com>
2024-12-11 11:42:34 +08:00
Bai
9cd163c99d fix: when oidc enabled and use_state user login raise 400 2024-12-06 16:26:59 +08:00
wangruidong
e72073f0cc perf: Add viewAssetOnlineSessionInfo conf 2024-11-25 15:26:14 +08:00
wangruidong
690f525afc perf: Add check for SECURITY_COMMAND_EXECUTION settings in ops tasks 2024-11-11 18:15:11 +08:00
wangruidong
6686afcec1 fix: Password reset is only required for AUTH_BACKEND_MODEL 2024-10-23 11:04:15 +08:00
wangruidong
0918f5c6f6 perf: Translate 2024-10-22 17:49:22 +08:00
wangruidong
891e3d5609 perf: Storage update comment failed 2024-10-22 17:28:47 +08:00
wangruidong
9fad591545 fix: Historical sessions download failed 2024-10-22 16:34:46 +08:00
fit2bot
1ed1c3a536 perf: optimize the connection of operation logs to ES to prevent ES downtime from causing the core component to become unhealthy. (#14283)
* perf: optimize the connection of operation logs to ES to prevent ES downtime from causing the core component to become unhealthy.

* perf: sync publish message

---------

Co-authored-by: jiangweidong <1053570670@qq.com>
2024-10-12 16:18:18 +08:00
wangruidong
63824d3491 fix: adhoc execute alert msg 2024-10-12 16:15:48 +08:00
wangruidong
96eadf060c perf: site msg content optimize 2024-10-11 11:30:59 +08:00
Bai
2c9128b0e7 perf: DEFAULT_PAGE_SIZE same as MAX_LIMIT_PER_PAGE 2024-10-10 18:00:26 +08:00
Bai
7d6fd0f881 fix: Fixed the issue that the workbench user login log only displays failed logs 2024-09-29 14:45:59 +08:00
jiangweidong
4e996afd5e perf: Cloud Sync IP Policy Updated to Preferred Option i18n 2024-09-27 14:29:08 +08:00
feng
1ed745d042 perf: Login encryption key cache added 2024-09-26 15:11:56 +08:00
feng
39ebbfcf10 perf: The locked ip shows the username 2024-09-06 11:00:39 +08:00
wangruidong
d0ec4f798b perf: Optimize asset connection speed with es command storage 2024-08-30 10:53:03 +08:00
62 changed files with 520 additions and 341 deletions

View File

@@ -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')

View File

@@ -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

View File

@@ -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

View 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)

View File

@@ -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({

View File

@@ -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 = {

View File

@@ -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.' \

View File

@@ -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):

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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):

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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-$%^&*'):

View File

@@ -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'

View File

@@ -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

View File

@@ -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,

View File

@@ -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'

View File

@@ -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

View File

@@ -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 ""

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:607f8df8c6cea454283e0bc371db3be0c81f7cfa981a5f00c20b5be139e16aac
size 178618
oid sha256:2dd9ffcfe15b130a5b3d7b4fcfe806eaae979973e8bd29ad9a473b9215424c57
size 178725

View File

@@ -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 "オートノード分類"

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fb60a2ebb525e5aaa2d2c64fb4956fcc56f1b608a6dfb186513e743e226af261
size 146172
oid sha256:4517c6a7464c68f949912b97c8a9abcc766ca19e32267a1d1da3f0e012471c1a
size 146255

View File

@@ -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 "自动节点分类"

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4aaedb4c31b1d11ddf63e5d0a27958ee5d42e78e2ce255746b8134d18ffc990f
size 146283
oid sha256:7d4d2709e597e055072474f08be2f363d43df239240051024a77213ba48ecfac
size 146366

View File

@@ -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 "自動節點分類"

View File

@@ -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(

View File

@@ -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:

View File

@@ -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()

View File

@@ -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)

View File

@@ -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:

View File

@@ -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()

View File

@@ -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

View File

@@ -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):

View File

@@ -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__)

View File

@@ -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

View File

@@ -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

View File

@@ -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',

View File

@@ -15,8 +15,7 @@
{% endfor %}
</ul>
<br/>
-
<br>
<p>
{% trans 'If you have any question, please contact the administrator' %}
</p>

View File

@@ -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)))

View File

@@ -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()

View File

@@ -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)

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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):