mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 08:32:48 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98a5e97518 | ||
|
|
17be132497 | ||
|
|
0cbce5f07a | ||
|
|
26dfb729bc | ||
|
|
bbe03ddb73 | ||
|
|
c88645fa57 | ||
|
|
7271feb598 | ||
|
|
950183ca85 | ||
|
|
6b1aa752d6 | ||
|
|
c908b47ccf | ||
|
|
88af33b7c2 | ||
|
|
136537fcfb | ||
|
|
dfd404c549 | ||
|
|
53886a5abf | ||
|
|
50d3125fec | ||
|
|
1145305f11 | ||
|
|
433f063142 | ||
|
|
8c4e496391 | ||
|
|
fad214ccbb | ||
|
|
3b0f68c4c7 | ||
|
|
87b4ecfb3a | ||
|
|
32f2be2793 | ||
|
|
a098bc6c06 | ||
|
|
86870ad985 | ||
|
|
c602bf7224 | ||
|
|
86dab4fc6e | ||
|
|
a85a80a945 | ||
|
|
349edc10aa | ||
|
|
44918e3cb5 | ||
|
|
9a2f6c0d70 | ||
|
|
934969a8f1 | ||
|
|
57162c1628 | ||
|
|
32fb36867f | ||
|
|
158b589028 | ||
|
|
d64277353c | ||
|
|
bff6f397ce | ||
|
|
0ad461a804 | ||
|
|
a1dcef0ba0 | ||
|
|
dbb1ee3a75 | ||
|
|
d6bd207a17 | ||
|
|
e69ba27ff4 | ||
|
|
adbe7c07c6 | ||
|
|
d1eacf53d4 | ||
|
|
472c14fd27 | ||
|
|
9d4854b8c3 |
@@ -24,6 +24,7 @@ ARG DEPENDENCIES=" \
|
||||
libjpeg-dev \
|
||||
libldap2-dev \
|
||||
libsasl2-dev \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
|
||||
@@ -6,7 +6,7 @@ from rest_framework.response import Response
|
||||
from accounts import serializers
|
||||
from accounts.filters import AccountFilterSet
|
||||
from accounts.models import Account
|
||||
from assets.models import Asset
|
||||
from assets.models import Asset, Node
|
||||
from common.permissions import UserConfirmation, ConfirmType
|
||||
from common.views.mixins import RecordViewLogMixin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
@@ -28,6 +28,7 @@ class AccountViewSet(OrgBulkModelViewSet):
|
||||
rbac_perms = {
|
||||
'partial_update': ['accounts.change_account'],
|
||||
'su_from_accounts': 'accounts.view_account',
|
||||
'username_suggestions': 'accounts.view_account',
|
||||
}
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='su-from-accounts')
|
||||
@@ -42,11 +43,34 @@ class AccountViewSet(OrgBulkModelViewSet):
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
accounts = asset.accounts.all()
|
||||
else:
|
||||
accounts = []
|
||||
accounts = Account.objects.none()
|
||||
accounts = self.filter_queryset(accounts)
|
||||
serializer = serializers.AccountSerializer(accounts, many=True)
|
||||
return Response(data=serializer.data)
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='username-suggestions')
|
||||
def username_suggestions(self, request, *args, **kwargs):
|
||||
asset_ids = request.query_params.get('assets')
|
||||
node_keys = request.query_params.get('keys')
|
||||
username = request.query_params.get('username')
|
||||
|
||||
assets = Asset.objects.all()
|
||||
if asset_ids:
|
||||
assets = assets.filter(id__in=asset_ids.split(','))
|
||||
if node_keys:
|
||||
patten = Node.get_node_all_children_key_pattern(node_keys.split(','))
|
||||
assets = assets.filter(nodes__key__regex=patten)
|
||||
|
||||
accounts = Account.objects.filter(asset__in=assets)
|
||||
if username:
|
||||
accounts = accounts.filter(username__icontains=username)
|
||||
usernames = list(accounts.values_list('username', flat=True).distinct()[:10])
|
||||
usernames.sort()
|
||||
common = [i for i in usernames if i in usernames if i.lower() in ['root', 'admin', 'administrator']]
|
||||
others = [i for i in usernames if i not in common]
|
||||
usernames = common + others
|
||||
return Response(data=usernames)
|
||||
|
||||
|
||||
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
||||
"""
|
||||
|
||||
@@ -206,7 +206,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
||||
serializer = serializer_cls(recorders, many=True)
|
||||
|
||||
header = [str(v.label) for v in serializer.child.fields.values()]
|
||||
rows = [list(row.values()) for row in serializer.data]
|
||||
rows = [[str(i) for i in row.values()] for row in serializer.data]
|
||||
if not rows:
|
||||
return False
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from accounts.const import AutomationTypes
|
||||
from jumpserver.utils import has_valid_xpack_license
|
||||
from .base import AccountBaseAutomation
|
||||
from .change_secret import ChangeSecretMixin
|
||||
|
||||
@@ -27,6 +28,8 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.type = AutomationTypes.push_account
|
||||
if not has_valid_xpack_license():
|
||||
self.is_periodic = False
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def to_attr_json(self):
|
||||
|
||||
@@ -33,7 +33,8 @@ class AuthValidateMixin(serializers.Serializer):
|
||||
return secret
|
||||
elif secret_type == SecretType.SSH_KEY:
|
||||
passphrase = passphrase if passphrase else None
|
||||
return validate_ssh_key(secret, passphrase)
|
||||
secret = validate_ssh_key(secret, passphrase)
|
||||
return secret
|
||||
else:
|
||||
return secret
|
||||
|
||||
@@ -41,8 +42,9 @@ class AuthValidateMixin(serializers.Serializer):
|
||||
secret_type = validated_data.get('secret_type')
|
||||
passphrase = validated_data.get('passphrase')
|
||||
secret = validated_data.pop('secret', None)
|
||||
self.handle_secret(secret, secret_type, passphrase)
|
||||
validated_data['secret'] = secret
|
||||
validated_data['secret'] = self.handle_secret(
|
||||
secret, secret_type, passphrase
|
||||
)
|
||||
for field in ('secret',):
|
||||
value = validated_data.get(field)
|
||||
if not value:
|
||||
|
||||
@@ -8,7 +8,7 @@ from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def task_activity_callback(self, pid, trigger, tp):
|
||||
def task_activity_callback(self, pid, trigger, tp, *args, **kwargs):
|
||||
model = AutomationTypes.get_type_model(tp)
|
||||
with tmp_to_root_org():
|
||||
instance = get_object_or_none(model, pk=pid)
|
||||
|
||||
@@ -9,7 +9,7 @@ from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def task_activity_callback(self, pid, trigger):
|
||||
def task_activity_callback(self, pid, trigger, *args, **kwargs):
|
||||
from accounts.models import AccountBackupAutomation
|
||||
with tmp_to_root_org():
|
||||
plan = get_object_or_none(AccountBackupAutomation, pk=pid)
|
||||
|
||||
@@ -27,7 +27,7 @@ def gather_asset_accounts_util(nodes, task_name):
|
||||
|
||||
@shared_task(
|
||||
queue="ansible", verbose_name=_('Gather asset accounts'),
|
||||
activity_callback=lambda self, node_ids, task_name=None: (node_ids, None)
|
||||
activity_callback=lambda self, node_ids, task_name=None, *args, **kwargs: (node_ids, None)
|
||||
)
|
||||
def gather_asset_accounts_task(node_ids, task_name=None):
|
||||
if task_name is None:
|
||||
|
||||
@@ -13,7 +13,7 @@ __all__ = [
|
||||
|
||||
@shared_task(
|
||||
queue="ansible", verbose_name=_('Push accounts to assets'),
|
||||
activity_callback=lambda self, account_ids, asset_ids: (account_ids, None)
|
||||
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None)
|
||||
)
|
||||
def push_accounts_to_assets_task(account_ids):
|
||||
from accounts.models import PushAccountAutomation
|
||||
|
||||
@@ -99,13 +99,14 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
||||
("platform", serializers.PlatformSerializer),
|
||||
("suggestion", serializers.MiniAssetSerializer),
|
||||
("gateways", serializers.GatewaySerializer),
|
||||
("spec_info", serializers.SpecSerializer)
|
||||
("spec_info", serializers.SpecSerializer),
|
||||
)
|
||||
rbac_perms = (
|
||||
("match", "assets.match_asset"),
|
||||
("platform", "assets.view_platform"),
|
||||
("gateways", "assets.view_gateway"),
|
||||
("spec_info", "assets.view_asset"),
|
||||
("info", "assets.view_asset"),
|
||||
)
|
||||
extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend]
|
||||
|
||||
|
||||
@@ -21,4 +21,10 @@ class HostViewSet(AssetViewSet):
|
||||
@action(methods=["GET"], detail=True, url_path="info")
|
||||
def info(self, *args, **kwargs):
|
||||
asset = super().get_object()
|
||||
return Response(asset.info)
|
||||
serializer = self.get_serializer(asset.info)
|
||||
data = serializer.data
|
||||
data['asset'] = {
|
||||
'id': asset.id, 'name': asset.name,
|
||||
'address': asset.address
|
||||
}
|
||||
return Response(data)
|
||||
|
||||
@@ -39,7 +39,7 @@ class Protocol(ChoicesMixin, models.TextChoices):
|
||||
'port': 3389,
|
||||
'secret_types': ['password'],
|
||||
'setting': {
|
||||
'console': True,
|
||||
'console': False,
|
||||
'security': 'any',
|
||||
}
|
||||
},
|
||||
|
||||
@@ -214,10 +214,13 @@ class AllTypes(ChoicesMixin):
|
||||
tp_node = cls.choice_to_node(tp, category_node['id'], opened=False, meta=meta)
|
||||
tp_count = category_type_mapper.get(category + '_' + tp, 0)
|
||||
tp_node['name'] += f'({tp_count})'
|
||||
platforms = tp_platforms.get(category + '_' + tp, [])
|
||||
if not platforms:
|
||||
tp_node['isParent'] = False
|
||||
nodes.append(tp_node)
|
||||
|
||||
# Platform 格式化
|
||||
for p in tp_platforms.get(category + '_' + tp, []):
|
||||
for p in platforms:
|
||||
platform_node = cls.platform_to_node(p, tp_node['id'], include_asset)
|
||||
platform_node['name'] += f'({platform_count.get(p.id, 0)})'
|
||||
nodes.append(platform_node)
|
||||
@@ -306,10 +309,11 @@ class AllTypes(ChoicesMixin):
|
||||
protocols_data = deepcopy(default_protocols)
|
||||
if _protocols:
|
||||
protocols_data = [p for p in protocols_data if p['name'] in _protocols]
|
||||
|
||||
for p in protocols_data:
|
||||
setting = _protocols_setting.get(p['name'], {})
|
||||
p['required'] = p.pop('required', False)
|
||||
p['default'] = p.pop('default', False)
|
||||
p['required'] = setting.pop('required', False)
|
||||
p['default'] = setting.pop('default', False)
|
||||
p['setting'] = {**p.get('setting', {}), **setting}
|
||||
|
||||
platform_data = {
|
||||
|
||||
@@ -93,7 +93,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='address',
|
||||
field=models.CharField(db_index=True, max_length=1024, verbose_name='Address'),
|
||||
field=models.CharField(db_index=True, max_length=767, verbose_name='Address'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
|
||||
@@ -34,8 +34,9 @@ def migrate_database_to_asset(apps, *args):
|
||||
_attrs = app.attrs or {}
|
||||
attrs.update(_attrs)
|
||||
|
||||
name = 'DB-{}'.format(app.name)
|
||||
db = db_model(
|
||||
id=app.id, name=app.name, address=attrs['host'],
|
||||
id=app.id, name=name, address=attrs['host'],
|
||||
protocols='{}/{}'.format(app.type, attrs['port']),
|
||||
db_name=attrs['database'] or '',
|
||||
platform=platforms_map[app.type],
|
||||
@@ -61,8 +62,9 @@ def migrate_cloud_to_asset(apps, *args):
|
||||
for app in applications:
|
||||
attrs = app.attrs
|
||||
print("\t- Create cloud: {}".format(app.name))
|
||||
name = 'Cloud-{}'.format(app.name)
|
||||
cloud = cloud_model(
|
||||
id=app.id, name=app.name,
|
||||
id=app.id, name=name,
|
||||
address=attrs.get('cluster', ''),
|
||||
protocols='k8s/443', platform=platform,
|
||||
org_id=app.org_id,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
# Generated by Django 3.2.12 on 2022-07-11 06:13
|
||||
|
||||
import time
|
||||
from django.utils import timezone
|
||||
from itertools import groupby
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_asset_accounts(apps, schema_editor):
|
||||
auth_book_model = apps.get_model('assets', 'AuthBook')
|
||||
account_model = apps.get_model('accounts', 'Account')
|
||||
account_history_model = apps.get_model('accounts', 'HistoricalAccount')
|
||||
|
||||
count = 0
|
||||
bulk_size = 1000
|
||||
@@ -20,34 +23,35 @@ def migrate_asset_accounts(apps, schema_editor):
|
||||
break
|
||||
|
||||
count += len(auth_books)
|
||||
accounts = []
|
||||
# auth book 和 account 相同的属性
|
||||
same_attrs = [
|
||||
'id', 'username', 'comment', 'date_created', 'date_updated',
|
||||
'created_by', 'asset_id', 'org_id',
|
||||
]
|
||||
# 认证的属性,可能是 authbook 的,可能是 systemuser 的
|
||||
# 认证的属性,可能是 auth_book 的,可能是 system_user 的
|
||||
auth_attrs = ['password', 'private_key', 'token']
|
||||
all_attrs = same_attrs + auth_attrs
|
||||
|
||||
accounts = []
|
||||
for auth_book in auth_books:
|
||||
values = {'version': 1}
|
||||
account_values = {'version': 1}
|
||||
|
||||
system_user = auth_book.systemuser
|
||||
if system_user:
|
||||
# 更新一次系统用户的认证属性
|
||||
values.update({attr: getattr(system_user, attr, '') for attr in all_attrs})
|
||||
values['created_by'] = str(system_user.id)
|
||||
values['privileged'] = system_user.type == 'admin'
|
||||
account_values.update({attr: getattr(system_user, attr, '') for attr in all_attrs})
|
||||
account_values['created_by'] = str(system_user.id)
|
||||
account_values['privileged'] = system_user.type == 'admin' \
|
||||
or system_user.username in ['root', 'Administrator']
|
||||
|
||||
auth_book_auth = {attr: getattr(auth_book, attr, '') for attr in all_attrs if getattr(auth_book, attr, '')}
|
||||
# 最终使用 authbook 的认证属性
|
||||
values.update(auth_book_auth)
|
||||
# 最终优先使用 auth_book 的认证属性
|
||||
account_values.update(auth_book_auth)
|
||||
|
||||
auth_infos = []
|
||||
username = values['username']
|
||||
username = account_values['username']
|
||||
for attr in auth_attrs:
|
||||
secret = values.pop(attr, None)
|
||||
secret = account_values.pop(attr, None)
|
||||
if not secret:
|
||||
continue
|
||||
|
||||
@@ -66,13 +70,48 @@ def migrate_asset_accounts(apps, schema_editor):
|
||||
auth_infos.append((username, 'password', ''))
|
||||
|
||||
for name, secret_type, secret in auth_infos:
|
||||
account = account_model(**values, name=name, secret=secret, secret_type=secret_type)
|
||||
if not name:
|
||||
continue
|
||||
account = account_model(**account_values, name=name, secret=secret, secret_type=secret_type)
|
||||
accounts.append(account)
|
||||
|
||||
account_model.objects.bulk_create(accounts, ignore_conflicts=True)
|
||||
accounts.sort(key=lambda x: (x.name, x.asset_id, x.date_updated))
|
||||
grouped_accounts = groupby(accounts, lambda x: (x.name, x.asset_id))
|
||||
|
||||
accounts_to_add = []
|
||||
accounts_to_history = []
|
||||
for key, _accounts in grouped_accounts:
|
||||
_accounts = list(_accounts)
|
||||
if not _accounts:
|
||||
continue
|
||||
_account = _accounts[-1]
|
||||
accounts_to_add.append(_account)
|
||||
_account_history = []
|
||||
|
||||
for ac in _accounts:
|
||||
if not ac.secret:
|
||||
continue
|
||||
if ac.id != _account.id and ac.secret == _account.secret:
|
||||
continue
|
||||
history_data = {
|
||||
'id': _account.id,
|
||||
'secret': ac.secret,
|
||||
'secret_type': ac.secret_type,
|
||||
'history_date': ac.date_updated,
|
||||
'history_type': '~',
|
||||
'history_change_reason': 'from account {}'.format(_account.name),
|
||||
}
|
||||
_account_history.append(account_history_model(**history_data))
|
||||
_account.version = len(_account_history)
|
||||
accounts_to_history.extend(_account_history)
|
||||
|
||||
account_model.objects.bulk_create(accounts_to_add, ignore_conflicts=True)
|
||||
account_history_model.objects.bulk_create(accounts_to_history, ignore_conflicts=True)
|
||||
print("\t - Create asset accounts: {}-{} using: {:.2f}s".format(
|
||||
count - len(auth_books), count, time.time() - start
|
||||
))
|
||||
print("\t - accounts: {}".format(len(accounts_to_add)))
|
||||
print("\t - histories: {}".format(len(accounts_to_history)))
|
||||
|
||||
|
||||
def migrate_db_accounts(apps, schema_editor):
|
||||
@@ -130,6 +169,9 @@ def migrate_db_accounts(apps, schema_editor):
|
||||
values['secret_type'] = secret_type
|
||||
values['secret'] = secret
|
||||
|
||||
if not name:
|
||||
continue
|
||||
|
||||
for app in apps:
|
||||
values['asset_id'] = str(app.id)
|
||||
account = account_model(**values)
|
||||
|
||||
29
apps/assets/migrations/0110_auto_20230315_1741.py
Normal file
29
apps/assets/migrations/0110_auto_20230315_1741.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.2.17 on 2023-03-15 09:41
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def set_windows_platform_non_console(apps, schema_editor):
|
||||
Platform = apps.get_model('assets', 'Platform')
|
||||
names = ['Windows', 'Windows-RDP', 'Windows-TLS', 'RemoteAppHost']
|
||||
windows = Platform.objects.filter(name__in=names)
|
||||
if not windows:
|
||||
return
|
||||
|
||||
for p in windows:
|
||||
rdp = p.protocols.filter(name='rdp').first()
|
||||
if not rdp:
|
||||
continue
|
||||
rdp.setting['console'] = False
|
||||
rdp.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0109_alter_asset_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(set_windows_platform_non_console)
|
||||
]
|
||||
@@ -100,7 +100,7 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
|
||||
Type = const.AllTypes
|
||||
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
address = models.CharField(max_length=1024, verbose_name=_('Address'), db_index=True)
|
||||
address = models.CharField(max_length=767, verbose_name=_('Address'), db_index=True)
|
||||
platform = models.ForeignKey(Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',
|
||||
verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
|
||||
@@ -489,7 +489,7 @@ class SomeNodesMixin:
|
||||
return cls.default_node()
|
||||
|
||||
if ori_org and ori_org.is_root():
|
||||
return None
|
||||
return cls.default_node()
|
||||
|
||||
org_roots = cls.org_root_nodes()
|
||||
org_roots_length = len(org_roots)
|
||||
|
||||
@@ -11,7 +11,7 @@ __all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation']
|
||||
|
||||
class PlatformProtocol(models.Model):
|
||||
SETTING_ATTRS = {
|
||||
'console': True,
|
||||
'console': False,
|
||||
'security': 'any,tls,rdp',
|
||||
'sftp_enabled': True,
|
||||
'sftp_home': '/tmp'
|
||||
|
||||
@@ -73,7 +73,7 @@ class AssetAccountSerializer(
|
||||
'is_active', 'version', 'secret_type',
|
||||
]
|
||||
fields_write_only = [
|
||||
'secret', 'push_now', 'template'
|
||||
'secret', 'passphrase', 'push_now', 'template'
|
||||
]
|
||||
fields = fields_mini + fields_write_only
|
||||
extra_kwargs = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from django.core import validators
|
||||
|
||||
from assets.const.web import FillType
|
||||
from common.serializers import WritableNestedModelSerializer
|
||||
@@ -19,7 +18,7 @@ class ProtocolSettingSerializer(serializers.Serializer):
|
||||
("nla", "NLA"),
|
||||
]
|
||||
# RDP
|
||||
console = serializers.BooleanField(required=False)
|
||||
console = serializers.BooleanField(required=False, default=False)
|
||||
security = serializers.ChoiceField(choices=SECURITY_CHOICES, default="any")
|
||||
|
||||
# SFTP
|
||||
|
||||
@@ -8,7 +8,7 @@ from orgs.utils import tmp_to_root_org, tmp_to_org
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def task_activity_callback(self, pid, trigger, tp):
|
||||
def task_activity_callback(self, pid, trigger, tp, *args, **kwargs):
|
||||
model = AutomationTypes.get_type_model(tp)
|
||||
with tmp_to_root_org():
|
||||
instance = get_object_or_none(model, pk=pid)
|
||||
|
||||
@@ -10,6 +10,7 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from common.drf.filters import DatetimeRangeFilter
|
||||
from common.plugins.es import QuerySet as ESQuerySet
|
||||
from common.utils import is_uuid
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.api import OrgReadonlyModelViewSet, OrgModelViewSet
|
||||
from orgs.utils import current_org, tmp_to_root_org
|
||||
from orgs.models import Organization
|
||||
@@ -143,13 +144,19 @@ class OperateLogViewSet(OrgReadonlyModelViewSet):
|
||||
search_fields = ['resource', 'user']
|
||||
ordering = ['-datetime']
|
||||
|
||||
@lazyproperty
|
||||
def is_action_detail(self):
|
||||
return self.detail and self.request.query_params.get('type') == 'action_detail'
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.query_params.get('type') == 'action_detail':
|
||||
if self.is_action_detail:
|
||||
return OperateLogActionDetailSerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
def get_queryset(self):
|
||||
org_q = Q(org_id=Organization.SYSTEM_ID) | Q(org_id=current_org.id)
|
||||
org_q = Q(org_id=current_org.id)
|
||||
if self.is_action_detail:
|
||||
org_q |= Q(org_id=Organization.SYSTEM_ID)
|
||||
with tmp_to_root_org():
|
||||
qs = OperateLog.objects.filter(org_q)
|
||||
es_config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import transaction
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.models import User
|
||||
from common.utils import get_request_ip, get_logger
|
||||
from common.utils.timezone import as_current_tz
|
||||
from common.utils.encode import Singleton
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from audits.backends.db import OperateLogStore
|
||||
from common.serializers.fields import LabeledChoiceField
|
||||
from common.utils import reverse, i18n_trans
|
||||
@@ -78,7 +78,7 @@ class OperateLogActionDetailSerializer(serializers.ModelSerializer):
|
||||
return data
|
||||
|
||||
|
||||
class OperateLogSerializer(serializers.ModelSerializer):
|
||||
class OperateLogSerializer(BulkOrgResourceModelSerializer):
|
||||
action = LabeledChoiceField(choices=ActionChoices.choices, label=_("Action"))
|
||||
resource = serializers.SerializerMethodField(label=_("Resource"))
|
||||
resource_type = serializers.SerializerMethodField(label=_('Resource Type'))
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import codecs
|
||||
import copy
|
||||
import csv
|
||||
|
||||
from itertools import chain
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.http import HttpResponse
|
||||
|
||||
from common.utils.timezone import as_current_tz
|
||||
from common.utils import validate_ip, get_ip_city, get_logger
|
||||
from settings.serializers import SettingsSerializer
|
||||
from .const import DEFAULT_CITY
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -70,6 +72,8 @@ def _get_instance_field_value(
|
||||
f.verbose_name = 'id'
|
||||
elif isinstance(value, (list, dict)):
|
||||
value = copy.deepcopy(value)
|
||||
elif isinstance(value, datetime):
|
||||
value = as_current_tz(value).strftime('%Y-%m-%d %H:%M:%S')
|
||||
elif isinstance(f, models.OneToOneField) and isinstance(value, models.Model):
|
||||
nested_data = _get_instance_field_value(
|
||||
value, include_model_fields, model_need_continue_fields, ('id',)
|
||||
|
||||
@@ -225,6 +225,7 @@ class MFAMixin:
|
||||
self.request.session['auth_mfa_time'] = time.time()
|
||||
self.request.session['auth_mfa_required'] = 0
|
||||
self.request.session['auth_mfa_type'] = mfa_type
|
||||
MFABlockUtils(self.request.user.username, self.get_request_ip()).clean_failed_count()
|
||||
|
||||
def clean_mfa_mark(self):
|
||||
keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type']
|
||||
|
||||
@@ -60,7 +60,7 @@ class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
|
||||
'state': state,
|
||||
'redirect_uri': redirect_uri,
|
||||
}
|
||||
url = URL.AUTHEN + '?' + urlencode(params)
|
||||
url = URL().authen + '?' + urlencode(params)
|
||||
return url
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
import datetime
|
||||
from typing import Callable
|
||||
|
||||
from django.db import IntegrityError
|
||||
from django.templatetags.static import static
|
||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||
from django.http import HttpResponse, HttpRequest
|
||||
@@ -229,6 +230,23 @@ class UserLoginView(mixins.AuthMixin, UserLoginContextMixin, FormView):
|
||||
) as e:
|
||||
form.add_error('code', e.msg)
|
||||
return super().form_invalid(form)
|
||||
except (IntegrityError,) as e:
|
||||
# (1062, "Duplicate entry 'youtester001@example.com' for key 'users_user.email'")
|
||||
error = str(e)
|
||||
if len(e.args) < 2:
|
||||
form.add_error(None, error)
|
||||
return super().form_invalid(form)
|
||||
|
||||
msg_list = e.args[1].split("'")
|
||||
if len(msg_list) < 4:
|
||||
form.add_error(None, error)
|
||||
return super().form_invalid(form)
|
||||
|
||||
email, field = msg_list[1], msg_list[3]
|
||||
if field == 'users_user.email':
|
||||
error = _('User email already exists ({})').format(email)
|
||||
form.add_error(None, error)
|
||||
return super().form_invalid(form)
|
||||
self.clear_rsa_key()
|
||||
return self.redirect_to_guard_view()
|
||||
|
||||
|
||||
@@ -32,11 +32,14 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
|
||||
return super().get(*args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
from users.utils import MFABlockUtils
|
||||
code = form.cleaned_data.get('code')
|
||||
mfa_type = form.cleaned_data.get('mfa_type')
|
||||
|
||||
try:
|
||||
self._do_check_user_mfa(code, mfa_type)
|
||||
user, ip = self.get_user_from_session(), self.get_request_ip()
|
||||
MFABlockUtils(user.username, ip).clean_failed_count()
|
||||
return redirect_to_guard_view('mfa_ok')
|
||||
except (errors.MFAFailedError, errors.BlockMFAError) as e:
|
||||
form.add_error('code', e.msg)
|
||||
|
||||
@@ -2,4 +2,3 @@ from __future__ import absolute_import
|
||||
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from werkzeug.local import Local
|
||||
|
||||
from django.utils import translation
|
||||
|
||||
|
||||
thread_local = Local()
|
||||
encrypted_field_set = set()
|
||||
encrypted_field_set = {'password', 'secret'}
|
||||
|
||||
|
||||
def _find(attr):
|
||||
@@ -10,4 +13,5 @@ def _find(attr):
|
||||
|
||||
def add_encrypted_field_set(label):
|
||||
if label:
|
||||
encrypted_field_set.add(str(label))
|
||||
with translation.override('en'):
|
||||
encrypted_field_set.add(str(label))
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
from django.conf import settings
|
||||
from common.utils.common import get_logger
|
||||
from common.sdk.im.utils import digest
|
||||
from common.sdk.im.mixin import RequestMixin, BaseRequest
|
||||
@@ -11,14 +12,30 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
class URL:
|
||||
AUTHEN = 'https://open.feishu.cn/open-apis/authen/v1/index'
|
||||
|
||||
GET_TOKEN = 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/'
|
||||
|
||||
# https://open.feishu.cn/document/ukTMukTMukTM/uEDO4UjLxgDO14SM4gTN
|
||||
GET_USER_INFO_BY_CODE = 'https://open.feishu.cn/open-apis/authen/v1/access_token'
|
||||
@property
|
||||
def host(self):
|
||||
if settings.FEISHU_VERSION == 'feishu':
|
||||
h = 'https://open.feishu.cn'
|
||||
else:
|
||||
h = 'https://open.larksuite.com'
|
||||
return h
|
||||
|
||||
SEND_MESSAGE = 'https://open.feishu.cn/open-apis/im/v1/messages'
|
||||
@property
|
||||
def authen(self):
|
||||
return f'{self.host}/open-apis/authen/v1/index'
|
||||
|
||||
@property
|
||||
def get_token(self):
|
||||
return f'{self.host}/open-apis/auth/v3/tenant_access_token/internal/'
|
||||
|
||||
@property
|
||||
def get_user_info_by_code(self):
|
||||
return f'{self.host}/open-apis/authen/v1/access_token'
|
||||
|
||||
@property
|
||||
def send_message(self):
|
||||
return f'{self.host}/open-apis/im/v1/messages'
|
||||
|
||||
|
||||
class ErrorCode:
|
||||
@@ -51,7 +68,7 @@ class FeishuRequests(BaseRequest):
|
||||
|
||||
def request_access_token(self):
|
||||
data = {'app_id': self._app_id, 'app_secret': self._app_secret}
|
||||
response = self.raw_request('post', url=URL.GET_TOKEN, data=data)
|
||||
response = self.raw_request('post', url=URL().get_token, data=data)
|
||||
self.check_errcode_is_0(response)
|
||||
|
||||
access_token = response['tenant_access_token']
|
||||
@@ -86,7 +103,7 @@ class FeiShu(RequestMixin):
|
||||
'code': code
|
||||
}
|
||||
|
||||
data = self._requests.post(URL.GET_USER_INFO_BY_CODE, json=body, check_errcode_is_0=False)
|
||||
data = self._requests.post(URL().get_user_info_by_code, json=body, check_errcode_is_0=False)
|
||||
|
||||
self._requests.check_errcode_is_0(data)
|
||||
return data['data']['user_id']
|
||||
@@ -107,7 +124,7 @@ class FeiShu(RequestMixin):
|
||||
|
||||
try:
|
||||
logger.info(f'Feishu send text: user_ids={user_ids} msg={msg}')
|
||||
self._requests.post(URL.SEND_MESSAGE, params=params, json=body)
|
||||
self._requests.post(URL().send_message, params=params, json=body)
|
||||
except APIException as e:
|
||||
# 只处理可预知的错误
|
||||
logger.exception(e)
|
||||
|
||||
@@ -10,7 +10,7 @@ from .utils import get_logger
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def task_activity_callback(self, subject, message, recipient_list, **kwargs):
|
||||
def task_activity_callback(self, subject, message, recipient_list, *args, **kwargs):
|
||||
from users.models import User
|
||||
email_list = recipient_list
|
||||
resource_ids = list(User.objects.filter(email__in=email_list).values_list('id', flat=True))
|
||||
|
||||
@@ -108,7 +108,7 @@ class Subscription:
|
||||
try:
|
||||
self.sub.close()
|
||||
except Exception as e:
|
||||
logger.error('Unsubscribe msg error: {}'.format(e))
|
||||
logger.debug('Unsubscribe msg error: {}'.format(e))
|
||||
|
||||
def retry(self, _next, error, complete):
|
||||
logger.info('Retry subscribe channel: {}'.format(self.ch))
|
||||
|
||||
@@ -274,4 +274,4 @@ def ensure_last_char_is_ascii(data):
|
||||
def data_to_json(data, sort_keys=True, indent=2, cls=None):
|
||||
if cls is None:
|
||||
cls = DjangoJSONEncoder
|
||||
return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls)
|
||||
return json.dumps(data, ensure_ascii=False, sort_keys=sort_keys, indent=indent, cls=cls)
|
||||
|
||||
@@ -35,7 +35,10 @@ def i18n_trans(s):
|
||||
tpl, args = s.split(' % ', 1)
|
||||
args = args.split(', ')
|
||||
args = [gettext(arg) for arg in args]
|
||||
return gettext(tpl) % tuple(args)
|
||||
try:
|
||||
return gettext(tpl) % tuple(args)
|
||||
except TypeError:
|
||||
return gettext(tpl)
|
||||
|
||||
|
||||
def hello():
|
||||
|
||||
@@ -376,6 +376,7 @@ class Config(dict):
|
||||
'AUTH_FEISHU': False,
|
||||
'FEISHU_APP_ID': '',
|
||||
'FEISHU_APP_SECRET': '',
|
||||
'FEISHU_VERSION': 'feishu',
|
||||
|
||||
'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2
|
||||
'LOGIN_REDIRECT_MSG_ENABLED': True,
|
||||
|
||||
@@ -137,6 +137,7 @@ DINGTALK_APPSECRET = CONFIG.DINGTALK_APPSECRET
|
||||
AUTH_FEISHU = CONFIG.AUTH_FEISHU
|
||||
FEISHU_APP_ID = CONFIG.FEISHU_APP_ID
|
||||
FEISHU_APP_SECRET = CONFIG.FEISHU_APP_SECRET
|
||||
FEISHU_VERSION = CONFIG.FEISHU_VERSION
|
||||
|
||||
# Saml2 auth
|
||||
AUTH_SAML2 = CONFIG.AUTH_SAML2
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-02-23 19:11+0800\n"
|
||||
"POT-Creation-Date: 2023-03-02 16:00+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"
|
||||
@@ -394,7 +394,7 @@ msgid "Date last login"
|
||||
msgstr "最終ログイン日"
|
||||
|
||||
#: accounts/models/automations/gather_account.py:15
|
||||
#: accounts/models/automations/push_account.py:13 accounts/models/base.py:34
|
||||
#: accounts/models/automations/push_account.py:14 accounts/models/base.py:34
|
||||
#: acls/serializers/base.py:18 acls/serializers/base.py:49
|
||||
#: assets/models/_user.py:23 audits/models.py:157 authentication/forms.py:25
|
||||
#: authentication/forms.py:27 authentication/models/temp_token.py:9
|
||||
@@ -419,11 +419,11 @@ msgstr "自動収集アカウント"
|
||||
msgid "Gather asset accounts"
|
||||
msgstr "アカウントのコレクション"
|
||||
|
||||
#: accounts/models/automations/push_account.py:12
|
||||
#: accounts/models/automations/push_account.py:13
|
||||
msgid "Triggers"
|
||||
msgstr "トリガー方式"
|
||||
|
||||
#: accounts/models/automations/push_account.py:14 acls/models/base.py:81
|
||||
#: accounts/models/automations/push_account.py:15 acls/models/base.py:81
|
||||
#: acls/serializers/base.py:81 acls/serializers/login_acl.py:25
|
||||
#: assets/models/cmd_filter.py:81 audits/models.py:65 audits/serializers.py:82
|
||||
#: authentication/serializers/connect_token_secret.py:109
|
||||
@@ -431,7 +431,7 @@ msgstr "トリガー方式"
|
||||
msgid "Action"
|
||||
msgstr "アクション"
|
||||
|
||||
#: accounts/models/automations/push_account.py:40
|
||||
#: accounts/models/automations/push_account.py:43
|
||||
msgid "Push asset account"
|
||||
msgstr "アカウントプッシュ"
|
||||
|
||||
@@ -447,7 +447,7 @@ msgstr "アカウントの確認"
|
||||
#: assets/models/group.py:20 assets/models/label.py:18
|
||||
#: assets/models/platform.py:21 assets/models/platform.py:76
|
||||
#: assets/serializers/asset/common.py:67 assets/serializers/asset/common.py:143
|
||||
#: assets/serializers/platform.py:91 assets/serializers/platform.py:136
|
||||
#: assets/serializers/platform.py:133
|
||||
#: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21
|
||||
#: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57
|
||||
#: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19
|
||||
@@ -533,7 +533,7 @@ msgstr "エスクローされたパスワード"
|
||||
#: accounts/serializers/account/account.py:75 applications/models.py:11
|
||||
#: assets/models/label.py:21 assets/models/platform.py:77
|
||||
#: assets/serializers/asset/common.py:120 assets/serializers/cagegory.py:8
|
||||
#: assets/serializers/platform.py:97 assets/serializers/platform.py:137
|
||||
#: assets/serializers/platform.py:94 assets/serializers/platform.py:134
|
||||
#: perms/serializers/user_permission.py:26 settings/models.py:35
|
||||
#: tickets/models/ticket/apply_application.py:13
|
||||
msgid "Category"
|
||||
@@ -544,7 +544,7 @@ msgstr "カテゴリ"
|
||||
#: acls/serializers/command_acl.py:18 applications/models.py:14
|
||||
#: assets/models/_user.py:50 assets/models/automations/base.py:20
|
||||
#: assets/models/cmd_filter.py:74 assets/models/platform.py:78
|
||||
#: assets/serializers/asset/common.py:121 assets/serializers/platform.py:96
|
||||
#: assets/serializers/asset/common.py:121 assets/serializers/platform.py:93
|
||||
#: audits/serializers.py:48
|
||||
#: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102
|
||||
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31
|
||||
@@ -741,7 +741,7 @@ msgstr "アクティブ"
|
||||
#: authentication/models/sso_token.py:16
|
||||
#: notifications/models/notification.py:12
|
||||
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58
|
||||
#: perms/serializers/permission.py:23 rbac/builtin.py:118
|
||||
#: perms/serializers/permission.py:23 rbac/builtin.py:122
|
||||
#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:19
|
||||
#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:32
|
||||
#: terminal/notifications.py:96 terminal/notifications.py:144
|
||||
@@ -908,7 +908,7 @@ msgstr "アプリケーション"
|
||||
msgid "Can match application"
|
||||
msgstr "アプリケーションを一致させることができます"
|
||||
|
||||
#: assets/api/asset/asset.py:142
|
||||
#: assets/api/asset/asset.py:143
|
||||
msgid "Cannot create asset directly, you should create a host or other"
|
||||
msgstr ""
|
||||
"資産を直接作成することはできません。ホストまたはその他を作成する必要がありま"
|
||||
@@ -944,10 +944,8 @@ msgid "No account"
|
||||
msgstr "アカウントなし"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:36
|
||||
#, fuzzy
|
||||
#| msgid "Assets amount"
|
||||
msgid "Asset, {}, using account {}"
|
||||
msgstr "資産額"
|
||||
msgstr "資産, {}, アカウントを使用 {}"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:55
|
||||
#, python-brace-format
|
||||
@@ -1459,23 +1457,23 @@ msgstr "メタ"
|
||||
msgid "Internal"
|
||||
msgstr "ビルトイン"
|
||||
|
||||
#: assets/models/platform.py:83 assets/serializers/platform.py:94
|
||||
#: assets/models/platform.py:83 assets/serializers/platform.py:91
|
||||
msgid "Charset"
|
||||
msgstr "シャーセット"
|
||||
|
||||
#: assets/models/platform.py:85 assets/serializers/platform.py:122
|
||||
#: assets/models/platform.py:85 assets/serializers/platform.py:119
|
||||
msgid "Domain enabled"
|
||||
msgstr "ドメインを有効にする"
|
||||
|
||||
#: assets/models/platform.py:87 assets/serializers/platform.py:121
|
||||
#: assets/models/platform.py:87 assets/serializers/platform.py:118
|
||||
msgid "Su enabled"
|
||||
msgstr "アカウントの切り替えを有効にする"
|
||||
|
||||
#: assets/models/platform.py:88 assets/serializers/platform.py:104
|
||||
#: assets/models/platform.py:88 assets/serializers/platform.py:101
|
||||
msgid "Su method"
|
||||
msgstr "アカウントの切り替え方法"
|
||||
|
||||
#: assets/models/platform.py:90 assets/serializers/platform.py:101
|
||||
#: assets/models/platform.py:90 assets/serializers/platform.py:98
|
||||
msgid "Automation"
|
||||
msgstr "オートメーション"
|
||||
|
||||
@@ -1488,7 +1486,7 @@ msgstr "%(value)s は偶数ではありません"
|
||||
msgid "Auto fill"
|
||||
msgstr "自動充填"
|
||||
|
||||
#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:99
|
||||
#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:96
|
||||
#: authentication/serializers/connect_token_secret.py:28
|
||||
#: authentication/serializers/connect_token_secret.py:66
|
||||
#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99
|
||||
@@ -1629,7 +1627,7 @@ msgstr "アカウントの収集方法"
|
||||
msgid "Primary"
|
||||
msgstr "主要"
|
||||
|
||||
#: assets/serializers/platform.py:123
|
||||
#: assets/serializers/platform.py:120
|
||||
msgid "Default Domain"
|
||||
msgstr "デフォルト ドメイン"
|
||||
|
||||
@@ -1940,20 +1938,20 @@ msgid "Auth Token"
|
||||
msgstr "認証トークン"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:31 authentication/notifications.py:73
|
||||
#: authentication/views/login.py:73 authentication/views/wecom.py:177
|
||||
#: authentication/views/login.py:74 authentication/views/wecom.py:177
|
||||
#: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10
|
||||
#: users/models/user.py:778
|
||||
msgid "WeCom"
|
||||
msgstr "企業微信"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:144
|
||||
#: authentication/views/login.py:85 notifications/backends/__init__.py:14
|
||||
#: authentication/views/login.py:86 notifications/backends/__init__.py:14
|
||||
#: settings/serializers/auth/feishu.py:10 users/models/user.py:780
|
||||
msgid "FeiShu"
|
||||
msgstr "本を飛ばす"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:179
|
||||
#: authentication/views/login.py:79 notifications/backends/__init__.py:12
|
||||
#: authentication/views/login.py:80 notifications/backends/__init__.py:12
|
||||
#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:779
|
||||
msgid "DingTalk"
|
||||
msgstr "DingTalk"
|
||||
@@ -2237,15 +2235,15 @@ msgstr "本を飛ばすは拘束されていません"
|
||||
msgid "Your password is invalid"
|
||||
msgstr "パスワードが無効です"
|
||||
|
||||
#: authentication/errors/redirect.py:85 authentication/mixins.py:306
|
||||
#: authentication/errors/redirect.py:85 authentication/mixins.py:307
|
||||
msgid "Your password is too simple, please change it for security"
|
||||
msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください"
|
||||
|
||||
#: authentication/errors/redirect.py:93 authentication/mixins.py:313
|
||||
#: authentication/errors/redirect.py:93 authentication/mixins.py:314
|
||||
msgid "You should to change your password before login"
|
||||
msgstr "ログインする前にパスワードを変更する必要があります"
|
||||
|
||||
#: authentication/errors/redirect.py:101 authentication/mixins.py:320
|
||||
#: authentication/errors/redirect.py:101 authentication/mixins.py:321
|
||||
msgid "Your password has expired, please reset before logging in"
|
||||
msgstr ""
|
||||
"パスワードの有効期限が切れました。ログインする前にリセットしてください。"
|
||||
@@ -2348,11 +2346,11 @@ msgstr "無効にする電話番号をクリアする"
|
||||
msgid "Authentication failed (before login check failed): {}"
|
||||
msgstr "認証に失敗しました (ログインチェックが失敗する前): {}"
|
||||
|
||||
#: authentication/mixins.py:256
|
||||
#: authentication/mixins.py:257
|
||||
msgid "The MFA type ({}) is not enabled"
|
||||
msgstr "MFAタイプ ({}) が有効になっていない"
|
||||
|
||||
#: authentication/mixins.py:296
|
||||
#: authentication/mixins.py:297
|
||||
msgid "Please change your password"
|
||||
msgstr "パスワードを変更してください"
|
||||
|
||||
@@ -2812,19 +2810,23 @@ msgstr "本を飛ばすからユーザーを取得できませんでした"
|
||||
msgid "Please login with a password and then bind the FeiShu"
|
||||
msgstr "パスワードでログインしてから本を飛ばすをバインドしてください"
|
||||
|
||||
#: authentication/views/login.py:181
|
||||
#: authentication/views/login.py:182
|
||||
msgid "Redirecting"
|
||||
msgstr "リダイレクト"
|
||||
|
||||
#: authentication/views/login.py:182
|
||||
#: authentication/views/login.py:183
|
||||
msgid "Redirecting to {} authentication"
|
||||
msgstr "{} 認証へのリダイレクト"
|
||||
|
||||
#: authentication/views/login.py:205
|
||||
#: authentication/views/login.py:206
|
||||
msgid "Please enable cookies and try again."
|
||||
msgstr "クッキーを有効にして、もう一度お試しください。"
|
||||
|
||||
#: authentication/views/login.py:307
|
||||
#: authentication/views/login.py:238
|
||||
msgid "User email already exists ({})"
|
||||
msgstr "ユーザー メールボックスは既に存在します ({})"
|
||||
|
||||
#: authentication/views/login.py:318
|
||||
msgid ""
|
||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||
" Don't close this page"
|
||||
@@ -2832,15 +2834,15 @@ msgstr ""
|
||||
"<b>{}</b> 確認を待ちます。彼女/彼へのリンクをコピーすることもできます <br/>\n"
|
||||
" このページを閉じないでください"
|
||||
|
||||
#: authentication/views/login.py:312
|
||||
#: authentication/views/login.py:323
|
||||
msgid "No ticket found"
|
||||
msgstr "チケットが見つかりません"
|
||||
|
||||
#: authentication/views/login.py:348
|
||||
#: authentication/views/login.py:359
|
||||
msgid "Logout success"
|
||||
msgstr "ログアウト成功"
|
||||
|
||||
#: authentication/views/login.py:349
|
||||
#: authentication/views/login.py:360
|
||||
msgid "Logout success, return login page"
|
||||
msgstr "ログアウト成功、ログインページを返す"
|
||||
|
||||
@@ -3509,15 +3511,15 @@ msgstr "終了しました"
|
||||
msgid "Time cost"
|
||||
msgstr "時を過ごす"
|
||||
|
||||
#: ops/tasks.py:32
|
||||
#: ops/tasks.py:34
|
||||
msgid "Run ansible task"
|
||||
msgstr "Ansible タスクを実行する"
|
||||
|
||||
#: ops/tasks.py:61
|
||||
#: ops/tasks.py:63
|
||||
msgid "Run ansible task execution"
|
||||
msgstr "Ansible タスクの実行を開始する"
|
||||
|
||||
#: ops/tasks.py:77
|
||||
#: ops/tasks.py:79
|
||||
msgid "Clear celery periodic tasks"
|
||||
msgstr "タスクログを定期的にクリアする"
|
||||
|
||||
@@ -3735,7 +3737,7 @@ msgstr "内部の役割は、破壊することはできません"
|
||||
msgid "The role has been bound to users, can't be destroy"
|
||||
msgstr "ロールはユーザーにバインドされており、破壊することはできません"
|
||||
|
||||
#: rbac/api/role.py:83
|
||||
#: rbac/api/role.py:87
|
||||
msgid "Internal role, can't be update"
|
||||
msgstr "内部ロール、更新できません"
|
||||
|
||||
@@ -3747,27 +3749,27 @@ msgstr "{} 少なくとも1つのシステムロール"
|
||||
msgid "RBAC"
|
||||
msgstr "RBAC"
|
||||
|
||||
#: rbac/builtin.py:109
|
||||
#: rbac/builtin.py:113
|
||||
msgid "SystemAdmin"
|
||||
msgstr "システム管理者"
|
||||
|
||||
#: rbac/builtin.py:112
|
||||
#: rbac/builtin.py:116
|
||||
msgid "SystemAuditor"
|
||||
msgstr "システム監査人"
|
||||
|
||||
#: rbac/builtin.py:115
|
||||
#: rbac/builtin.py:119
|
||||
msgid "SystemComponent"
|
||||
msgstr "システムコンポーネント"
|
||||
|
||||
#: rbac/builtin.py:121
|
||||
#: rbac/builtin.py:125
|
||||
msgid "OrgAdmin"
|
||||
msgstr "組織管理者"
|
||||
|
||||
#: rbac/builtin.py:124
|
||||
#: rbac/builtin.py:128
|
||||
msgid "OrgAuditor"
|
||||
msgstr "監査員を組織する"
|
||||
|
||||
#: rbac/builtin.py:127
|
||||
#: rbac/builtin.py:131
|
||||
msgid "OrgUser"
|
||||
msgstr "組織ユーザー"
|
||||
|
||||
@@ -5408,9 +5410,9 @@ msgstr "セッション"
|
||||
msgid "Risk level"
|
||||
msgstr "リスクレベル"
|
||||
|
||||
#: terminal/connect_methods.py:47 terminal/connect_methods.py:48
|
||||
#: terminal/connect_methods.py:49 terminal/connect_methods.py:50
|
||||
#: terminal/connect_methods.py:51
|
||||
#: terminal/connect_methods.py:54 terminal/connect_methods.py:55
|
||||
#: terminal/connect_methods.py:56 terminal/connect_methods.py:57
|
||||
#: terminal/connect_methods.py:58
|
||||
msgid "DB Client"
|
||||
msgstr "データベース クライアント"
|
||||
|
||||
@@ -5899,11 +5901,11 @@ msgstr "孤立したセッションをクリアする"
|
||||
msgid "Upload session replay to external storage"
|
||||
msgstr "セッションの記録を外部ストレージにアップロードする"
|
||||
|
||||
#: terminal/tasks.py:83
|
||||
#: terminal/tasks.py:84
|
||||
msgid "Run applet host deployment"
|
||||
msgstr "アプリケーション マシンの展開を実行する"
|
||||
|
||||
#: terminal/tasks.py:90
|
||||
#: terminal/tasks.py:94
|
||||
msgid "Install applet"
|
||||
msgstr "アプリをインストールする"
|
||||
|
||||
@@ -7388,30 +7390,3 @@ msgstr "究極のエディション"
|
||||
#: xpack/plugins/license/models.py:85
|
||||
msgid "Community edition"
|
||||
msgstr "コミュニティ版"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Only admin users"
|
||||
#~ msgid "Unix admin user"
|
||||
#~ msgstr "管理者のみ"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Only admin users"
|
||||
#~ msgid "Windows admin user"
|
||||
#~ msgstr "管理者のみ"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Only admin users"
|
||||
#~ msgid "Linux admin user"
|
||||
#~ msgstr "管理者のみ"
|
||||
|
||||
#~ msgid "Can push account to asset"
|
||||
#~ msgstr "アカウントをアセットにプッシュできます"
|
||||
|
||||
#~ msgid "Add asset to node"
|
||||
#~ msgstr "ノードにアセットを追加する"
|
||||
|
||||
#~ msgid "Move asset to node"
|
||||
#~ msgstr "アセットをノードに移動する"
|
||||
|
||||
#~ msgid "Remove asset from node"
|
||||
#~ msgstr "ノードからアセットを削除"
|
||||
|
||||
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-02-23 19:11+0800\n"
|
||||
"POT-Creation-Date: 2023-03-02 16:00+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"
|
||||
@@ -393,7 +393,7 @@ msgid "Date last login"
|
||||
msgstr "最后登录日期"
|
||||
|
||||
#: accounts/models/automations/gather_account.py:15
|
||||
#: accounts/models/automations/push_account.py:13 accounts/models/base.py:34
|
||||
#: accounts/models/automations/push_account.py:14 accounts/models/base.py:34
|
||||
#: acls/serializers/base.py:18 acls/serializers/base.py:49
|
||||
#: assets/models/_user.py:23 audits/models.py:157 authentication/forms.py:25
|
||||
#: authentication/forms.py:27 authentication/models/temp_token.py:9
|
||||
@@ -418,11 +418,11 @@ msgstr "自动化收集账号"
|
||||
msgid "Gather asset accounts"
|
||||
msgstr "收集账号"
|
||||
|
||||
#: accounts/models/automations/push_account.py:12
|
||||
#: accounts/models/automations/push_account.py:13
|
||||
msgid "Triggers"
|
||||
msgstr "触发方式"
|
||||
|
||||
#: accounts/models/automations/push_account.py:14 acls/models/base.py:81
|
||||
#: accounts/models/automations/push_account.py:15 acls/models/base.py:81
|
||||
#: acls/serializers/base.py:81 acls/serializers/login_acl.py:25
|
||||
#: assets/models/cmd_filter.py:81 audits/models.py:65 audits/serializers.py:82
|
||||
#: authentication/serializers/connect_token_secret.py:109
|
||||
@@ -430,7 +430,7 @@ msgstr "触发方式"
|
||||
msgid "Action"
|
||||
msgstr "动作"
|
||||
|
||||
#: accounts/models/automations/push_account.py:40
|
||||
#: accounts/models/automations/push_account.py:43
|
||||
msgid "Push asset account"
|
||||
msgstr "账号推送"
|
||||
|
||||
@@ -446,7 +446,7 @@ msgstr "账号验证"
|
||||
#: assets/models/group.py:20 assets/models/label.py:18
|
||||
#: assets/models/platform.py:21 assets/models/platform.py:76
|
||||
#: assets/serializers/asset/common.py:67 assets/serializers/asset/common.py:143
|
||||
#: assets/serializers/platform.py:91 assets/serializers/platform.py:136
|
||||
#: assets/serializers/platform.py:133
|
||||
#: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21
|
||||
#: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57
|
||||
#: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19
|
||||
@@ -529,7 +529,7 @@ msgstr "已托管密码"
|
||||
#: accounts/serializers/account/account.py:75 applications/models.py:11
|
||||
#: assets/models/label.py:21 assets/models/platform.py:77
|
||||
#: assets/serializers/asset/common.py:120 assets/serializers/cagegory.py:8
|
||||
#: assets/serializers/platform.py:97 assets/serializers/platform.py:137
|
||||
#: assets/serializers/platform.py:94 assets/serializers/platform.py:134
|
||||
#: perms/serializers/user_permission.py:26 settings/models.py:35
|
||||
#: tickets/models/ticket/apply_application.py:13
|
||||
msgid "Category"
|
||||
@@ -540,7 +540,7 @@ msgstr "类别"
|
||||
#: acls/serializers/command_acl.py:18 applications/models.py:14
|
||||
#: assets/models/_user.py:50 assets/models/automations/base.py:20
|
||||
#: assets/models/cmd_filter.py:74 assets/models/platform.py:78
|
||||
#: assets/serializers/asset/common.py:121 assets/serializers/platform.py:96
|
||||
#: assets/serializers/asset/common.py:121 assets/serializers/platform.py:93
|
||||
#: audits/serializers.py:48
|
||||
#: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102
|
||||
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31
|
||||
@@ -737,7 +737,7 @@ msgstr "激活中"
|
||||
#: authentication/models/sso_token.py:16
|
||||
#: notifications/models/notification.py:12
|
||||
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58
|
||||
#: perms/serializers/permission.py:23 rbac/builtin.py:118
|
||||
#: perms/serializers/permission.py:23 rbac/builtin.py:122
|
||||
#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:19
|
||||
#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:32
|
||||
#: terminal/notifications.py:96 terminal/notifications.py:144
|
||||
@@ -902,7 +902,7 @@ msgstr "应用程序"
|
||||
msgid "Can match application"
|
||||
msgstr "匹配应用"
|
||||
|
||||
#: assets/api/asset/asset.py:142
|
||||
#: assets/api/asset/asset.py:143
|
||||
msgid "Cannot create asset directly, you should create a host or other"
|
||||
msgstr "不能直接创建资产, 你应该创建主机或其他资产"
|
||||
|
||||
@@ -936,10 +936,8 @@ msgid "No account"
|
||||
msgstr "没有账号"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:36
|
||||
#, fuzzy
|
||||
#| msgid "Assets amount"
|
||||
msgid "Asset, {}, using account {}"
|
||||
msgstr "资产数量"
|
||||
msgstr "资产, {}, 使用账号 {}"
|
||||
|
||||
#: assets/automations/ping_gateway/manager.py:55
|
||||
#, python-brace-format
|
||||
@@ -1451,23 +1449,23 @@ msgstr "元数据"
|
||||
msgid "Internal"
|
||||
msgstr "内置"
|
||||
|
||||
#: assets/models/platform.py:83 assets/serializers/platform.py:94
|
||||
#: assets/models/platform.py:83 assets/serializers/platform.py:91
|
||||
msgid "Charset"
|
||||
msgstr "编码"
|
||||
|
||||
#: assets/models/platform.py:85 assets/serializers/platform.py:122
|
||||
#: assets/models/platform.py:85 assets/serializers/platform.py:119
|
||||
msgid "Domain enabled"
|
||||
msgstr "启用网域"
|
||||
|
||||
#: assets/models/platform.py:87 assets/serializers/platform.py:121
|
||||
#: assets/models/platform.py:87 assets/serializers/platform.py:118
|
||||
msgid "Su enabled"
|
||||
msgstr "启用账号切换"
|
||||
|
||||
#: assets/models/platform.py:88 assets/serializers/platform.py:104
|
||||
#: assets/models/platform.py:88 assets/serializers/platform.py:101
|
||||
msgid "Su method"
|
||||
msgstr "账号切换方式"
|
||||
|
||||
#: assets/models/platform.py:90 assets/serializers/platform.py:101
|
||||
#: assets/models/platform.py:90 assets/serializers/platform.py:98
|
||||
msgid "Automation"
|
||||
msgstr "自动化"
|
||||
|
||||
@@ -1480,7 +1478,7 @@ msgstr "%(value)s is not an even number"
|
||||
msgid "Auto fill"
|
||||
msgstr "自动代填"
|
||||
|
||||
#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:99
|
||||
#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:96
|
||||
#: authentication/serializers/connect_token_secret.py:28
|
||||
#: authentication/serializers/connect_token_secret.py:66
|
||||
#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99
|
||||
@@ -1621,7 +1619,7 @@ msgstr "收集账号方式"
|
||||
msgid "Primary"
|
||||
msgstr "主要的"
|
||||
|
||||
#: assets/serializers/platform.py:123
|
||||
#: assets/serializers/platform.py:120
|
||||
msgid "Default Domain"
|
||||
msgstr "默认网域"
|
||||
|
||||
@@ -1930,20 +1928,20 @@ msgid "Auth Token"
|
||||
msgstr "认证令牌"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:31 authentication/notifications.py:73
|
||||
#: authentication/views/login.py:73 authentication/views/wecom.py:177
|
||||
#: authentication/views/login.py:74 authentication/views/wecom.py:177
|
||||
#: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10
|
||||
#: users/models/user.py:778
|
||||
msgid "WeCom"
|
||||
msgstr "企业微信"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:144
|
||||
#: authentication/views/login.py:85 notifications/backends/__init__.py:14
|
||||
#: authentication/views/login.py:86 notifications/backends/__init__.py:14
|
||||
#: settings/serializers/auth/feishu.py:10 users/models/user.py:780
|
||||
msgid "FeiShu"
|
||||
msgstr "飞书"
|
||||
|
||||
#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:179
|
||||
#: authentication/views/login.py:79 notifications/backends/__init__.py:12
|
||||
#: authentication/views/login.py:80 notifications/backends/__init__.py:12
|
||||
#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:779
|
||||
msgid "DingTalk"
|
||||
msgstr "钉钉"
|
||||
@@ -2217,15 +2215,15 @@ msgstr "没有绑定飞书"
|
||||
msgid "Your password is invalid"
|
||||
msgstr "您的密码无效"
|
||||
|
||||
#: authentication/errors/redirect.py:85 authentication/mixins.py:306
|
||||
#: authentication/errors/redirect.py:85 authentication/mixins.py:307
|
||||
msgid "Your password is too simple, please change it for security"
|
||||
msgstr "你的密码过于简单,为了安全,请修改"
|
||||
|
||||
#: authentication/errors/redirect.py:93 authentication/mixins.py:313
|
||||
#: authentication/errors/redirect.py:93 authentication/mixins.py:314
|
||||
msgid "You should to change your password before login"
|
||||
msgstr "登录完成前,请先修改密码"
|
||||
|
||||
#: authentication/errors/redirect.py:101 authentication/mixins.py:320
|
||||
#: authentication/errors/redirect.py:101 authentication/mixins.py:321
|
||||
msgid "Your password has expired, please reset before logging in"
|
||||
msgstr "您的密码已过期,先修改再登录"
|
||||
|
||||
@@ -2326,11 +2324,11 @@ msgstr "清空手机号码禁用"
|
||||
msgid "Authentication failed (before login check failed): {}"
|
||||
msgstr "认证失败(登录前检查失败): {}"
|
||||
|
||||
#: authentication/mixins.py:256
|
||||
#: authentication/mixins.py:257
|
||||
msgid "The MFA type ({}) is not enabled"
|
||||
msgstr "该 MFA ({}) 方式没有启用"
|
||||
|
||||
#: authentication/mixins.py:296
|
||||
#: authentication/mixins.py:297
|
||||
msgid "Please change your password"
|
||||
msgstr "请修改密码"
|
||||
|
||||
@@ -2782,19 +2780,23 @@ msgstr "从飞书获取用户失败"
|
||||
msgid "Please login with a password and then bind the FeiShu"
|
||||
msgstr "请使用密码登录,然后绑定飞书"
|
||||
|
||||
#: authentication/views/login.py:181
|
||||
#: authentication/views/login.py:182
|
||||
msgid "Redirecting"
|
||||
msgstr "跳转中"
|
||||
|
||||
#: authentication/views/login.py:182
|
||||
#: authentication/views/login.py:183
|
||||
msgid "Redirecting to {} authentication"
|
||||
msgstr "正在跳转到 {} 认证"
|
||||
|
||||
#: authentication/views/login.py:205
|
||||
#: authentication/views/login.py:206
|
||||
msgid "Please enable cookies and try again."
|
||||
msgstr "设置你的浏览器支持cookie"
|
||||
|
||||
#: authentication/views/login.py:307
|
||||
#: authentication/views/login.py:238
|
||||
msgid "User email already exists ({})"
|
||||
msgstr "用户邮箱已存在 ({})"
|
||||
|
||||
#: authentication/views/login.py:318
|
||||
msgid ""
|
||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||
" Don't close this page"
|
||||
@@ -2802,15 +2804,15 @@ msgstr ""
|
||||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||
" 不要关闭本页面"
|
||||
|
||||
#: authentication/views/login.py:312
|
||||
#: authentication/views/login.py:323
|
||||
msgid "No ticket found"
|
||||
msgstr "没有发现工单"
|
||||
|
||||
#: authentication/views/login.py:348
|
||||
#: authentication/views/login.py:359
|
||||
msgid "Logout success"
|
||||
msgstr "退出登录成功"
|
||||
|
||||
#: authentication/views/login.py:349
|
||||
#: authentication/views/login.py:360
|
||||
msgid "Logout success, return login page"
|
||||
msgstr "退出登录成功,返回到登录页面"
|
||||
|
||||
@@ -3474,15 +3476,15 @@ msgstr "是否完成"
|
||||
msgid "Time cost"
|
||||
msgstr "花费时间"
|
||||
|
||||
#: ops/tasks.py:32
|
||||
#: ops/tasks.py:34
|
||||
msgid "Run ansible task"
|
||||
msgstr "运行 Ansible 任务"
|
||||
|
||||
#: ops/tasks.py:61
|
||||
#: ops/tasks.py:63
|
||||
msgid "Run ansible task execution"
|
||||
msgstr "开始执行 Ansible 任务"
|
||||
|
||||
#: ops/tasks.py:77
|
||||
#: ops/tasks.py:79
|
||||
msgid "Clear celery periodic tasks"
|
||||
msgstr "清理周期任务"
|
||||
|
||||
@@ -3699,7 +3701,7 @@ msgstr "内部角色,不能删除"
|
||||
msgid "The role has been bound to users, can't be destroy"
|
||||
msgstr "角色已绑定用户,不能删除"
|
||||
|
||||
#: rbac/api/role.py:83
|
||||
#: rbac/api/role.py:87
|
||||
msgid "Internal role, can't be update"
|
||||
msgstr "内部角色,不能更新"
|
||||
|
||||
@@ -3711,27 +3713,27 @@ msgstr "{} 至少有一个系统角色"
|
||||
msgid "RBAC"
|
||||
msgstr "RBAC"
|
||||
|
||||
#: rbac/builtin.py:109
|
||||
#: rbac/builtin.py:113
|
||||
msgid "SystemAdmin"
|
||||
msgstr "系统管理员"
|
||||
|
||||
#: rbac/builtin.py:112
|
||||
#: rbac/builtin.py:116
|
||||
msgid "SystemAuditor"
|
||||
msgstr "系统审计员"
|
||||
|
||||
#: rbac/builtin.py:115
|
||||
#: rbac/builtin.py:119
|
||||
msgid "SystemComponent"
|
||||
msgstr "系统组件"
|
||||
|
||||
#: rbac/builtin.py:121
|
||||
#: rbac/builtin.py:125
|
||||
msgid "OrgAdmin"
|
||||
msgstr "组织管理员"
|
||||
|
||||
#: rbac/builtin.py:124
|
||||
#: rbac/builtin.py:128
|
||||
msgid "OrgAuditor"
|
||||
msgstr "组织审计员"
|
||||
|
||||
#: rbac/builtin.py:127
|
||||
#: rbac/builtin.py:131
|
||||
msgid "OrgUser"
|
||||
msgstr "组织用户"
|
||||
|
||||
@@ -5336,9 +5338,9 @@ msgstr "会话"
|
||||
msgid "Risk level"
|
||||
msgstr "风险等级"
|
||||
|
||||
#: terminal/connect_methods.py:47 terminal/connect_methods.py:48
|
||||
#: terminal/connect_methods.py:49 terminal/connect_methods.py:50
|
||||
#: terminal/connect_methods.py:51
|
||||
#: terminal/connect_methods.py:54 terminal/connect_methods.py:55
|
||||
#: terminal/connect_methods.py:56 terminal/connect_methods.py:57
|
||||
#: terminal/connect_methods.py:58
|
||||
msgid "DB Client"
|
||||
msgstr "数据库客户端"
|
||||
|
||||
@@ -5825,11 +5827,11 @@ msgstr "清除孤儿会话"
|
||||
msgid "Upload session replay to external storage"
|
||||
msgstr "上传会话录像到外部存储"
|
||||
|
||||
#: terminal/tasks.py:83
|
||||
#: terminal/tasks.py:84
|
||||
msgid "Run applet host deployment"
|
||||
msgstr "运行应用机部署"
|
||||
|
||||
#: terminal/tasks.py:90
|
||||
#: terminal/tasks.py:94
|
||||
msgid "Install applet"
|
||||
msgstr "安装应用"
|
||||
|
||||
|
||||
@@ -62,10 +62,10 @@ class PlaybookFileBrowserAPIView(APIView):
|
||||
rbac_perms = ()
|
||||
permission_classes = (RBACPermission,)
|
||||
rbac_perms = {
|
||||
'GET': 'ops.change_playbooks',
|
||||
'POST': 'ops.change_playbooks',
|
||||
'DELETE': 'ops.change_playbooks',
|
||||
'PATCH': 'ops.change_playbooks',
|
||||
'GET': 'ops.change_playbook',
|
||||
'POST': 'ops.change_playbook',
|
||||
'DELETE': 'ops.change_playbook',
|
||||
'PATCH': 'ops.change_playbook',
|
||||
}
|
||||
protected_files = ['root', 'main.yml']
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
from celery import shared_task
|
||||
from celery.exceptions import SoftTimeLimitExceeded
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from ops.celery import app
|
||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
from .celery.decorator import (
|
||||
register_as_period_task, after_app_ready_start
|
||||
@@ -19,7 +21,7 @@ from .notifications import ServerPerformanceCheckUtil
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def job_task_activity_callback(self, job_id, trigger):
|
||||
def job_task_activity_callback(self, job_id, *args, **kwargs):
|
||||
job = get_object_or_none(Job, id=job_id)
|
||||
if not job:
|
||||
return
|
||||
@@ -48,7 +50,7 @@ def run_ops_job(job_id):
|
||||
logger.error("Start adhoc execution error: {}".format(e))
|
||||
|
||||
|
||||
def job_execution_task_activity_callback(self, execution_id, trigger):
|
||||
def job_execution_task_activity_callback(self, execution_id, *args, **kwargs):
|
||||
execution = get_object_or_none(JobExecution, id=execution_id)
|
||||
if not execution:
|
||||
return
|
||||
@@ -78,16 +80,14 @@ def run_ops_job_execution(execution_id, **kwargs):
|
||||
@after_app_ready_start
|
||||
def clean_celery_periodic_tasks():
|
||||
"""清除celery定时任务"""
|
||||
need_cleaned_tasks = [
|
||||
'handle_be_interrupted_change_auth_task_periodic',
|
||||
]
|
||||
logger.info('Start clean celery periodic tasks: {}'.format(need_cleaned_tasks))
|
||||
for task_name in need_cleaned_tasks:
|
||||
logger.info('Start clean task: {}'.format(task_name))
|
||||
task = get_celery_periodic_task(task_name)
|
||||
if task is None:
|
||||
logger.info('Task does not exist: {}'.format(task_name))
|
||||
logger.info('Start clean celery periodic tasks.')
|
||||
register_tasks = PeriodicTask.objects.all()
|
||||
for task in register_tasks:
|
||||
if task.task in app.tasks:
|
||||
continue
|
||||
|
||||
task_name = task.name
|
||||
logger.info('Start clean task: {}'.format(task_name))
|
||||
disable_celery_periodic_task(task_name)
|
||||
delete_celery_periodic_task(task_name)
|
||||
task = get_celery_periodic_task(task_name)
|
||||
|
||||
@@ -13,7 +13,7 @@ class CeleryTaskLogView(PermissionsMixin, TemplateView):
|
||||
template_name = 'ops/celery_task_log.html'
|
||||
permission_classes = [RBACPermission]
|
||||
rbac_perms = {
|
||||
'GET': 'ops.view_celerytask'
|
||||
'GET': 'ops.view_celerytaskexecution'
|
||||
}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
@@ -114,9 +114,7 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
|
||||
@staticmethod
|
||||
def compute_total_count_today_active_assets():
|
||||
t = local_zero_hour()
|
||||
return Session.objects.filter(
|
||||
date_start__gte=t, is_success=False
|
||||
).values('asset_id').distinct().count()
|
||||
return Session.objects.filter(date_start__gte=t).values('asset_id').distinct().count()
|
||||
|
||||
@staticmethod
|
||||
def compute_total_count_today_failed_sessions():
|
||||
|
||||
@@ -102,7 +102,10 @@ def on_post_delete_refresh_org_resource_statistics_cache(sender, instance, **kwa
|
||||
|
||||
|
||||
def _refresh_session_org_resource_statistics_cache(instance: Session):
|
||||
cache_field_name = ['total_count_online_users', 'total_count_online_sessions', 'total_count_today_failed_sessions']
|
||||
cache_field_name = [
|
||||
'total_count_online_users', 'total_count_online_sessions',
|
||||
'total_count_today_active_assets','total_count_today_failed_sessions'
|
||||
]
|
||||
|
||||
org_cache = OrgResourceStatisticsCache(instance.org)
|
||||
org_cache.expire(*cache_field_name)
|
||||
|
||||
@@ -30,6 +30,12 @@ class BaseUserPermedAssetsApi(SelfOrPKUserMixin, ListAPIView):
|
||||
filterset_class = AssetFilterSet
|
||||
serializer_class = serializers.AssetPermedSerializer
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = super().get_serializer_class()
|
||||
if self.request.query_params.get('id'):
|
||||
serializer_class = serializers.AssetPermedDetailSerializer
|
||||
return serializer_class
|
||||
|
||||
def get_queryset(self):
|
||||
if getattr(self, 'swagger_fake_view', False):
|
||||
return Asset.objects.none()
|
||||
|
||||
@@ -23,6 +23,7 @@ def migrate_app_perms_to_assets(apps, schema_editor):
|
||||
asset_permission = asset_permission_model()
|
||||
for attr in attrs:
|
||||
setattr(asset_permission, attr, getattr(app_perm, attr))
|
||||
asset_permission.name = f"App-{app_perm.name}"
|
||||
asset_permissions.append(asset_permission)
|
||||
asset_permission_model.objects.bulk_create(asset_permissions, ignore_conflicts=True)
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ def migrate_system_user_to_accounts(apps, schema_editor):
|
||||
bulk_size = 10000
|
||||
while True:
|
||||
asset_permissions = asset_permission_model.objects \
|
||||
.prefetch_related('system_users')[count:bulk_size]
|
||||
.prefetch_related('system_users')[count:bulk_size]
|
||||
if not asset_permissions:
|
||||
break
|
||||
count += len(asset_permissions)
|
||||
|
||||
count += len(asset_permissions)
|
||||
updated = []
|
||||
for asset_permission in asset_permissions:
|
||||
asset_permission.accounts = [s.username for s in asset_permission.system_users.all()]
|
||||
|
||||
@@ -15,7 +15,7 @@ from perms.serializers.permission import ActionChoicesField
|
||||
|
||||
__all__ = [
|
||||
'NodePermedSerializer', 'AssetPermedSerializer',
|
||||
'AccountsPermedSerializer'
|
||||
'AssetPermedDetailSerializer', 'AccountsPermedSerializer'
|
||||
]
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ class AssetPermedSerializer(OrgResourceModelSerializerMixin):
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetPermedDetailSerializer(AssetPermedSerializer):
|
||||
class Meta(AssetPermedSerializer.Meta):
|
||||
fields = AssetPermedSerializer.Meta.fields + ['spec_info']
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class NodePermedSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Node
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from orgs.utils import tmp_to_org
|
||||
from accounts.models import Account
|
||||
from accounts.const import AliasAccount
|
||||
from .permission import AssetPermissionUtil
|
||||
@@ -16,10 +17,11 @@ class PermAccountUtil(AssetPermissionUtil):
|
||||
:param asset: Asset
|
||||
:param account_name: 可能是 @USER @INPUT 字符串
|
||||
"""
|
||||
permed_accounts = self.get_permed_accounts_for_user(user, asset)
|
||||
accounts_mapper = {account.alias: account for account in permed_accounts}
|
||||
account = accounts_mapper.get(account_name)
|
||||
return account
|
||||
with tmp_to_org(asset.org):
|
||||
permed_accounts = self.get_permed_accounts_for_user(user, asset)
|
||||
accounts_mapper = {account.alias: account for account in permed_accounts}
|
||||
account = accounts_mapper.get(account_name)
|
||||
return account
|
||||
|
||||
def get_permed_accounts_for_user(self, user, asset):
|
||||
""" 获取授权给用户某个资产的账号 """
|
||||
|
||||
@@ -18,14 +18,19 @@ user_perms = (
|
||||
('assets', 'asset', 'match', 'asset'),
|
||||
('assets', 'systemuser', 'match', 'systemuser'),
|
||||
('assets', 'node', 'match', 'node'),
|
||||
("ops", "adhoc", "*", "*"),
|
||||
("ops", "playbook", "*", "*"),
|
||||
("ops", "job", "*", "*"),
|
||||
("ops", "jobexecution", "*", "*"),
|
||||
("ops", "celerytaskexecution", "view", "*"),
|
||||
)
|
||||
|
||||
system_user_perms = (
|
||||
('authentication', 'connectiontoken', 'add,change,view', 'connectiontoken'),
|
||||
('authentication', 'temptoken', 'add,change,view', 'temptoken'),
|
||||
('authentication', 'accesskey', '*', '*'),
|
||||
('tickets', 'ticket', 'view', 'ticket'),
|
||||
) + user_perms + _view_all_joined_org_perms
|
||||
('authentication', 'connectiontoken', 'add,change,view', 'connectiontoken'),
|
||||
('authentication', 'temptoken', 'add,change,view', 'temptoken'),
|
||||
('authentication', 'accesskey', '*', '*'),
|
||||
('tickets', 'ticket', 'view', 'ticket'),
|
||||
) + user_perms + _view_all_joined_org_perms
|
||||
|
||||
_auditor_perms = (
|
||||
('rbac', 'menupermission', 'view', 'audit'),
|
||||
@@ -41,7 +46,6 @@ auditor_perms = user_perms + _auditor_perms
|
||||
|
||||
system_auditor_perms = system_user_perms + _auditor_perms + _view_root_perms
|
||||
|
||||
|
||||
app_exclude_perms = [
|
||||
('users', 'user', 'add,delete', 'user'),
|
||||
('orgs', 'org', 'add,delete,change', 'org'),
|
||||
|
||||
@@ -135,7 +135,7 @@ only_system_permissions = (
|
||||
('xpack', 'license', '*', '*'),
|
||||
('settings', 'setting', '*', '*'),
|
||||
('tickets', '*', '*', '*'),
|
||||
('ops', 'task', 'view', 'taskmonitor'),
|
||||
('ops', 'celerytask', 'view', 'taskmonitor'),
|
||||
('terminal', 'terminal', '*', '*'),
|
||||
('terminal', 'commandstorage', '*', '*'),
|
||||
('terminal', 'replaystorage', '*', '*'),
|
||||
|
||||
@@ -97,13 +97,13 @@ class RBACPermission(permissions.DjangoModelPermissions):
|
||||
else:
|
||||
model_cls = queryset.model
|
||||
except AssertionError as e:
|
||||
logger.error(f'Error get model cls: {e}')
|
||||
# logger.error(f'Error get model cls: {e}')
|
||||
model_cls = None
|
||||
except AttributeError as e:
|
||||
logger.error(f'Error get model cls: {e}')
|
||||
# logger.error(f'Error get model cls: {e}')
|
||||
model_cls = None
|
||||
except Exception as e:
|
||||
logger.error('Error get model class: {} of {}'.format(e, view))
|
||||
# logger.error('Error get model class: {} of {}'.format(e, view))
|
||||
raise e
|
||||
return model_cls
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class MailTestingAPI(APIView):
|
||||
# if k.startswith('EMAIL'):
|
||||
# setattr(settings, k, v)
|
||||
try:
|
||||
subject = settings.EMAIL_SUBJECT_PREFIX + "Test"
|
||||
subject = settings.EMAIL_SUBJECT_PREFIX or '' + "Test"
|
||||
message = "Test smtp setting"
|
||||
email_from = email_from or email_host_user
|
||||
email_recipient = email_recipient or email_from
|
||||
|
||||
@@ -9,6 +9,13 @@ __all__ = ['FeiShuSettingSerializer']
|
||||
class FeiShuSettingSerializer(serializers.Serializer):
|
||||
PREFIX_TITLE = _('FeiShu')
|
||||
|
||||
VERSION_CHOICES = (
|
||||
('feishu', _('FeiShu')),
|
||||
('lark', 'Lark')
|
||||
)
|
||||
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('Enable FeiShu Auth'))
|
||||
FEISHU_APP_ID = serializers.CharField(max_length=256, required=True, label='App ID')
|
||||
FEISHU_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
|
||||
AUTH_FEISHU = serializers.BooleanField(default=False, label=_('Enable FeiShu Auth'))
|
||||
FEISHU_VERSION = serializers.ChoiceField(
|
||||
choices=VERSION_CHOICES, default='feishu', label=_('Version')
|
||||
)
|
||||
|
||||
@@ -17,17 +17,17 @@ class WebMethod(TextChoices):
|
||||
|
||||
@classmethod
|
||||
def get_methods(cls):
|
||||
return {
|
||||
methods = {
|
||||
Protocol.ssh: [cls.web_cli, cls.web_sftp],
|
||||
Protocol.telnet: [cls.web_cli],
|
||||
Protocol.rdp: [cls.web_gui],
|
||||
Protocol.vnc: [cls.web_gui],
|
||||
|
||||
Protocol.mysql: [cls.web_cli, cls.web_gui],
|
||||
Protocol.mariadb: [cls.web_cli, cls.web_gui],
|
||||
Protocol.oracle: [cls.web_cli, cls.web_gui],
|
||||
Protocol.postgresql: [cls.web_cli, cls.web_gui],
|
||||
Protocol.sqlserver: [cls.web_cli, cls.web_gui],
|
||||
Protocol.mysql: [cls.web_cli],
|
||||
Protocol.mariadb: [cls.web_cli],
|
||||
Protocol.oracle: [cls.web_cli],
|
||||
Protocol.postgresql: [cls.web_cli],
|
||||
Protocol.sqlserver: [cls.web_cli],
|
||||
Protocol.redis: [cls.web_cli],
|
||||
Protocol.mongodb: [cls.web_cli],
|
||||
Protocol.clickhouse: [cls.web_cli],
|
||||
@@ -35,6 +35,13 @@ class WebMethod(TextChoices):
|
||||
Protocol.k8s: [cls.web_cli],
|
||||
Protocol.http: []
|
||||
}
|
||||
if not settings.XPACK_ENABLED:
|
||||
return methods
|
||||
|
||||
web_gui_dbs = [Protocol.mysql, Protocol.mariadb, Protocol.oracle, Protocol.postgresql]
|
||||
for db in web_gui_dbs:
|
||||
methods[db].append(cls.web_gui)
|
||||
return methods
|
||||
|
||||
|
||||
class NativeClient(TextChoices):
|
||||
@@ -130,8 +137,6 @@ class AppletMethod:
|
||||
from .models import Applet, AppletHost
|
||||
|
||||
methods = defaultdict(list)
|
||||
if not settings.XPACK_ENABLED:
|
||||
return methods
|
||||
|
||||
has_applet_hosts = AppletHost.objects.all().exists()
|
||||
applets = Applet.objects.filter(is_active=True)
|
||||
|
||||
@@ -105,9 +105,13 @@ class Session(OrgModelMixin):
|
||||
def find_ok_relative_path_in_storage(self, storage):
|
||||
session_paths = self.get_all_possible_relative_path()
|
||||
for rel_path in session_paths:
|
||||
if storage.exists(rel_path):
|
||||
return rel_path
|
||||
|
||||
# storage 为多个外部存储时, 可能会因部分不可用,
|
||||
# 抛出异常, 影响录像的获取
|
||||
try:
|
||||
if storage.exists(rel_path):
|
||||
return rel_path
|
||||
except:
|
||||
pass
|
||||
@property
|
||||
def asset_obj(self):
|
||||
return Asset.objects.get(id=self.asset_id)
|
||||
|
||||
@@ -80,14 +80,20 @@ def upload_session_replay_to_external_storage(session_id):
|
||||
return
|
||||
|
||||
|
||||
@shared_task(verbose_name=_('Run applet host deployment'), activity_callback=lambda did: ([did], ))
|
||||
@shared_task(
|
||||
verbose_name=_('Run applet host deployment'),
|
||||
activity_callback=lambda self, did, *args, **kwargs: ([did], )
|
||||
)
|
||||
def run_applet_host_deployment(did):
|
||||
with tmp_to_builtin_org(system=1):
|
||||
deployment = AppletHostDeployment.objects.get(id=did)
|
||||
deployment.start()
|
||||
|
||||
|
||||
@shared_task(verbose_name=_('Install applet'), activity_callback=lambda did, applet_id: ([did],))
|
||||
@shared_task(
|
||||
verbose_name=_('Install applet'),
|
||||
activity_callback=lambda self, did, applet_id, *args, **kwargs: ([did],)
|
||||
)
|
||||
def run_applet_host_deployment_install_applet(did, applet_id):
|
||||
with tmp_to_builtin_org(system=1):
|
||||
deployment = AppletHostDeployment.objects.get(id=did)
|
||||
|
||||
Reference in New Issue
Block a user