Compare commits

...

37 Commits

Author SHA1 Message Date
ibuler
9bccbe9143 fix: 修复 private storage permission 2023-09-11 12:20:00 +08:00
feng
37f04720e3 fix: ansible task 500 2023-08-01 16:06:35 +08:00
ibuler
bd1db91e99 perf: 修改 ansible version 2023-08-01 10:51:41 +08:00
ibuler
eae99e23f2 perf: 修改 inventory 2023-08-01 10:50:33 +08:00
Bai
c46743901d fix: 修复客户端方式访问资产 Endpoint 标签匹配策略不生效的问题 2023-07-19 18:14:11 +08:00
老广
9ddd04adbf Merge pull request #11004 from jumpserver/pr@v3.4@fix_ansiblejobrunerror
fix: 修复批量执行命令时资产名称包含 [ 特殊字符执行报错的问题(issue: 10986)
2023-07-18 18:13:54 +08:00
Bai
db55402fb8 fix: 修复批量执行命令时资产名称包含 [ 特殊字符执行报错的问题(issue: 10986) 2023-07-18 10:06:41 +00:00
老广
c8924274c3 Merge pull request #10968 from jumpserver/pr@v3.4@fix_ansibletesterror
fix: 修复 Ansible 测试资产可连接性报错的问题(Connection to UNKNOWN port 65535 timed out)
2023-07-17 10:28:12 +08:00
Bai
6d180cbdd2 fix: 修复 Ansible 测试资产可连接性报错的问题(Connection to UNKNOWN port 65535 timed out) 2023-07-14 11:19:32 +00:00
Bai
550c93b48b fix: 增加 TypeError 捕获 2023-07-11 11:16:07 +08:00
Bai
7d581737cb fix: 修复 beat 定时任务重复执行的问题 2023-07-11 11:16:07 +08:00
Bai
0e0c860d59 fix: 修复导入LDAP数据库超时导致 Lock wait timeout 的问题 2023-07-05 19:10:42 +08:00
吴小白
e0da9ee30c Merge pull request #10911 from jumpserver/pr@v3.4@perf_chrome
perf: 修正 Chrome driver 路径
2023-07-05 18:37:53 +08:00
Eric
499e008c6d perf: 修正 Chrome driver 路径 2023-07-05 10:29:33 +00:00
Bai
896cb061ab fix: 修复用户导入时手机号为dict类型报错的问题 2023-07-05 16:49:27 +08:00
feng
11464151e2 fix: 有默认值 required 为false 2023-07-05 15:31:09 +08:00
feng
4311e52681 fix: 推送账号 不填写home 推送失败 2023-07-05 15:31:09 +08:00
吴小白
a7c06f62f6 perf: 更新 Chrome 2023-07-04 18:45:33 +08:00
Eric
33db6de372 fix: 修复自定义远程应用的连接问题 2023-06-27 14:42:35 +08:00
feng
0bea545b6f fix: 修复自动化任务原子性error 导致整个任务失败问题 2023-06-25 18:20:28 +08:00
jiangweidong
424066b38f perf: 优化飞书接收到的工单审批的连接无法点击的问题 2023-06-25 11:08:56 +08:00
Eric
13d895b22e fix: 修复远程应用会话无法监控的问题 2023-06-21 12:05:19 +08:00
Bai
d5c51a4f0e fix: 修复迁移文件时触发信号记录操作日志导致迁移失败的问题 2023-06-21 11:02:14 +08:00
Eric
7c965706d4 perf: 修复 terminal 显示问题 2023-06-20 16:33:43 +08:00
feng
f59499f77e fix: 修改 push_account_params 数据迁移逻辑,不在导入公共方法生成数据 2023-06-19 18:23:23 +08:00
ibuler
98536cbc24 fix: 修改原来 platform 为 device 时,导致的 asset 类型不对 2023-06-19 17:54:17 +08:00
老广
7c29e60a82 Merge pull request #10757 from jumpserver/pr@v3.4@fix_custom_asset_detail_error
perf: 修复自定义资产详情没有 auto_config 的问题
2023-06-16 18:48:41 +08:00
ibuler
9e68d7d1a0 perf: 修复自定义资产详情没有 auto_config 的问题 2023-06-16 10:45:00 +00:00
ibuler
77b7134404 perf: 优化自定义 platform field 2023-06-16 17:02:46 +08:00
老广
fb7a839c16 Merge pull request #10755 from jumpserver/pr@v3.4@fix_permed_asset_duplicate
fix: 修复授权资产根据协议搜索重复的问题
2023-06-16 16:53:21 +08:00
ibuler
e80855068e fix: 修复授权资产根据协议搜索重复的问题 2023-06-16 08:44:53 +00:00
ibuler
5832246e5f perf: 修改 login acls 迁移冲突问题
perf: 修改 login acls 迁移,避免冲突
2023-06-16 13:58:51 +08:00
ibuler
dbef45e23b perf: 修复 acl 迁移后无法使用 2023-06-15 18:46:33 +08:00
feng
dedc42d775 feat: 添加自动化任务rdp ping 2023-06-15 18:46:04 +08:00
老广
f64073f48f Merge pull request #10739 from jumpserver/pr@v3.4@pr@dev@change_base_image
perf: 修改基础镜像版本
2023-06-15 17:36:33 +08:00
ibuler
70754ad748 perf: 修改基础镜像版本 2023-06-15 17:33:04 +08:00
fit2bot
3158980057 perf: 修改翻译 (#10734)
Co-authored-by: feng <1304903146@qq.com>
2023-06-15 15:41:25 +08:00
46 changed files with 378 additions and 172 deletions

View File

@@ -1,4 +1,4 @@
FROM python:3.9-slim-buster as stage-build FROM python:3.9-slim-bullseye as stage-build
ARG TARGETARCH ARG TARGETARCH
ARG VERSION ARG VERSION
@@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
ADD . . ADD . .
RUN cd utils && bash -ixeu build.sh RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim-buster FROM python:3.9-slim-bullseye
ARG TARGETARCH ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com> MAINTAINER JumpServer Team <ibuler@qq.com>

View File

@@ -1,11 +1,12 @@
- hosts: custom - hosts: custom
gather_facts: no gather_facts: no
vars: vars:
ansible_shell_type: sh
ansible_connection: local ansible_connection: local
tasks: tasks:
- name: Verify account - name: Verify account (pyfreerdp)
ssh_ping: rdp_ping:
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}" login_user: "{{ account.username }}"

View File

@@ -4,7 +4,7 @@
ansible_connection: local ansible_connection: local
tasks: tasks:
- name: Verify account - name: Verify account (paramiko)
ssh_ping: ssh_ping:
login_host: "{{ jms_asset.address }}" login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"

View File

@@ -63,15 +63,17 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_snapshot(obj): def get_snapshot(obj):
tp = obj.snapshot['type'] tp = obj.snapshot.get('type', '')
type_display = tp if not hasattr(AutomationTypes, tp) \
else getattr(AutomationTypes, tp).label
snapshot = { snapshot = {
'type': tp, 'type': tp,
'name': obj.snapshot['name'], 'name': obj.snapshot.get('name'),
'comment': obj.snapshot['comment'], 'comment': obj.snapshot.get('comment'),
'accounts': obj.snapshot['accounts'], 'accounts': obj.snapshot.get('accounts'),
'node_amount': len(obj.snapshot['nodes']), 'node_amount': len(obj.snapshot.get('nodes', [])),
'asset_amount': len(obj.snapshot['assets']), 'asset_amount': len(obj.snapshot.get('assets', [])),
'type_display': getattr(AutomationTypes, tp).label, 'type_display': type_display,
} }
return snapshot return snapshot

View File

@@ -1,5 +1,4 @@
# Generated by Django 3.2.17 on 2023-06-06 10:57 # Generated by Django 3.2.17 on 2023-06-06 10:57
from collections import defaultdict
from django.db import migrations, models from django.db import migrations, models
@@ -8,17 +7,20 @@ import common.db.fields
def migrate_users_login_acls(apps, schema_editor): def migrate_users_login_acls(apps, schema_editor):
login_acl_model = apps.get_model('acls', 'LoginACL') login_acl_model = apps.get_model('acls', 'LoginACL')
name_used = defaultdict(int)
for login_acl in login_acl_model.objects.all(): name_used = []
name = login_acl.name login_acls = []
if name_used[name] > 0: for login_acl in login_acl_model.objects.all().select_related('user'):
login_acl.name += "_{}".format(name_used[name]) name = '{}_{}'.format(login_acl.name, login_acl.user.username)
name_used[name] += 1 if name.lower() in name_used:
name += '_{}'.format(str(login_acl.user_id)[:4])
name_used.append(name.lower())
login_acl.name = name
login_acl.users = { login_acl.users = {
"type": "ids", "ids": [str(login_acl.user_id)] "type": "ids", "ids": [str(login_acl.user_id)]
} }
login_acl.save() login_acls.append(login_acl)
login_acl_model.objects.bulk_update(login_acls, ['name', 'users'])
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@@ -82,7 +82,7 @@ class AssetFilterSet(BaseFilterSet):
@staticmethod @staticmethod
def filter_protocols(queryset, name, value): def filter_protocols(queryset, name, value):
value = value.split(',') value = value.split(',')
return queryset.filter(protocols__name__in=value) return queryset.filter(protocols__name__in=value).distinct()
@staticmethod @staticmethod
def filter_labels(queryset, name, value): def filter_labels(queryset, name, value):
@@ -91,7 +91,7 @@ class AssetFilterSet(BaseFilterSet):
queryset = queryset.filter(labels__name=n, labels__value=v) queryset = queryset.filter(labels__name=n, labels__value=v)
else: else:
q = Q(labels__name__contains=value) | Q(labels__value__contains=value) q = Q(labels__name__contains=value) | Q(labels__value__contains=value)
queryset = queryset.filter(q) queryset = queryset.filter(q).distinct()
return queryset return queryset

View File

@@ -57,6 +57,7 @@ class BasePlaybookManager:
data = self.params.get(method_id) data = self.params.get(method_id)
if not data: if not data:
data = automation_params.get(method_id, {}) data = automation_params.get(method_id, {})
params = serializer(data).data params = serializer(data).data
return { return {
field_name: automation_params.get(field_name, '') field_name: automation_params.get(field_name, '')
@@ -262,7 +263,7 @@ class BasePlaybookManager:
info = self.file_to_json(runner.inventory) info = self.file_to_json(runner.inventory)
servers, not_valid = [], [] servers, not_valid = [], []
for k, host in info['all']['hosts'].items(): for k, host in info['all']['hosts'].items():
jms_asset, jms_gateway = host['jms_asset'], host.get('gateway') jms_asset, jms_gateway = host.get('jms_asset'), host.get('gateway')
if not jms_gateway: if not jms_gateway:
continue continue

View File

@@ -0,0 +1,15 @@
- hosts: custom
gather_facts: no
vars:
ansible_shell_type: sh
ansible_connection: local
tasks:
- name: Test asset connection (pyfreerdp)
rdp_ping:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"

View File

@@ -0,0 +1,13 @@
id: ping_by_rdp
name: "{{ 'Ping by pyfreerdp' | trans }}"
category:
- device
- host
type:
- windows
method: ping
i18n:
Ping by pyfreerdp:
zh: 使用 Python 模块 pyfreerdp 测试主机可连接性
en: Ping by pyfreerdp module
ja: Pyfreerdpモジュールを使用してホストにPingする

View File

@@ -4,7 +4,7 @@
ansible_connection: local ansible_connection: local
tasks: tasks:
- name: Test asset connection - name: Test asset connection (paramiko)
ssh_ping: ssh_ping:
login_user: "{{ jms_account.username }}" login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}" login_password: "{{ jms_account.secret }}"

View File

@@ -2,6 +2,7 @@
import django.db import django.db
from django.db import migrations, models from django.db import migrations, models
import common.db.fields import common.db.fields
@@ -118,7 +119,7 @@ class Migration(migrations.Migration):
primary_key=True, serialize=False, to='assets.asset')), primary_key=True, serialize=False, to='assets.asset')),
], ],
options={ options={
'verbose_name': 'Host', 'verbose_name': 'Host',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(

View File

@@ -137,6 +137,24 @@ def migrate_to_nodes(apps, *args):
parent.save() parent.save()
def migrate_ori_host_to_devices(apps, *args):
device_model = apps.get_model('assets', 'Device')
asset_model = apps.get_model('assets', 'Asset')
host_model = apps.get_model('assets', 'Host')
hosts_need_migrate_to_device = host_model.objects.filter(asset_ptr__platform__category='device')
assets = asset_model.objects.filter(id__in=hosts_need_migrate_to_device.values_list('asset_ptr_id', flat=True))
assets_map = {asset.id: asset for asset in assets}
for host in hosts_need_migrate_to_device:
asset = assets_map.get(host.asset_ptr_id)
if not asset:
continue
device = device_model(asset_ptr_id=asset.id)
device.__dict__.update(asset.__dict__)
device.save()
host.delete(keep_parents=True)
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('assets', '0097_auto_20220426_1558'), ('assets', '0097_auto_20220426_1558'),
@@ -146,5 +164,6 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.RunPython(migrate_database_to_asset), migrations.RunPython(migrate_database_to_asset),
migrations.RunPython(migrate_cloud_to_asset), migrations.RunPython(migrate_cloud_to_asset),
migrations.RunPython(migrate_to_nodes) migrations.RunPython(migrate_to_nodes),
migrations.RunPython(migrate_ori_host_to_devices),
] ]

View File

@@ -2,16 +2,13 @@
from django.db import migrations, models from django.db import migrations, models
from assets.const import AllTypes
def migrate_automation_push_account_params(apps, schema_editor): def migrate_automation_push_account_params(apps, schema_editor):
platform_automation_model = apps.get_model('assets', 'PlatformAutomation') platform_automation_model = apps.get_model('assets', 'PlatformAutomation')
platform_automation_methods = AllTypes.get_automation_methods()
methods_id_data_map = { methods_id_data_map = {
i['id']: None if i['params_serializer'] is None else i['params_serializer']({}).data 'push_account_aix': {'sudo': '/bin/whoami', 'shell': '/bin/bash', 'home': '', 'groups': ''},
for i in platform_automation_methods 'push_account_posix': {'sudo': '/bin/whoami', 'shell': '/bin/bash', 'home': '', 'groups': ''},
if i['method'] == 'push_account' 'push_account_local_windows': {'groups': 'Users,Remote Desktop Users'},
} }
automation_objs = [] automation_objs = []
for automation in platform_automation_model.objects.all(): for automation in platform_automation_model.objects.all():

View File

@@ -206,15 +206,14 @@ class Asset(NodesRelationMixin, AbsConnectivity, JSONFilterMixin, JMSOrgBaseMode
@lazyproperty @lazyproperty
def auto_config(self): def auto_config(self):
platform = self.platform platform = self.platform
automation = self.platform.automation
auto_config = { auto_config = {
'su_enabled': platform.su_enabled, 'su_enabled': platform.su_enabled,
'domain_enabled': platform.domain_enabled, 'domain_enabled': platform.domain_enabled,
'ansible_enabled': False 'ansible_enabled': False
} }
automation = getattr(self.platform, 'automation', None)
if not automation: if not automation:
return auto_config return auto_config
auto_config.update(model_to_dict(automation)) auto_config.update(model_to_dict(automation))
return auto_config return auto_config

View File

@@ -51,14 +51,14 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
from assets.const import AutomationTypes as AssetTypes from assets.const import AutomationTypes as AssetTypes
from accounts.const import AutomationTypes as AccountTypes from accounts.const import AutomationTypes as AccountTypes
tp_dict = dict(AssetTypes.choices) | dict(AccountTypes.choices) tp_dict = dict(AssetTypes.choices) | dict(AccountTypes.choices)
tp = obj.snapshot['type'] tp = obj.snapshot.get('type', '')
snapshot = { snapshot = {
'type': {'value': tp, 'label': tp_dict.get(tp, tp)}, 'type': {'value': tp, 'label': tp_dict.get(tp, tp)},
'name': obj.snapshot['name'], 'name': obj.snapshot.get('name'),
'comment': obj.snapshot['comment'], 'comment': obj.snapshot.get('comment'),
'accounts': obj.snapshot['accounts'], 'accounts': obj.snapshot.get('accounts'),
'node_amount': len(obj.snapshot['nodes']), 'node_amount': len(obj.snapshot.get('nodes', [])),
'asset_amount': len(obj.snapshot['assets']), 'asset_amount': len(obj.snapshot.get('assets', [])),
} }
return snapshot return snapshot

View File

@@ -42,7 +42,7 @@ def _get_instance_field_value(
if getattr(f, 'attname', None) in model_need_continue_fields: if getattr(f, 'attname', None) in model_need_continue_fields:
continue continue
value = getattr(instance, f.name) or getattr(instance, f.attname) value = getattr(instance, f.name, None) or getattr(instance, f.attname, None)
if not isinstance(value, bool) and not value: if not isinstance(value, bool) and not value:
continue continue

View File

@@ -22,7 +22,7 @@ from common.utils.http import is_true, is_false
from orgs.mixins.api import RootOrgViewMixin from orgs.mixins.api import RootOrgViewMixin
from perms.models import ActionChoices from perms.models import ActionChoices
from terminal.connect_methods import NativeClient, ConnectMethodUtil from terminal.connect_methods import NativeClient, ConnectMethodUtil
from terminal.models import EndpointRule from terminal.models import EndpointRule, Endpoint
from ..models import ConnectionToken, date_expired_default from ..models import ConnectionToken, date_expired_default
from ..serializers import ( from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer, ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
@@ -165,11 +165,13 @@ class RDPFileClientProtocolURLMixin:
return data return data
def get_smart_endpoint(self, protocol, asset=None): def get_smart_endpoint(self, protocol, asset=None):
target_ip = asset.get_target_ip() if asset else '' endpoint = Endpoint.match_by_instance_label(asset, protocol)
endpoint = EndpointRule.match_endpoint( if not endpoint:
target_instance=asset, target_ip=target_ip, target_ip = asset.get_target_ip() if asset else ''
protocol=protocol, request=self.request endpoint = EndpointRule.match_endpoint(
) target_instance=asset, target_ip=target_ip,
protocol=protocol, request=self.request
)
return endpoint return endpoint

View File

@@ -328,13 +328,13 @@ class RelatedManager:
q = Q() q = Q()
if isinstance(val, str): if isinstance(val, str):
val = [val] val = [val]
if ['*'] in val:
return Q()
for ip in val: for ip in val:
if not ip: if not ip:
continue continue
try: try:
if ip == '*': if '/' in ip:
return Q()
elif '/' in ip:
network = ipaddress.ip_network(ip) network = ipaddress.ip_network(ip)
ips = network.hosts() ips = network.hosts()
q |= Q(**{"{}__in".format(name): ips}) q |= Q(**{"{}__in".format(name): ips})
@@ -378,7 +378,7 @@ class RelatedManager:
if match == 'ip_in': if match == 'ip_in':
q = cls.get_ip_in_q(name, val) q = cls.get_ip_in_q(name, val)
elif match in ("exact", "contains", "startswith", "endswith", "gte", "lte", "gt", "lt"): elif match in ("contains", "startswith", "endswith", "gte", "lte", "gt", "lt"):
lookup = "{}__{}".format(name, match) lookup = "{}__{}".format(name, match)
q = Q(**{lookup: val}) q = Q(**{lookup: val})
elif match == 'regex': elif match == 'regex':
@@ -470,9 +470,9 @@ class JSONManyToManyDescriptor:
continue continue
if rule_match == 'in': if rule_match == 'in':
res &= value in rule_value res &= value in rule_value or '*' in rule_value
elif rule_match == 'exact': elif rule_match == 'exact':
res &= value == rule_value res &= value == rule_value or rule_value == '*'
elif rule_match == 'contains': elif rule_match == 'contains':
res &= rule_value in value res &= rule_value in value
elif rule_match == 'startswith': elif rule_match == 'startswith':
@@ -499,7 +499,7 @@ class JSONManyToManyDescriptor:
elif rule['match'] == 'ip_in': elif rule['match'] == 'ip_in':
if isinstance(rule_value, str): if isinstance(rule_value, str):
rule_value = [rule_value] rule_value = [rule_value]
res &= contains_ip(value, rule_value) res &= '*' in rule_value or contains_ip(value, rule_value)
elif rule['match'] == 'm2m': elif rule['match'] == 'm2m':
if isinstance(value, Manager): if isinstance(value, Manager):
value = value.values_list('id', flat=True) value = value.values_list('id', flat=True)

View File

@@ -12,7 +12,7 @@ from common.utils import get_object_or_none
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission): class IsValidUser(permissions.IsAuthenticated):
"""Allows access to valid user, is active and not expired""" """Allows access to valid user, is active and not expired"""
def has_permission(self, request, view): def has_permission(self, request, view):

View File

@@ -44,19 +44,21 @@ def set_default_by_type(tp, data, field_info):
def create_serializer_class(serializer_name, fields_info): def create_serializer_class(serializer_name, fields_info):
serializer_fields = {} serializer_fields = {}
fields_name = ['name', 'label', 'default', 'type', 'help_text'] fields_name = ['name', 'label', 'default', 'required', 'type', 'help_text']
for i, field_info in enumerate(fields_info): for i, field_info in enumerate(fields_info):
data = {k: field_info.get(k) for k in fields_name} data = {k: field_info.get(k) for k in fields_name}
field_type = data.pop('type', 'str') field_type = data.pop('type', 'str')
if data.get('default') is None: # 用户定义 default 和 required 可能会冲突, 所以要处理一下
default = data.get('default')
if default is not None:
data['required'] = False
else:
data.pop('default', None) data.pop('default', None)
data['required'] = field_info.get('required', True) data['required'] = True
data = set_default_by_type(field_type, data, field_info) data = set_default_by_type(field_type, data, field_info)
data = set_default_if_need(data, i) data = set_default_if_need(data, i)
if data.get('default', None) is not None:
data['required'] = False
field_name = data.pop('name') field_name = data.pop('name')
field_class = type_field_map.get(field_type, serializers.CharField) field_class = type_field_map.get(field_type, serializers.CharField)
serializer_fields[field_name] = field_class(**data) serializer_fields[field_name] = field_class(**data)

View File

@@ -212,6 +212,23 @@ class BitChoicesField(TreeChoicesField):
class PhoneField(serializers.CharField): class PhoneField(serializers.CharField):
def to_internal_value(self, data):
if isinstance(data, dict):
code = data.get('code')
phone = data.get('phone', '')
if code and phone:
data = '{}{}'.format(code, phone)
else:
data = phone
try:
phone = phonenumbers.parse(data, 'CN')
data = '{}{}'.format(phone.country_code, phone.national_number)
except phonenumbers.NumberParseException:
data = '+86{}'.format(data)
return super().to_internal_value(data)
def to_representation(self, value): def to_representation(self, value):
if value: if value:
try: try:

View File

@@ -16,6 +16,8 @@ def allow_access(private_file):
path_base = path_list[1] if len(path_list) > 1 else None path_base = path_list[1] if len(path_list) > 1 else None
path_perm = path_perms_map.get(path_base, None) path_perm = path_perms_map.get(path_base, None)
if ".." in request_path:
return False
if not path_perm: if not path_perm:
return False return False
if path_perm == '*' or request.user.has_perms([path_perm]): if path_perm == '*' or request.user.has_perms([path_perm]):

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a76aa384867a4732eb7d2365515a1a972502ebadcba4de8236c1dcb3c5c7fdd2 oid sha256:cd4fb6a0396c8636f8a36645354a5102790c020d73cdeb1f0e1d1f1b34ea39e9
size 145757 size 145760

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-14 20:40+0800\n" "POT-Creation-Date: 2023-06-15 15:35+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -28,7 +28,7 @@ msgstr "パラメータ 'action' は [{}] でなければなりません。"
#: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/confirm/password.py:9 authentication/forms.py:32
#: authentication/templates/authentication/login.html:274 #: authentication/templates/authentication/login.html:274
#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47
#: users/forms/profile.py:22 users/serializers/user.py:105 #: users/forms/profile.py:22 users/serializers/user.py:104
#: users/templates/users/_msg_user_created.html:13 #: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18 #: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28 #: xpack/plugins/cloud/serializers/account_attrs.py:28
@@ -505,7 +505,7 @@ msgstr "特権アカウント"
#: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39 #: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39
#: assets/models/label.py:22 #: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:114 #: authentication/serializers/connect_token_secret.py:114
#: terminal/models/applet/applet.py:39 users/serializers/user.py:170 #: terminal/models/applet/applet.py:39 users/serializers/user.py:169
msgid "Is active" msgid "Is active"
msgstr "アクティブです。" msgstr "アクティブです。"
@@ -1281,7 +1281,7 @@ msgstr "資産ハードウェア情報の収集"
#: assets/models/asset/common.py:159 assets/serializers/asset/custom.py:14 #: assets/models/asset/common.py:159 assets/serializers/asset/custom.py:14
msgid "Custom info" msgid "Custom info"
msgstr "自动化信息" msgstr "カスタム属性"
#: assets/models/asset/common.py:335 #: assets/models/asset/common.py:335
msgid "Can refresh asset hardware info" msgid "Can refresh asset hardware info"
@@ -1645,8 +1645,8 @@ msgstr "プロトコルが必要です: {}"
msgid "Default database" msgid "Default database"
msgstr "デフォルト・データベース" msgstr "デフォルト・データベース"
#: assets/serializers/asset/database.py:28 common/db/fields.py:570 #: assets/serializers/asset/database.py:28 common/db/fields.py:579
#: common/db/fields.py:575 common/serializers/fields.py:104 #: common/db/fields.py:584 common/serializers/fields.py:104
#: tickets/serializers/ticket/common.py:58 #: tickets/serializers/ticket/common.py:58
#: xpack/plugins/cloud/serializers/account_attrs.py:56 #: xpack/plugins/cloud/serializers/account_attrs.py:56
#: xpack/plugins/cloud/serializers/account_attrs.py:79 #: xpack/plugins/cloud/serializers/account_attrs.py:79
@@ -2654,7 +2654,7 @@ msgstr "アクション"
#: authentication/serializers/connection_token.py:44 #: authentication/serializers/connection_token.py:44
#: perms/serializers/permission.py:38 perms/serializers/permission.py:57 #: perms/serializers/permission.py:38 perms/serializers/permission.py:57
#: users/serializers/user.py:97 users/serializers/user.py:173 #: users/serializers/user.py:96 users/serializers/user.py:172
msgid "Is expired" msgid "Is expired"
msgstr "期限切れです" msgstr "期限切れです"
@@ -2680,8 +2680,8 @@ msgid "The {} cannot be empty"
msgstr "{} 空にしてはならない" msgstr "{} 空にしてはならない"
#: authentication/serializers/token.py:79 perms/serializers/permission.py:37 #: authentication/serializers/token.py:79 perms/serializers/permission.py:37
#: perms/serializers/permission.py:58 users/serializers/user.py:98 #: perms/serializers/permission.py:58 users/serializers/user.py:97
#: users/serializers/user.py:171 #: users/serializers/user.py:170
msgid "Is valid" msgid "Is valid"
msgstr "有効です" msgstr "有効です"
@@ -3116,7 +3116,7 @@ msgstr "テキストフィールドへのマーシャルデータ"
msgid "Encrypt field using Secret Key" msgid "Encrypt field using Secret Key"
msgstr "Secret Keyを使用したフィールドの暗号化" msgstr "Secret Keyを使用したフィールドの暗号化"
#: common/db/fields.py:558 #: common/db/fields.py:567
msgid "" msgid ""
"Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or " "Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or "
"{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', " "{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', "
@@ -3126,19 +3126,19 @@ msgstr ""
"'type''ids''ids':[]}或 #タイプ:属性、属性:[名前ip、照合正確、" "'type''ids''ids':[]}或 #タイプ:属性、属性:[名前ip、照合正確、"
"値1.1.1.1" "値1.1.1.1"
#: common/db/fields.py:565 #: common/db/fields.py:574
msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\"" msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\""
msgstr "無効なタイプです。all、ids、またはattrsでなければなりません" msgstr "無効なタイプです。all、ids、またはattrsでなければなりません"
#: common/db/fields.py:568 #: common/db/fields.py:577
msgid "Invalid ids for ids, should be a list" msgid "Invalid ids for ids, should be a list"
msgstr "無効なID、リストでなければなりません" msgstr "無効なID、リストでなければなりません"
#: common/db/fields.py:573 common/db/fields.py:578 #: common/db/fields.py:582 common/db/fields.py:587
msgid "Invalid attrs, should be a list of dict" msgid "Invalid attrs, should be a list of dict"
msgstr "無効な属性、dictリストでなければなりません" msgstr "無効な属性、dictリストでなければなりません"
#: common/db/fields.py:580 #: common/db/fields.py:589
msgid "Invalid attrs, should be has name and value" msgid "Invalid attrs, should be has name and value"
msgstr "名前と値が必要な無効な属性" msgstr "名前と値が必要な無効な属性"
@@ -3654,7 +3654,7 @@ msgstr "Material"
msgid "Material Type" msgid "Material Type"
msgstr "Material を選択してオプションを設定します。" msgstr "Material を選択してオプションを設定します。"
#: ops/models/job.py:460 #: ops/models/job.py:461
msgid "Job Execution" msgid "Job Execution"
msgstr "ジョブ実行" msgstr "ジョブ実行"
@@ -6711,7 +6711,7 @@ msgstr "公開キー"
msgid "Force enable" msgid "Force enable"
msgstr "強制有効" msgstr "強制有効"
#: users/models/user.py:765 users/serializers/user.py:172 #: users/models/user.py:765 users/serializers/user.py:171
msgid "Is service account" msgid "Is service account"
msgstr "サービスアカウントです" msgstr "サービスアカウントです"
@@ -6723,7 +6723,7 @@ msgstr "アバター"
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:773 users/serializers/user.py:109 #: users/models/user.py:773 users/serializers/user.py:108
msgid "Phone" msgid "Phone"
msgstr "電話" msgstr "電話"
@@ -6740,7 +6740,7 @@ msgid "Secret key"
msgstr "秘密キー" msgstr "秘密キー"
#: users/models/user.py:794 users/serializers/profile.py:149 #: users/models/user.py:794 users/serializers/profile.py:149
#: users/serializers/user.py:169 #: users/serializers/user.py:168
msgid "Is first login" msgid "Is first login"
msgstr "最初のログインです" msgstr "最初のログインです"
@@ -6823,55 +6823,55 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする
msgid "The newly set password is inconsistent" msgid "The newly set password is inconsistent"
msgstr "新しく設定されたパスワードが一致しない" msgstr "新しく設定されたパスワードが一致しない"
#: users/serializers/user.py:43 #: users/serializers/user.py:42
msgid "System roles" msgid "System roles"
msgstr "システムの役割" msgstr "システムの役割"
#: users/serializers/user.py:47 #: users/serializers/user.py:46
msgid "Org roles" msgid "Org roles"
msgstr "組織ロール" msgstr "組織ロール"
#: users/serializers/user.py:90 #: users/serializers/user.py:89
msgid "Password strategy" msgid "Password strategy"
msgstr "パスワード戦略" msgstr "パスワード戦略"
#: users/serializers/user.py:92 #: users/serializers/user.py:91
msgid "MFA enabled" msgid "MFA enabled"
msgstr "MFA有効化" msgstr "MFA有効化"
#: users/serializers/user.py:94 #: users/serializers/user.py:93
msgid "MFA force enabled" msgid "MFA force enabled"
msgstr "MFAフォース有効化" msgstr "MFAフォース有効化"
#: users/serializers/user.py:96 #: users/serializers/user.py:95
msgid "Login blocked" msgid "Login blocked"
msgstr "ログインがロックされました" msgstr "ログインがロックされました"
#: users/serializers/user.py:99 users/serializers/user.py:177 #: users/serializers/user.py:98 users/serializers/user.py:176
msgid "Is OTP bound" msgid "Is OTP bound"
msgstr "仮想MFAがバインドされているか" msgstr "仮想MFAがバインドされているか"
#: users/serializers/user.py:101 #: users/serializers/user.py:100
msgid "Can public key authentication" msgid "Can public key authentication"
msgstr "公開鍵認証が可能" msgstr "公開鍵認証が可能"
#: users/serializers/user.py:174 #: users/serializers/user.py:173
msgid "Avatar url" msgid "Avatar url"
msgstr "アバターURL" msgstr "アバターURL"
#: users/serializers/user.py:178 #: users/serializers/user.py:177
msgid "MFA level" msgid "MFA level"
msgstr "MFA レベル" msgstr "MFA レベル"
#: users/serializers/user.py:284 #: users/serializers/user.py:283
msgid "Select users" msgid "Select users"
msgstr "ユーザーの選択" msgstr "ユーザーの選択"
#: users/serializers/user.py:285 #: users/serializers/user.py:284
msgid "For security, only list several users" msgid "For security, only list several users"
msgstr "セキュリティのために、複数のユーザーのみをリストします" msgstr "セキュリティのために、複数のユーザーのみをリストします"
#: users/serializers/user.py:318 #: users/serializers/user.py:317
msgid "name not unique" msgid "name not unique"
msgstr "名前が一意ではない" msgstr "名前が一意ではない"

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f32e327dd50762b76d209f80b7de470df6faf5242af383dd6152b0c7ea5a7974 oid sha256:0efb248e80873f34d20f0fc3d4dd5c5a346048cb683c2b6bda3df939697fc52c
size 119261 size 119261

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-14 20:40+0800\n" "POT-Creation-Date: 2023-06-15 15:35+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@@ -27,7 +27,7 @@ msgstr "参数 'action' 必须是 [{}]"
#: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/confirm/password.py:9 authentication/forms.py:32
#: authentication/templates/authentication/login.html:274 #: authentication/templates/authentication/login.html:274
#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47
#: users/forms/profile.py:22 users/serializers/user.py:105 #: users/forms/profile.py:22 users/serializers/user.py:104
#: users/templates/users/_msg_user_created.html:13 #: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18 #: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28 #: xpack/plugins/cloud/serializers/account_attrs.py:28
@@ -504,7 +504,7 @@ msgstr "特权账号"
#: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39 #: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39
#: assets/models/label.py:22 #: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:114 #: authentication/serializers/connect_token_secret.py:114
#: terminal/models/applet/applet.py:39 users/serializers/user.py:170 #: terminal/models/applet/applet.py:39 users/serializers/user.py:169
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
@@ -1274,7 +1274,7 @@ msgstr "收集资产硬件信息"
#: assets/models/asset/common.py:159 assets/serializers/asset/custom.py:14 #: assets/models/asset/common.py:159 assets/serializers/asset/custom.py:14
msgid "Custom info" msgid "Custom info"
msgstr "自动化信息" msgstr "自定义属性"
#: assets/models/asset/common.py:335 #: assets/models/asset/common.py:335
msgid "Can refresh asset hardware info" msgid "Can refresh asset hardware info"
@@ -1636,8 +1636,8 @@ msgstr "协议是必填的: {}"
msgid "Default database" msgid "Default database"
msgstr "默认数据库" msgstr "默认数据库"
#: assets/serializers/asset/database.py:28 common/db/fields.py:570 #: assets/serializers/asset/database.py:28 common/db/fields.py:579
#: common/db/fields.py:575 common/serializers/fields.py:104 #: common/db/fields.py:584 common/serializers/fields.py:104
#: tickets/serializers/ticket/common.py:58 #: tickets/serializers/ticket/common.py:58
#: xpack/plugins/cloud/serializers/account_attrs.py:56 #: xpack/plugins/cloud/serializers/account_attrs.py:56
#: xpack/plugins/cloud/serializers/account_attrs.py:79 #: xpack/plugins/cloud/serializers/account_attrs.py:79
@@ -2629,7 +2629,7 @@ msgstr "动作"
#: authentication/serializers/connection_token.py:44 #: authentication/serializers/connection_token.py:44
#: perms/serializers/permission.py:38 perms/serializers/permission.py:57 #: perms/serializers/permission.py:38 perms/serializers/permission.py:57
#: users/serializers/user.py:97 users/serializers/user.py:173 #: users/serializers/user.py:96 users/serializers/user.py:172
msgid "Is expired" msgid "Is expired"
msgstr "已过期" msgstr "已过期"
@@ -2653,8 +2653,8 @@ msgid "The {} cannot be empty"
msgstr "{} 不能为空" msgstr "{} 不能为空"
#: authentication/serializers/token.py:79 perms/serializers/permission.py:37 #: authentication/serializers/token.py:79 perms/serializers/permission.py:37
#: perms/serializers/permission.py:58 users/serializers/user.py:98 #: perms/serializers/permission.py:58 users/serializers/user.py:97
#: users/serializers/user.py:171 #: users/serializers/user.py:170
msgid "Is valid" msgid "Is valid"
msgstr "是否有效" msgstr "是否有效"
@@ -3081,7 +3081,7 @@ msgstr "编码数据为 text"
msgid "Encrypt field using Secret Key" msgid "Encrypt field using Secret Key"
msgstr "加密的字段" msgstr "加密的字段"
#: common/db/fields.py:558 #: common/db/fields.py:567
msgid "" msgid ""
"Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or " "Invalid JSON data for JSONManyToManyField, should be like {'type': 'all'} or "
"{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', " "{'type': 'ids', 'ids': []} or {'type': 'attrs', 'attrs': [{'name': 'ip', "
@@ -3091,19 +3091,19 @@ msgstr ""
"{'type': 'attrs', 'attrs': [{'name': 'ip', 'match': 'exact', 'value': " "{'type': 'attrs', 'attrs': [{'name': 'ip', 'match': 'exact', 'value': "
"'1.1.1.1'}}" "'1.1.1.1'}}"
#: common/db/fields.py:565 #: common/db/fields.py:574
msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\"" msgid "Invalid type, should be \"all\", \"ids\" or \"attrs\""
msgstr "无效类型,应为 all、ids 或 attrs" msgstr "无效类型,应为 all、ids 或 attrs"
#: common/db/fields.py:568 #: common/db/fields.py:577
msgid "Invalid ids for ids, should be a list" msgid "Invalid ids for ids, should be a list"
msgstr "无效的ID应为列表" msgstr "无效的ID应为列表"
#: common/db/fields.py:573 common/db/fields.py:578 #: common/db/fields.py:582 common/db/fields.py:587
msgid "Invalid attrs, should be a list of dict" msgid "Invalid attrs, should be a list of dict"
msgstr "无效的属性应为dict列表" msgstr "无效的属性应为dict列表"
#: common/db/fields.py:580 #: common/db/fields.py:589
msgid "Invalid attrs, should be has name and value" msgid "Invalid attrs, should be has name and value"
msgstr "无效属性,应具有名称和值" msgstr "无效属性,应具有名称和值"
@@ -3612,7 +3612,7 @@ msgstr "Material"
msgid "Material Type" msgid "Material Type"
msgstr "Material 类型" msgstr "Material 类型"
#: ops/models/job.py:460 #: ops/models/job.py:461
msgid "Job Execution" msgid "Job Execution"
msgstr "作业执行" msgstr "作业执行"
@@ -6618,7 +6618,7 @@ msgstr "SSH公钥"
msgid "Force enable" msgid "Force enable"
msgstr "强制启用" msgstr "强制启用"
#: users/models/user.py:765 users/serializers/user.py:172 #: users/models/user.py:765 users/serializers/user.py:171
msgid "Is service account" msgid "Is service account"
msgstr "服务账号" msgstr "服务账号"
@@ -6630,7 +6630,7 @@ msgstr "头像"
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:773 users/serializers/user.py:109 #: users/models/user.py:773 users/serializers/user.py:108
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
@@ -6647,7 +6647,7 @@ msgid "Secret key"
msgstr "Secret key" msgstr "Secret key"
#: users/models/user.py:794 users/serializers/profile.py:149 #: users/models/user.py:794 users/serializers/profile.py:149
#: users/serializers/user.py:169 #: users/serializers/user.py:168
msgid "Is first login" msgid "Is first login"
msgstr "首次登录" msgstr "首次登录"
@@ -6730,55 +6730,55 @@ msgstr "新密码不能是最近 {} 次的密码"
msgid "The newly set password is inconsistent" msgid "The newly set password is inconsistent"
msgstr "两次密码不一致" msgstr "两次密码不一致"
#: users/serializers/user.py:43 #: users/serializers/user.py:42
msgid "System roles" msgid "System roles"
msgstr "系统角色" msgstr "系统角色"
#: users/serializers/user.py:47 #: users/serializers/user.py:46
msgid "Org roles" msgid "Org roles"
msgstr "组织角色" msgstr "组织角色"
#: users/serializers/user.py:90 #: users/serializers/user.py:89
msgid "Password strategy" msgid "Password strategy"
msgstr "密码策略" msgstr "密码策略"
#: users/serializers/user.py:92 #: users/serializers/user.py:91
msgid "MFA enabled" msgid "MFA enabled"
msgstr "MFA 已启用" msgstr "MFA 已启用"
#: users/serializers/user.py:94 #: users/serializers/user.py:93
msgid "MFA force enabled" msgid "MFA force enabled"
msgstr "强制 MFA" msgstr "强制 MFA"
#: users/serializers/user.py:96 #: users/serializers/user.py:95
msgid "Login blocked" msgid "Login blocked"
msgstr "登录被锁定" msgstr "登录被锁定"
#: users/serializers/user.py:99 users/serializers/user.py:177 #: users/serializers/user.py:98 users/serializers/user.py:176
msgid "Is OTP bound" msgid "Is OTP bound"
msgstr "是否绑定了虚拟 MFA" msgstr "是否绑定了虚拟 MFA"
#: users/serializers/user.py:101 #: users/serializers/user.py:100
msgid "Can public key authentication" msgid "Can public key authentication"
msgstr "可以使用公钥认证" msgstr "可以使用公钥认证"
#: users/serializers/user.py:174 #: users/serializers/user.py:173
msgid "Avatar url" msgid "Avatar url"
msgstr "头像路径" msgstr "头像路径"
#: users/serializers/user.py:178 #: users/serializers/user.py:177
msgid "MFA level" msgid "MFA level"
msgstr "MFA 级别" msgstr "MFA 级别"
#: users/serializers/user.py:284 #: users/serializers/user.py:283
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
#: users/serializers/user.py:285 #: users/serializers/user.py:284
msgid "For security, only list several users" msgid "For security, only list several users"
msgstr "为了安全,仅列出几个用户" msgstr "为了安全,仅列出几个用户"
#: users/serializers/user.py:318 #: users/serializers/user.py:317
msgid "name not unique" msgid "name not unique"
msgstr "名称重复" msgstr "名称重复"

View File

@@ -194,7 +194,7 @@ class Message(metaclass=MessageType):
return self.markdown_msg return self.markdown_msg
def get_feishu_msg(self) -> dict: def get_feishu_msg(self) -> dict:
return self.text_msg return self.markdown_msg
def get_email_msg(self) -> dict: def get_email_msg(self) -> dict:
return self.html_msg_with_sign return self.html_msg_with_sign

View File

@@ -72,8 +72,7 @@ def create_system_messages(app_config: AppConfig, **kwargs):
sub, created = SystemMsgSubscription.objects.get_or_create(message_type=message_type) sub, created = SystemMsgSubscription.objects.get_or_create(message_type=message_type)
if created: if created:
obj.post_insert_to_db(sub) obj.post_insert_to_db(sub)
logger.info( logger.info(f'Create MsgSubscription: package={app_config.module.__package__} type={message_type}')
f'Create SystemMsgSubscription: package={app_config.module.__package__} type={message_type}')
except ModuleNotFoundError: except ModuleNotFoundError:
pass pass

View File

@@ -1,7 +1,8 @@
[defaults] [defaults]
forks = 10 forks = 10
host_key_checking = False host_key_checking = False
library = /opt/jumpserver/apps/ops/ansible/modules:./modules library = /opt/jumpserver/apps/ops/ansible/modules:./modules
timeout = 65
[inventory] [inventory]
[privilege_escalation] [privilege_escalation]
[paramiko_connection] [paramiko_connection]

View File

@@ -109,21 +109,21 @@ class DefaultCallback:
pass pass
def playbook_on_stats(self, event_data, **kwargs): def playbook_on_stats(self, event_data, **kwargs):
failed = []
error_func = lambda err, task_detail: err + f"{task_detail[0]}: {task_detail[1]['stderr']};" error_func = lambda err, task_detail: err + f"{task_detail[0]}: {task_detail[1]['stderr']};"
for tp in ['dark', 'failures']: for tp in ['dark', 'failures']:
for host, tasks in self.result[tp].items(): for host, tasks in self.result[tp].items():
failed.append(host)
error = reduce(error_func, tasks.items(), '').strip(';') error = reduce(error_func, tasks.items(), '').strip(';')
self.summary[tp][host] = error self.summary[tp][host] = error
failures = list(self.result['failures'].keys())
dark_or_failures = list(self.result['dark'].keys()) + failures
for host, tasks in self.result.get('ignored', {}).items(): for host, tasks in self.result.get('ignored', {}).items():
ignore_errors = reduce(error_func, tasks.items(), '').strip(';') ignore_errors = reduce(error_func, tasks.items(), '').strip(';')
if host in failed: if host in failures:
self.summary['failures'][host] += {ignore_errors} self.summary['failures'][host] += {ignore_errors}
self.summary['ok'] = list(set(self.result['ok'].keys()) - set(failed)) self.summary['ok'] = list(set(self.result['ok'].keys()) - set(dark_or_failures))
self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(failed)) self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(dark_or_failures))
def playbook_on_include(self, event_data, **kwargs): def playbook_on_include(self, event_data, **kwargs):
pass pass

View File

@@ -268,9 +268,15 @@ class JMSInventory:
data = {'all': {'hosts': {}}} data = {'all': {'hosts': {}}}
for host in hosts: for host in hosts:
name = host.pop('name') name = host.pop('name')
name = name.replace('[', '_').replace(']', '_')
data['all']['hosts'][name] = host data['all']['hosts'][name] = host
if self.exclude_localhost and data['all']['hosts'].__contains__('localhost'): if not self.exclude_localhost:
data['all']['hosts'].update({'localhost': {'ansible_host': '255.255.255.255'}}) data['all']['hosts'].update({
'localhost': {
'ansible_host': '127.0.0.1',
'ansible_connection': 'local'
}
})
return data return data
def write_to_file(self, path): def write_to_file(self, path):

View File

@@ -39,10 +39,6 @@ import pyfreerdp
from typing import NamedTuple from typing import NamedTuple
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ops.ansible.modules_utils.custom_common import (
common_argument_spec
)
# ========================================= # =========================================
# Module execution. # Module execution.
@@ -55,6 +51,18 @@ class Param(NamedTuple):
password: str password: str
def common_argument_spec():
options = dict(
login_host=dict(type='str', required=False, default='localhost'),
login_port=dict(type='int', required=False, default=22),
login_user=dict(type='str', required=False, default='root'),
login_password=dict(type='str', required=False, no_log=True),
login_secret_type=dict(type='str', required=False, default='password'),
login_private_key_path=dict(type='str', required=False, no_log=True),
)
return options
def main(): def main():
options = common_argument_spec() options = common_argument_spec()
module = AnsibleModule(argument_spec=options, supports_check_mode=True) module = AnsibleModule(argument_spec=options, supports_check_mode=True)

View File

View File

@@ -0,0 +1,80 @@
import logging
from celery.utils.log import get_logger
from django.db import close_old_connections
from django.core.exceptions import ObjectDoesNotExist
from django.db.utils import DatabaseError, InterfaceError
from django_celery_beat.schedulers import DatabaseScheduler as DJDatabaseScheduler
logger = get_logger(__name__)
debug, info, warning = logger.debug, logger.info, logger.warning
__all__ = ['DatabaseScheduler']
class DatabaseScheduler(DJDatabaseScheduler):
def sync(self):
if logger.isEnabledFor(logging.DEBUG):
debug('Writing entries...')
_tried = set()
_failed = set()
try:
close_old_connections()
while self._dirty:
name = self._dirty.pop()
try:
# 源码
# self.schedule[name].save()
# _tried.add(name)
"""
::Debug Description (2023.07.10)::
如果调用 self.schedule 可能会导致 self.save() 方法之前重新获取数据库中的数据, 而不是临时设置的 last_run_at 数据
如果这里调用 self.schedule
那么可能会导致调用 save 的 self.schedule[name] 的 last_run_at 是从数据库中获取回来的老数据
而不是任务执行后临时设置的 last_run_at (在 __next__() 方法中设置的)
当 `max_interval` 间隔之后, 下一个任务检测周期还是会再次执行任务
::Demo::
任务信息:
beat config: max_interval = 60s
任务名称: cap
任务执行周期: 每 3 分钟执行一次
任务最后执行时间: 18:00
任务第一次执行: 18:03 (执行时设置 last_run_at = 18:03, 此时在内存中)
任务执行完成后,
检测到需要 sync, sync 中调用了 self.schedule,
self.schedule 中发现 schedule_changed() 为 True, 需要调用 all_as_schedule()
此时sync 中调用的 self.schedule[name] 的 last_run_at 是 18:00
这时候在 self.sync() 进行 self.save()
beat: Waking up 60s ...
任务第二次执行: 18:04 (因为获取回来的 last_run_at 是 18:00, entry.is_due() == True)
::解决方法::
所以这里为了避免从数据库中获取,直接使用 _schedule #
"""
self._schedule[name].save()
_tried.add(name)
except (KeyError, TypeError, ObjectDoesNotExist):
_failed.add(name)
except DatabaseError as exc:
logger.exception('Database error while sync: %r', exc)
except InterfaceError:
warning(
'DatabaseScheduler: InterfaceError in sync(), '
'waiting to retry in next call...'
)
finally:
# retry later, only for the failed ones
self._dirty |= _failed

View File

@@ -41,8 +41,8 @@ def get_parent_keys(key, include_self=True):
class JMSPermedInventory(JMSInventory): class JMSPermedInventory(JMSInventory):
def __init__(self, assets, account_policy='privileged_first', def __init__(self, assets, account_policy='privileged_first',
account_prefer='root,Administrator', host_callback=None, exclude_localhost=False, user=None): account_prefer='root,Administrator', host_callback=None, user=None):
super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost) super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost=True)
self.user = user self.user = user
self.assets_accounts_mapper = self.get_assets_accounts_mapper() self.assets_accounts_mapper = self.get_assets_accounts_mapper()

