mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-07-17 08:21:38 +00:00
t po v3
:wqMerge branch 'v3' of github.com:jumpserver/jumpserver into v3
This commit is contained in:
commit
1b1c91bab0
@ -4,7 +4,6 @@ from .automations import *
|
|||||||
from .category import *
|
from .category import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
from .favorite_asset import *
|
from .favorite_asset import *
|
||||||
from .gathered_user import *
|
|
||||||
from .label import *
|
from .label import *
|
||||||
from .mixin import *
|
from .mixin import *
|
||||||
from .node import *
|
from .node import *
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
from orgs.mixins.api import OrgModelViewSet
|
|
||||||
from assets.models import GatheredUser
|
|
||||||
|
|
||||||
from ..serializers import GatheredUserSerializer
|
|
||||||
from ..filters import AssetRelatedByNodeFilterBackend
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['GatheredUserViewSet']
|
|
||||||
|
|
||||||
|
|
||||||
class GatheredUserViewSet(OrgModelViewSet):
|
|
||||||
model = GatheredUser
|
|
||||||
serializer_class = GatheredUserSerializer
|
|
||||||
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
|
|
||||||
|
|
||||||
filterset_fields = ['asset', 'username', 'present', 'asset__address', 'asset__name', 'asset_id']
|
|
||||||
search_fields = ['username', 'asset__address', 'asset__name']
|
|
@ -1,8 +1,9 @@
|
|||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from assets.const import AutomationTypes
|
from assets.const import AutomationTypes, Source
|
||||||
from orgs.utils import tmp_to_org
|
from orgs.utils import tmp_to_org
|
||||||
from .filter import GatherAccountsFilter
|
from .filter import GatherAccountsFilter
|
||||||
from ...models import GatheredUser
|
|
||||||
from ..base.manager import BasePlaybookManager
|
from ..base.manager import BasePlaybookManager
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@ -26,20 +27,33 @@ class GatherAccountsManager(BasePlaybookManager):
|
|||||||
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
|
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bulk_create_accounts(asset, result):
|
||||||
|
account_objs = []
|
||||||
|
account_model = asset.accounts.model
|
||||||
|
account_usernames = set(asset.accounts.values_list('username', flat=True))
|
||||||
|
with tmp_to_org(asset.org_id):
|
||||||
|
accounts_dict = {}
|
||||||
|
for username, data in result.items():
|
||||||
|
comment = ''
|
||||||
|
d = {'asset': asset, 'username': username, 'name': username, 'source': Source.COLLECTED}
|
||||||
|
if data.get('date'):
|
||||||
|
comment += f"{_('Date last login')}: {data['date']}\n "
|
||||||
|
if data.get('address'):
|
||||||
|
comment += f"{_('IP last login')}: {data['address'][:32]}"
|
||||||
|
d['comment'] = comment
|
||||||
|
accounts_dict[username] = d
|
||||||
|
for username, data in accounts_dict.items():
|
||||||
|
if username in account_usernames:
|
||||||
|
continue
|
||||||
|
account_objs.append(account_model(**data))
|
||||||
|
account_model.objects.bulk_create(account_objs)
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
info = result.get('debug', {}).get('res', {}).get('info', {})
|
info = result.get('debug', {}).get('res', {}).get('info', {})
|
||||||
asset = self.host_asset_mapper.get(host)
|
asset = self.host_asset_mapper.get(host)
|
||||||
org_id = asset.org_id
|
|
||||||
if asset and info:
|
if asset and info:
|
||||||
result = self.filter_success_result(host, info)
|
result = self.filter_success_result(host, info)
|
||||||
with tmp_to_org(org_id):
|
self.bulk_create_accounts(asset, result)
|
||||||
GatheredUser.objects.filter(asset=asset, present=True).update(present=False)
|
|
||||||
for username, data in result.items():
|
|
||||||
defaults = {'asset': asset, 'present': True, 'username': username}
|
|
||||||
if data.get('date'):
|
|
||||||
defaults['date_last_login'] = data['date']
|
|
||||||
if data.get('address'):
|
|
||||||
defaults['ip_last_login'] = data['address'][:32]
|
|
||||||
GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username)
|
|
||||||
else:
|
else:
|
||||||
logger.error("Not found info".format(host))
|
logger.error("Not found info".format(host))
|
||||||
|
@ -13,3 +13,14 @@ class SecretType(TextChoices):
|
|||||||
SSH_KEY = 'ssh_key', _('SSH key')
|
SSH_KEY = 'ssh_key', _('SSH key')
|
||||||
ACCESS_KEY = 'access_key', _('Access key')
|
ACCESS_KEY = 'access_key', _('Access key')
|
||||||
TOKEN = 'token', _('Token')
|
TOKEN = 'token', _('Token')
|
||||||
|
|
||||||
|
|
||||||
|
class AliasAccount(TextChoices):
|
||||||
|
ALL = '@ALL', _('All')
|
||||||
|
INPUT = '@INPUT', _('Manual input')
|
||||||
|
USER = '@USER', _('Dynamic user')
|
||||||
|
|
||||||
|
|
||||||
|
class Source(TextChoices):
|
||||||
|
LOCAL = 'local', _('Local')
|
||||||
|
COLLECTED = 'collected', _('Collected')
|
||||||
|
@ -126,17 +126,6 @@ class LabelFilterBackend(filters.BaseFilterBackend):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
|
|
||||||
def filter_node_related_all(self, queryset, node):
|
|
||||||
return queryset.filter(
|
|
||||||
Q(asset__nodes__key__istartswith=f'{node.key}:') |
|
|
||||||
Q(asset__nodes__key=node.key)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
def filter_node_related_direct(self, queryset, node):
|
|
||||||
return queryset.filter(asset__nodes__key=node.key).distinct()
|
|
||||||
|
|
||||||
|
|
||||||
class IpInFilterBackend(filters.BaseFilterBackend):
|
class IpInFilterBackend(filters.BaseFilterBackend):
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
ips = request.query_params.get('ips')
|
ips = request.query_params.get('ips')
|
||||||
|
21
apps/assets/migrations/0119_auto_20221227_1740.py
Normal file
21
apps/assets/migrations/0119_auto_20221227_1740.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2022-12-27 09:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0118_auto_20221227_1504'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='account',
|
||||||
|
name='source',
|
||||||
|
field=models.CharField(default='local', max_length=30, verbose_name='Source'),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='GatheredUser',
|
||||||
|
),
|
||||||
|
]
|
@ -7,7 +7,6 @@ from .gateway import *
|
|||||||
from .domain import *
|
from .domain import *
|
||||||
from .node import *
|
from .node import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
from .gathered_user import *
|
|
||||||
from .favorite_asset import *
|
from .favorite_asset import *
|
||||||
from .account import *
|
from .account import *
|
||||||
from .backup import *
|
from .backup import *
|
||||||
|
@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from simple_history.models import HistoricalRecords
|
from simple_history.models import HistoricalRecords
|
||||||
|
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
|
from ..const import AliasAccount, Source
|
||||||
from .base import AbsConnectivity, BaseAccount
|
from .base import AbsConnectivity, BaseAccount
|
||||||
|
|
||||||
__all__ = ['Account', 'AccountTemplate']
|
__all__ = ['Account', 'AccountTemplate']
|
||||||
@ -40,11 +41,6 @@ class AccountHistoricalRecords(HistoricalRecords):
|
|||||||
|
|
||||||
|
|
||||||
class Account(AbsConnectivity, BaseAccount):
|
class Account(AbsConnectivity, BaseAccount):
|
||||||
class AliasAccount(models.TextChoices):
|
|
||||||
ALL = '@ALL', _('All')
|
|
||||||
INPUT = '@INPUT', _('Manual input')
|
|
||||||
USER = '@USER', _('Dynamic user')
|
|
||||||
|
|
||||||
asset = models.ForeignKey(
|
asset = models.ForeignKey(
|
||||||
'assets.Asset', related_name='accounts',
|
'assets.Asset', related_name='accounts',
|
||||||
on_delete=models.CASCADE, verbose_name=_('Asset')
|
on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||||
@ -55,6 +51,7 @@ class Account(AbsConnectivity, BaseAccount):
|
|||||||
)
|
)
|
||||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
||||||
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
|
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
|
||||||
|
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Account')
|
verbose_name = _('Account')
|
||||||
@ -89,12 +86,12 @@ class Account(AbsConnectivity, BaseAccount):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_manual_account(cls):
|
def get_manual_account(cls):
|
||||||
""" @INPUT 手动登录的账号(any) """
|
""" @INPUT 手动登录的账号(any) """
|
||||||
return cls(name=cls.AliasAccount.INPUT.label, username=cls.AliasAccount.INPUT.value, secret=None)
|
return cls(name=AliasAccount.INPUT.label, username=AliasAccount.INPUT.value, secret=None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_account(cls, username):
|
def get_user_account(cls, username):
|
||||||
""" @USER 动态用户的账号(self) """
|
""" @USER 动态用户的账号(self) """
|
||||||
return cls(name=cls.AliasAccount.USER.label, username=cls.AliasAccount.USER.value)
|
return cls(name=AliasAccount.USER.label, username=AliasAccount.USER.value)
|
||||||
|
|
||||||
def get_su_from_accounts(self):
|
def get_su_from_accounts(self):
|
||||||
""" 排除自己和以自己为 su-from 的账号 """
|
""" 排除自己和以自己为 su-from 的账号 """
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orgs.mixins.models import JMSOrgBaseModel
|
|
||||||
|
|
||||||
__all__ = ['GatheredUser']
|
|
||||||
|
|
||||||
|
|
||||||
class GatheredUser(JMSOrgBaseModel):
|
|
||||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
|
|
||||||
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
|
|
||||||
present = models.BooleanField(default=True, verbose_name=_("Present"))
|
|
||||||
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date last login"))
|
|
||||||
ip_last_login = models.CharField(max_length=39, default='', verbose_name=_("IP last login"))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.asset.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ip(self):
|
|
||||||
return self.asset.address
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('GatherUser')
|
|
||||||
ordering = ['asset']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{}: {}'.format(self.asset.name, self.username)
|
|
@ -6,7 +6,6 @@ from .label import *
|
|||||||
from .node import *
|
from .node import *
|
||||||
from .gateway import *
|
from .gateway import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
from .gathered_user import *
|
|
||||||
from .favorite_asset import *
|
from .favorite_asset import *
|
||||||
from .account import *
|
from .account import *
|
||||||
from .platform import *
|
from .platform import *
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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 SecretType
|
from assets.const import SecretType, Source
|
||||||
from assets.models import Account, AccountTemplate, Asset
|
from assets.models import Account, AccountTemplate, Asset
|
||||||
from assets.tasks import push_accounts_to_assets
|
from assets.tasks import push_accounts_to_assets
|
||||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
@ -74,6 +74,7 @@ class AccountAssetSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
|
class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
|
||||||
asset = AccountAssetSerializer(label=_('Asset'))
|
asset = AccountAssetSerializer(label=_('Asset'))
|
||||||
|
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
|
||||||
su_from = ObjectRelatedField(
|
su_from = ObjectRelatedField(
|
||||||
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
|
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
|
||||||
label=_('Su from'), attrs=('id', 'name', 'username')
|
label=_('Su from'), attrs=('id', 'name', 'username')
|
||||||
@ -83,7 +84,7 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
|
|||||||
model = Account
|
model = Account
|
||||||
fields = BaseAccountSerializer.Meta.fields \
|
fields = BaseAccountSerializer.Meta.fields \
|
||||||
+ ['su_from', 'version', 'asset'] \
|
+ ['su_from', 'version', 'asset'] \
|
||||||
+ ['template', 'push_now']
|
+ ['template', 'push_now', 'source']
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
**BaseAccountSerializer.Meta.extra_kwargs,
|
**BaseAccountSerializer.Meta.extra_kwargs,
|
||||||
'name': {'required': False, 'allow_null': True},
|
'name': {'required': False, 'allow_null': True},
|
||||||
|
@ -67,6 +67,7 @@ class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSeriali
|
|||||||
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
|
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
|
||||||
labels = AssetLabelSerializer(many=True, required=False, label=_('Labels'))
|
labels = AssetLabelSerializer(many=True, required=False, label=_('Labels'))
|
||||||
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))
|
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))
|
||||||
|
accounts = AssetAccountSerializer(many=True, required=False, label=_('Account'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
@ -85,6 +86,7 @@ class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSeriali
|
|||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'label': _("Name")},
|
'name': {'label': _("Name")},
|
||||||
'address': {'label': _('Address')},
|
'address': {'label': _('Address')},
|
||||||
|
'nodes_display': {'label': _('Node path')},
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
|
||||||
from common.drf.fields import ObjectRelatedField
|
|
||||||
from ..models import GatheredUser, Asset
|
|
||||||
|
|
||||||
|
|
||||||
class GatheredUserSerializer(OrgResourceModelSerializerMixin):
|
|
||||||
asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = GatheredUser
|
|
||||||
fields_mini = ['id']
|
|
||||||
fields_small = fields_mini + [
|
|
||||||
'username', 'ip_last_login', 'present', 'name',
|
|
||||||
'date_last_login', 'date_created', 'date_updated'
|
|
||||||
]
|
|
||||||
fields_fk = ['asset', 'ip']
|
|
||||||
fields = fields_small + fields_fk
|
|
||||||
read_only_fields = fields
|
|
||||||
extra_kwargs = {
|
|
||||||
'name': {'label': _("Hostname")},
|
|
||||||
'ip': {'label': 'IP'},
|
|
||||||
}
|
|
@ -23,7 +23,6 @@ router.register(r'labels', api.LabelViewSet, 'label')
|
|||||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||||
router.register(r'domains', api.DomainViewSet, 'domain')
|
router.register(r'domains', api.DomainViewSet, 'domain')
|
||||||
router.register(r'gateways', api.GatewayViewSet, 'gateway')
|
router.register(r'gateways', api.GatewayViewSet, 'gateway')
|
||||||
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
|
|
||||||
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
||||||
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
|
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
|
||||||
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
|
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
|
||||||
@ -51,7 +50,6 @@ urlpatterns = [
|
|||||||
name='account-secret-history'),
|
name='account-secret-history'),
|
||||||
|
|
||||||
path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'),
|
path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'),
|
||||||
# path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
|
||||||
path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'),
|
path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'),
|
||||||
path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'),
|
path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'),
|
||||||
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||||
|
@ -17,12 +17,10 @@ MODELS_NEED_RECORD = (
|
|||||||
"LoginAssetACL",
|
"LoginAssetACL",
|
||||||
"LoginConfirmSetting",
|
"LoginConfirmSetting",
|
||||||
# assets
|
# assets
|
||||||
'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule',
|
'Asset', 'Node', 'Domain', 'Gateway', 'CommandFilterRule',
|
||||||
'CommandFilter', 'Platform', 'Label',
|
'CommandFilter', 'Platform', 'Label',
|
||||||
# applications
|
|
||||||
'Application',
|
|
||||||
# account
|
# account
|
||||||
'AuthBook',
|
'Account',
|
||||||
# orgs
|
# orgs
|
||||||
"Organization",
|
"Organization",
|
||||||
# settings
|
# settings
|
||||||
@ -36,8 +34,7 @@ MODELS_NEED_RECORD = (
|
|||||||
# rbac
|
# rbac
|
||||||
'Role', 'SystemRole', 'OrgRole', 'RoleBinding', 'OrgRoleBinding', 'SystemRoleBinding',
|
'Role', 'SystemRole', 'OrgRole', 'RoleBinding', 'OrgRoleBinding', 'SystemRoleBinding',
|
||||||
# xpack
|
# xpack
|
||||||
'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan',
|
'License', 'Account', 'SyncInstanceTask', 'Interface',
|
||||||
'GatherUserTask', 'Interface',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a533ee3b36cdd61fe239be159cc80362e5cd358f134857987029bde8e4c1231e
|
oid sha256:93524bfafbdfcb59e20920619c934b1df7b040573e98d1f1f481c73126110630
|
||||||
size 119530
|
size 119578
|
||||||
|
@ -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: 2022-12-26 20:28+0800\n"
|
"POT-Creation-Date: 2022-12-27 12:11+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"
|
||||||
@ -1308,12 +1308,16 @@ msgstr "プロトコル"
|
|||||||
msgid "Address"
|
msgid "Address"
|
||||||
msgstr "アドレス"
|
msgstr "アドレス"
|
||||||
|
|
||||||
#: assets/serializers/asset/common.py:156
|
#: assets/serializers/asset/common.py:89
|
||||||
|
msgid "Node path"
|
||||||
|
msgstr "ノードパスです"
|
||||||
|
|
||||||
|
#: assets/serializers/asset/common.py:157
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Platform not exist"
|
msgid "Platform not exist"
|
||||||
msgstr "アプリが存在しません"
|
msgstr "アプリが存在しません"
|
||||||
|
|
||||||
#: assets/serializers/asset/common.py:172
|
#: assets/serializers/asset/common.py:173
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Protocol is required: {}"
|
msgid "Protocol is required: {}"
|
||||||
msgstr "プロトコル重複: {}"
|
msgstr "プロトコル重複: {}"
|
||||||
@ -7443,9 +7447,6 @@ msgstr "コミュニティ版"
|
|||||||
#~ msgid "Apps amount"
|
#~ msgid "Apps amount"
|
||||||
#~ msgstr "アプリの量"
|
#~ msgstr "アプリの量"
|
||||||
|
|
||||||
#~ msgid "Nodes amount"
|
|
||||||
#~ msgstr "ノード量"
|
|
||||||
|
|
||||||
#~ msgid "Login mode display"
|
#~ msgid "Login mode display"
|
||||||
#~ msgstr "ログインモード表示"
|
#~ msgstr "ログインモード表示"
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7496f27202b5dcd9d677a70f9efc86c30b74fc306d6b5fa7202238c15c845971
|
oid sha256:8ea4225734c8f9df88bb72379ccd48b50d833766fbde3ce64cacf5eb6f87082b
|
||||||
size 105814
|
size 105853
|
||||||
|
@ -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: 2022-12-26 20:28+0800\n"
|
"POT-Creation-Date: 2022-12-27 12:11+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"
|
||||||
@ -1246,11 +1246,15 @@ msgstr "协议组"
|
|||||||
msgid "Address"
|
msgid "Address"
|
||||||
msgstr "地址"
|
msgstr "地址"
|
||||||
|
|
||||||
#: assets/serializers/asset/common.py:156
|
#: assets/serializers/asset/common.py:89
|
||||||
|
msgid "Node path"
|
||||||
|
msgstr "节点路径"
|
||||||
|
|
||||||
|
#: assets/serializers/asset/common.py:157
|
||||||
msgid "Platform not exist"
|
msgid "Platform not exist"
|
||||||
msgstr "平台不存在"
|
msgstr "平台不存在"
|
||||||
|
|
||||||
#: assets/serializers/asset/common.py:172
|
#: assets/serializers/asset/common.py:173
|
||||||
msgid "Protocol is required: {}"
|
msgid "Protocol is required: {}"
|
||||||
msgstr "协议是必填的: {}"
|
msgstr "协议是必填的: {}"
|
||||||
|
|
||||||
@ -7217,9 +7221,6 @@ msgstr "社区版"
|
|||||||
#~ msgid "Apps amount"
|
#~ msgid "Apps amount"
|
||||||
#~ msgstr "应用数量"
|
#~ msgstr "应用数量"
|
||||||
|
|
||||||
#~ msgid "Nodes amount"
|
|
||||||
#~ msgstr "节点数量"
|
|
||||||
|
|
||||||
#~ msgid "Login mode display"
|
#~ msgid "Login mode display"
|
||||||
#~ msgstr "认证方式名称"
|
#~ msgstr "认证方式名称"
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ class DefaultCallback:
|
|||||||
'failed': 'failed',
|
'failed': 'failed',
|
||||||
'running': 'running',
|
'running': 'running',
|
||||||
'pending': 'pending',
|
'pending': 'pending',
|
||||||
|
'timeout': 'timeout',
|
||||||
'unknown': 'unknown'
|
'unknown': 'unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ class AdHocRunner:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/', extra_vars={},
|
def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/', extra_vars={},
|
||||||
dry_run=False):
|
dry_run=False, timeout=-1):
|
||||||
self.id = uuid.uuid4()
|
self.id = uuid.uuid4()
|
||||||
self.inventory = inventory
|
self.inventory = inventory
|
||||||
self.pattern = pattern
|
self.pattern = pattern
|
||||||
@ -25,6 +25,7 @@ class AdHocRunner:
|
|||||||
self.runner = None
|
self.runner = None
|
||||||
self.extra_vars = extra_vars
|
self.extra_vars = extra_vars
|
||||||
self.dry_run = dry_run
|
self.dry_run = dry_run
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
def check_module(self):
|
def check_module(self):
|
||||||
if self.module not in self.cmd_modules_choices:
|
if self.module not in self.cmd_modules_choices:
|
||||||
@ -41,6 +42,7 @@ class AdHocRunner:
|
|||||||
os.mkdir(self.project_dir, 0o755)
|
os.mkdir(self.project_dir, 0o755)
|
||||||
|
|
||||||
ansible_runner.run(
|
ansible_runner.run(
|
||||||
|
timeout=self.timeout if self.timeout > 0 else None,
|
||||||
extravars=self.extra_vars,
|
extravars=self.extra_vars,
|
||||||
host_pattern=self.pattern,
|
host_pattern=self.pattern,
|
||||||
private_data_dir=self.project_dir,
|
private_data_dir=self.project_dir,
|
||||||
|
@ -110,6 +110,7 @@ class JobExecutionTaskDetail(APIView):
|
|||||||
with tmp_to_org(org):
|
with tmp_to_org(org):
|
||||||
execution = get_object_or_404(JobExecution, task_id=task_id)
|
execution = get_object_or_404(JobExecution, task_id=task_id)
|
||||||
return Response(data={
|
return Response(data={
|
||||||
|
'status': execution.status,
|
||||||
'is_finished': execution.is_finished,
|
'is_finished': execution.is_finished,
|
||||||
'is_success': execution.is_success,
|
'is_success': execution.is_success,
|
||||||
'time_cost': execution.time_cost,
|
'time_cost': execution.time_cost,
|
||||||
|
@ -43,9 +43,11 @@ class RunasPolicies(models.TextChoices):
|
|||||||
class Modules(models.TextChoices):
|
class Modules(models.TextChoices):
|
||||||
shell = 'shell', _('Shell')
|
shell = 'shell', _('Shell')
|
||||||
winshell = 'win_shell', _('Powershell')
|
winshell = 'win_shell', _('Powershell')
|
||||||
|
python = 'python', _('Python')
|
||||||
|
|
||||||
|
|
||||||
class JobStatus(models.TextChoices):
|
class JobStatus(models.TextChoices):
|
||||||
running = 'running', _('Running')
|
running = 'running', _('Running')
|
||||||
success = 'success', _('Success')
|
success = 'success', _('Success')
|
||||||
|
timeout = 'timeout', _('Timeout')
|
||||||
failed = 'failed', _('Failed')
|
failed = 'failed', _('Failed')
|
||||||
|
33
apps/ops/migrations/0035_auto_20221227_1520.py
Normal file
33
apps/ops/migrations/0035_auto_20221227_1520.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 3.2.14 on 2022-12-27 07:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ops', '0034_alter_celerytask_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='historicaljob',
|
||||||
|
name='module',
|
||||||
|
field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell'), ('python', 'Python')], default='shell', max_length=128, null=True, verbose_name='Module'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='historicaljob',
|
||||||
|
name='timeout',
|
||||||
|
field=models.IntegerField(default=-1, verbose_name='Timeout (Seconds)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='job',
|
||||||
|
name='module',
|
||||||
|
field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell'), ('python', 'Python')], default='shell', max_length=128, null=True, verbose_name='Module'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='job',
|
||||||
|
name='timeout',
|
||||||
|
field=models.IntegerField(default=-1, verbose_name='Timeout (Seconds)'),
|
||||||
|
),
|
||||||
|
]
|
@ -27,7 +27,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
|
|||||||
module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell,
|
module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell,
|
||||||
verbose_name=_('Module'), null=True)
|
verbose_name=_('Module'), null=True)
|
||||||
chdir = models.CharField(default="", max_length=1024, verbose_name=_('Chdir'), null=True, blank=True)
|
chdir = models.CharField(default="", max_length=1024, verbose_name=_('Chdir'), null=True, blank=True)
|
||||||
timeout = models.IntegerField(default=60, verbose_name=_('Timeout (Seconds)'))
|
timeout = models.IntegerField(default=-1, verbose_name=_('Timeout (Seconds)'))
|
||||||
playbook = models.ForeignKey('ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.SET_NULL)
|
playbook = models.ForeignKey('ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.SET_NULL)
|
||||||
type = models.CharField(max_length=128, choices=Types.choices, default=Types.adhoc, verbose_name=_("Type"))
|
type = models.CharField(max_length=128, choices=Types.choices, default=Types.adhoc, verbose_name=_("Type"))
|
||||||
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
||||||
@ -165,12 +165,11 @@ class JobExecution(JMSOrgBaseModel):
|
|||||||
if self.current_job.type != 'adhoc':
|
if self.current_job.type != 'adhoc':
|
||||||
return
|
return
|
||||||
result = self.current_job.args
|
result = self.current_job.args
|
||||||
|
if self.current_job.chdir:
|
||||||
result += " chdir={}".format(self.current_job.chdir)
|
result += " chdir={}".format(self.current_job.chdir)
|
||||||
|
|
||||||
if self.current_job.module in ['python']:
|
if self.current_job.module in ['python']:
|
||||||
result += " executable={}".format(self.current_job.module)
|
result += " executable={}".format(self.current_job.module)
|
||||||
print(result)
|
return result
|
||||||
return self.job.args
|
|
||||||
|
|
||||||
def get_runner(self):
|
def get_runner(self):
|
||||||
inv = self.current_job.inventory
|
inv = self.current_job.inventory
|
||||||
@ -198,6 +197,7 @@ class JobExecution(JMSOrgBaseModel):
|
|||||||
runner = AdHocRunner(
|
runner = AdHocRunner(
|
||||||
self.inventory_path,
|
self.inventory_path,
|
||||||
module,
|
module,
|
||||||
|
timeout=self.current_job.timeout,
|
||||||
module_args=args,
|
module_args=args,
|
||||||
pattern="all",
|
pattern="all",
|
||||||
project_dir=self.private_dir,
|
project_dir=self.private_dir,
|
||||||
@ -238,7 +238,7 @@ class JobExecution(JMSOrgBaseModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_finished(self):
|
def is_finished(self):
|
||||||
return self.status in [JobStatus.success, JobStatus.failed]
|
return self.status in [JobStatus.success, JobStatus.failed, JobStatus.timeout]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_success(self):
|
def is_success(self):
|
||||||
|
@ -15,7 +15,7 @@ from .serializers import (
|
|||||||
from users.models import User, UserGroup
|
from users.models import User, UserGroup
|
||||||
from assets.models import (
|
from assets.models import (
|
||||||
Asset, Domain, Label, Node,
|
Asset, Domain, Label, Node,
|
||||||
CommandFilter, CommandFilterRule, GatheredUser
|
CommandFilter, CommandFilterRule
|
||||||
)
|
)
|
||||||
from perms.models import AssetPermission
|
from perms.models import AssetPermission
|
||||||
from orgs.utils import current_org, tmp_to_root_org
|
from orgs.utils import current_org, tmp_to_root_org
|
||||||
@ -28,8 +28,7 @@ logger = get_logger(__file__)
|
|||||||
# 部分 org 相关的 model,需要清空这些数据之后才能删除该组织
|
# 部分 org 相关的 model,需要清空这些数据之后才能删除该组织
|
||||||
org_related_models = [
|
org_related_models = [
|
||||||
User, UserGroup, Asset, Label, Domain, Node, Label,
|
User, UserGroup, Asset, Label, Domain, Node, Label,
|
||||||
CommandFilter, CommandFilterRule, GatheredUser,
|
CommandFilter, CommandFilterRule, AssetPermission,
|
||||||
AssetPermission,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ from rest_framework.exceptions import PermissionDenied, NotFound
|
|||||||
|
|
||||||
from assets.utils import KubernetesTree
|
from assets.utils import KubernetesTree
|
||||||
from assets.models import Asset, Account
|
from assets.models import Asset, Account
|
||||||
|
from assets.const import AliasAccount
|
||||||
from assets.api import SerializeToTreeNodeMixin
|
from assets.api import SerializeToTreeNodeMixin
|
||||||
from authentication.models import ConnectionToken
|
from authentication.models import ConnectionToken
|
||||||
from common.utils import get_object_or_none, lazyproperty
|
from common.utils import get_object_or_none, lazyproperty
|
||||||
@ -157,7 +158,7 @@ class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView):
|
|||||||
raise NotFound('Account is not found')
|
raise NotFound('Account is not found')
|
||||||
account = accounts[0]
|
account = accounts[0]
|
||||||
if account.username in [
|
if account.username in [
|
||||||
Account.AliasAccount.INPUT, Account.AliasAccount.USER
|
AliasAccount.INPUT, AliasAccount.USER
|
||||||
]:
|
]:
|
||||||
return token.input_secret
|
return token.input_secret
|
||||||
else:
|
else:
|
||||||
|
@ -13,6 +13,7 @@ from common.utils import date_expired_default
|
|||||||
from common.utils.timezone import local_now
|
from common.utils.timezone import local_now
|
||||||
|
|
||||||
from perms.const import ActionChoices
|
from perms.const import ActionChoices
|
||||||
|
from assets.const import AliasAccount
|
||||||
|
|
||||||
__all__ = ['AssetPermission', 'ActionChoices']
|
__all__ = ['AssetPermission', 'ActionChoices']
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ class AssetPermissionQuerySet(models.QuerySet):
|
|||||||
|
|
||||||
def filter_by_accounts(self, accounts):
|
def filter_by_accounts(self, accounts):
|
||||||
q = Q(accounts__contains=list(accounts)) | \
|
q = Q(accounts__contains=list(accounts)) | \
|
||||||
Q(accounts__contains=Account.AliasAccount.ALL.value)
|
Q(accounts__contains=AliasAccount.ALL.value)
|
||||||
return self.filter(q)
|
return self.filter(q)
|
||||||
|
|
||||||
|
|
||||||
@ -127,7 +128,7 @@ class AssetPermission(JMSOrgBaseModel):
|
|||||||
"""
|
"""
|
||||||
asset_ids = self.get_all_assets(flat=True)
|
asset_ids = self.get_all_assets(flat=True)
|
||||||
q = Q(asset_id__in=asset_ids)
|
q = Q(asset_id__in=asset_ids)
|
||||||
if Account.AliasAccount.ALL not in self.accounts:
|
if AliasAccount.ALL not in self.accounts:
|
||||||
q &= Q(username__in=self.accounts)
|
q &= Q(username__in=self.accounts)
|
||||||
accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username')
|
accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username')
|
||||||
if not flat:
|
if not flat:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from assets.models import Account
|
from assets.models import Account
|
||||||
|
from assets.const import AliasAccount
|
||||||
from .permission import AssetPermissionUtil
|
from .permission import AssetPermissionUtil
|
||||||
|
|
||||||
__all__ = ['PermAccountUtil']
|
__all__ = ['PermAccountUtil']
|
||||||
@ -44,21 +45,21 @@ class PermAccountUtil(AssetPermissionUtil):
|
|||||||
cleaned_accounts_expired = defaultdict(list)
|
cleaned_accounts_expired = defaultdict(list)
|
||||||
|
|
||||||
# @ALL 账号先处理,后面的每个最多映射一个账号
|
# @ALL 账号先处理,后面的每个最多映射一个账号
|
||||||
all_action_bit = alias_action_bit_mapper.pop(Account.AliasAccount.ALL, None)
|
all_action_bit = alias_action_bit_mapper.pop(AliasAccount.ALL, None)
|
||||||
if all_action_bit:
|
if all_action_bit:
|
||||||
for account in asset_accounts:
|
for account in asset_accounts:
|
||||||
cleaned_accounts_action_bit[account] |= all_action_bit
|
cleaned_accounts_action_bit[account] |= all_action_bit
|
||||||
cleaned_accounts_expired[account].extend(
|
cleaned_accounts_expired[account].extend(
|
||||||
alias_expired_mapper[Account.AliasAccount.ALL]
|
alias_expired_mapper[AliasAccount.ALL]
|
||||||
)
|
)
|
||||||
|
|
||||||
for alias, action_bit in alias_action_bit_mapper.items():
|
for alias, action_bit in alias_action_bit_mapper.items():
|
||||||
if alias == Account.AliasAccount.USER:
|
if alias == AliasAccount.USER:
|
||||||
if user.username in username_account_mapper:
|
if user.username in username_account_mapper:
|
||||||
account = username_account_mapper[user.username]
|
account = username_account_mapper[user.username]
|
||||||
else:
|
else:
|
||||||
account = Account.get_user_account(user.username)
|
account = Account.get_user_account(user.username)
|
||||||
elif alias == Account.AliasAccount.INPUT:
|
elif alias == AliasAccount.INPUT:
|
||||||
account = Account.get_manual_account()
|
account = Account.get_manual_account()
|
||||||
elif alias in username_account_mapper:
|
elif alias in username_account_mapper:
|
||||||
account = username_account_mapper[alias]
|
account = username_account_mapper[alias]
|
||||||
|
Loading…
Reference in New Issue
Block a user