View File

@@ -23,7 +23,6 @@ def sync_ldap_user():
@shared_task(verbose_name=_('Periodic import ldap user')) @shared_task(verbose_name=_('Periodic import ldap user'))
@transaction.atomic
def import_ldap_user(): def import_ldap_user():
logger.info("Start import ldap user task") logger.info("Start import ldap user task")
util_server = LDAPServerUtil() util_server = LDAPServerUtil()

View File

@@ -157,35 +157,43 @@
script: | script: |
pip install -r '{{ ansible_env.TEMP }}\pip_packages\pip_packages\requirements.txt' --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages\pip_packages' pip install -r '{{ ansible_env.TEMP }}\pip_packages\pip_packages\requirements.txt' --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages\pip_packages'
- name: Download chromedriver (Chromium) - name: Download chromedriver (Chrome)
ansible.windows.win_get_url: ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chromedriver_win32.zip" url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chromedriver_win32.zip"
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip" dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}" validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Unzip chromedriver (Chromium) - name: Unzip chromedriver (Chrome)
community.windows.win_unzip: community.windows.win_unzip:
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip" src: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip"
dest: C:\Program Files\JumpServer\drivers dest: C:\Program Files\JumpServer\drivers\chromedriver_win32
- name: Download chromium zip package (Chromium) - name: Download Chrome zip package (Chrome)
ansible.windows.win_get_url: ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chrome-win.zip" url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chrome-win.zip"
dest: "{{ ansible_env.TEMP }}\\chrome-win.zip" dest: "{{ ansible_env.TEMP }}\\chrome-win.zip"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}" validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Unzip Chromium (Chromium) - name: Unzip Chrome (Chrome)
community.windows.win_unzip: community.windows.win_unzip:
src: "{{ ansible_env.TEMP }}\\chrome-win.zip" src: "{{ ansible_env.TEMP }}\\chrome-win.zip"
dest: C:\Program Files\Chrome dest: C:\Program Files\JumpServer\applications
- name: Set chromium and driver on the global system path (Chromium) - name: Check and Clean global system path (Chrome)
ansible.windows.win_path: ansible.windows.win_path:
elements: elements:
- 'C:\Program Files\Chrome\chrome-win32' - 'C:\Program Files\Chrome\chrome-win32'
- 'C:\Program Files\Chrome\chrome-win'
- 'C:\Program Files\chrome-win'
state: absent
- name: Set Chrome and driver on the global system path (Chrome)
ansible.windows.win_path:
elements:
- 'C:\Program Files\JumpServer\applications\Chrome\Application'
- 'C:\Program Files\JumpServer\drivers\chromedriver_win32' - 'C:\Program Files\JumpServer\drivers\chromedriver_win32'
- name: Set Chromium variables disable Google Api (Chromium) - name: Set Chrome variables disable Google Api (Chrome)
ansible.windows.win_environment: ansible.windows.win_environment:
level: machine level: machine
variables: variables:
@@ -221,4 +229,4 @@
- name: Sync all remote applets - name: Sync all remote applets
ansible.windows.win_powershell: ansible.windows.win_powershell:
script: | script: |
tinkerd install all tinkerd install all

View File

@@ -10,12 +10,11 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from assets.const import Protocol
from assets.models import Asset from assets.models import Asset
from common.utils import get_object_or_none, lazyproperty from common.utils import get_object_or_none, lazyproperty
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from terminal.backends import get_multi_command_storage from terminal.backends import get_multi_command_storage
from terminal.const import SessionType from terminal.const import SessionType, TerminalType
from users.models import User from users.models import User
@@ -112,6 +111,7 @@ class Session(OrgModelMixin):
return rel_path return rel_path
except: except:
pass pass
@property @property
def asset_obj(self): def asset_obj(self):
return Asset.objects.get(id=self.asset_id) return Asset.objects.get(id=self.asset_id)
@@ -132,10 +132,7 @@ class Session(OrgModelMixin):
if self.type != SessionType.normal: if self.type != SessionType.normal:
# 会话监控仅支持 normal不支持 tunnel 和 command # 会话监控仅支持 normal不支持 tunnel 和 command
return False return False
if self.protocol in [ if self.terminal.type in [TerminalType.lion, TerminalType.koko]:
Protocol.ssh, Protocol.vnc, Protocol.rdp,
Protocol.telnet, Protocol.k8s
]:
return True return True
else: else:
return False return False

View File

@@ -1,13 +1,15 @@
from rest_framework import permissions from rest_framework import permissions
from common.utils import get_logger from common.utils import get_logger
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['IsSessionAssignee'] __all__ = ['IsSessionAssignee']
class IsSessionAssignee(permissions.BasePermission): class IsSessionAssignee(permissions.IsAuthenticated):
def has_permission(self, request, view):
return False
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
try: try:

View File

@@ -1,7 +1,6 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from assets.const import Protocol
from common.serializers.fields import LabeledChoiceField from common.serializers.fields import LabeledChoiceField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .terminal import TerminalSmallSerializer from .terminal import TerminalSmallSerializer
@@ -14,11 +13,9 @@ __all__ = [
] ]
class SessionSerializer(BulkOrgResourceModelSerializer): class SessionSerializer(BulkOrgResourceModelSerializer):
org_id = serializers.CharField(allow_blank=True) org_id = serializers.CharField(allow_blank=True)
protocol = serializers.ChoiceField(choices=Protocol.choices, label=_("Protocol")) protocol = serializers.CharField(max_length=128, label=_("Protocol"))
type = LabeledChoiceField( type = LabeledChoiceField(
choices=SessionType.choices, label=_("Type"), default=SessionType.normal choices=SessionType.choices, label=_("Type"), default=SessionType.normal
) )
@@ -33,7 +30,7 @@ class SessionSerializer(BulkOrgResourceModelSerializer):
"user", "asset", "user_id", "asset_id", 'account', 'account_id', "user", "asset", "user_id", "asset_id", 'account', 'account_id',
"protocol", 'type', "login_from", "remote_addr", "protocol", 'type', "login_from", "remote_addr",
"is_success", "is_finished", "has_replay", "has_command", "is_success", "is_finished", "has_replay", "has_command",
"date_start", "date_end", "comment" "date_start", "date_end", "comment", "terminal_display"
] ]
fields_fk = ["terminal", ] fields_fk = ["terminal", ]
fields_custom = ["can_replay", "can_join", "can_terminate"] fields_custom = ["can_replay", "can_join", "can_terminate"]

View File

@@ -1,12 +1,12 @@
from rest_framework import permissions from rest_framework import permissions
class IsAssignee(permissions.BasePermission): class IsAssignee(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return obj.has_current_assignee(request.user) return obj.has_current_assignee(request.user)
class IsApplicant(permissions.BasePermission): class IsApplicant(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return obj.applicant == request.user return obj.applicant == request.user

View File

@@ -1,6 +1,5 @@
from rest_framework import permissions from rest_framework import permissions
from rbac.builtin import BuiltinRole
from .utils import is_auth_password_time_valid from .utils import is_auth_password_time_valid
@@ -11,7 +10,7 @@ class IsAuthPasswdTimeValid(permissions.IsAuthenticated):
and is_auth_password_time_valid(request.session) and is_auth_password_time_valid(request.session)
class UserObjectPermission(permissions.BasePermission): class UserObjectPermission(permissions.IsAuthenticated):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
if view.action not in ['update', 'partial_update', 'destroy']: if view.action not in ['update', 'partial_update', 'destroy']:

View File

@@ -1,6 +1,6 @@
aiofiles==22.1.0 aiofiles==22.1.0
amqp==5.0.9 amqp==5.0.9
git+https://github.com/jumpserver/ansible@master#egg=ansible-core ansible-core@https://github.com/jumpserver/ansible/releases/download/v2.14.1.2/ansible-2.14.1.2.zip
ansible==7.1.0 ansible==7.1.0
ansible-runner==2.2.1 ansible-runner==2.2.1
asn1crypto==0.24.0 asn1crypto==0.24.0

View File

@@ -0,0 +1,37 @@
import os
import sys
import django
if os.path.exists('../apps'):
sys.path.insert(0, '../apps')
elif os.path.exists('./apps'):
sys.path.insert(0, './apps')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
django.setup()
from assets.models import Asset as asset_model, Host as host_model, Device as device_model
from orgs.models import Organization
def clean_host():
root = Organization.root()
root.change_to()
devices = host_model.objects.filter(platform__category='device')
assets = asset_model.objects.filter(id__in=devices.values_list('asset_ptr_id', flat=True))
assets_map = {asset.id: asset for asset in assets}
for host in devices:
asset = assets_map.get(host.asset_ptr_id)
if not asset:
continue
device = device_model(asset_ptr_id=asset.id)
device.__dict__.update(asset.__dict__)
device.save()
host.delete(keep_parents=True)
if __name__ == "__main__":
clean_host()

View File

@@ -54,7 +54,7 @@ else:
connection_params['port'] = settings.REDIS_PORT connection_params['port'] = settings.REDIS_PORT
redis_client = Redis(**connection_params) redis_client = Redis(**connection_params)
scheduler = "django_celery_beat.schedulers:DatabaseScheduler" scheduler = "ops.celery.beat.schedulers:DatabaseScheduler"
processes = [] processes = []
cmd = [ cmd = [
'celery', 'celery',