diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 1d7dbca00..b0efb6af8 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -5,3 +5,4 @@ from .system_user import * from .node import * from .domain import * from .cmd_filter import * +from .asset_user import * diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py new file mode 100644 index 000000000..7000a95a5 --- /dev/null +++ b/apps/assets/api/asset_user.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# + + +from rest_framework.response import Response +from rest_framework import viewsets, status, generics +from rest_framework.pagination import LimitOffsetPagination + +from common.permissions import IsOrgAdminOrAppUser +from common.utils import get_object_or_none, get_logger + +from ..backends.multi import AssetUserManager +from ..models import Asset +from .. import serializers +from ..tasks import test_asset_users_connectivity_manual + + +__all__ = [ + 'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi', +] + + +logger = get_logger(__name__) + + +class AssetUserViewSet(viewsets.GenericViewSet): + pagination_class = LimitOffsetPagination + serializer_class = serializers.AssetUserSerializer + permission_classes = (IsOrgAdminOrAppUser, ) + http_method_names = ['get', 'post'] + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + + def get_queryset(self): + username = self.request.GET.get('username') + asset_id = self.request.GET.get('asset_id') + asset = get_object_or_none(Asset, pk=asset_id) + queryset = AssetUserManager.filter(username=username, asset=asset) + return queryset + + def filter_queryset(self, queryset): + queryset = sorted( + queryset, + key=lambda q: (q.asset.hostname, q.connectivity, q.username) + ) + return queryset + + +class AssetUserAuthInfoApi(generics.RetrieveAPIView): + serializer_class = serializers.AssetUserAuthInfoSerializer + permission_classes = (IsOrgAdminOrAppUser,) + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(instance) + status_code = status.HTTP_200_OK + if not instance: + status_code = status.HTTP_400_BAD_REQUEST + return Response(serializer.data, status=status_code) + + def get_object(self): + username = self.request.GET.get('username') + asset_id = self.request.GET.get('asset_id') + asset = get_object_or_none(Asset, pk=asset_id) + try: + instance = AssetUserManager.get(username, asset) + except Exception as e: + logger.error(e, exc_info=True) + return None + else: + return instance + + +class AssetUserTestConnectiveApi(generics.RetrieveAPIView): + """ + Test asset users connective + """ + + def get_asset_users(self): + username = self.request.GET.get('username') + asset_id = self.request.GET.get('asset_id') + asset = get_object_or_none(Asset, pk=asset_id) + asset_users = AssetUserManager.filter(username=username, asset=asset) + return asset_users + + def retrieve(self, request, *args, **kwargs): + asset_users = self.get_asset_users() + task = test_asset_users_connectivity_manual.delay(asset_users) + return Response({"task": task.id}) + + + diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 5c131f400..9805872e7 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -30,7 +30,7 @@ from ..tasks import push_system_user_to_assets_manual, \ logger = get_logger(__file__) __all__ = [ - 'SystemUserViewSet', 'SystemUserAuthInfoApi', + 'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi', 'SystemUserPushApi', 'SystemUserTestConnectiveApi', 'SystemUserAssetsListView', 'SystemUserPushToAssetApi', 'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi', @@ -68,6 +68,22 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): return Response(status=204) +class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): + """ + Get system user with asset auth info + """ + queryset = SystemUser.objects.all() + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = serializers.SystemUserAuthSerializer + + def get_object(self): + instance = super().get_object() + aid = self.kwargs.get('aid') + asset = get_object_or_404(Asset, pk=aid) + instance.load_specific_asset_auth(asset) + return instance + + class SystemUserPushApi(generics.RetrieveAPIView): """ Push system user to cluster assets api diff --git a/apps/assets/backends/__init__.py b/apps/assets/backends/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/backends/base.py b/apps/assets/backends/base.py new file mode 100644 index 000000000..c93ea6a31 --- /dev/null +++ b/apps/assets/backends/base.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# + +from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist +from abc import abstractmethod + + +class NotSupportError(Exception): + pass + + +class BaseBackend: + ObjectDoesNotExist = ObjectDoesNotExist + MultipleObjectsReturned = MultipleObjectsReturned + NotSupportError = NotSupportError + MSG_NOT_EXIST = '{} Object matching query does not exist' + MSG_MULTIPLE = '{} get() returned more than one object ' \ + '-- it returned {}!' + + @classmethod + def get(cls, username, asset): + instances = cls.filter(username, asset) + if len(instances) == 1: + return instances[0] + elif len(instances) == 0: + cls.raise_does_not_exist(cls.__name__) + else: + cls.raise_multiple_return(cls.__name__, len(instances)) + + @classmethod + @abstractmethod + def filter(cls, username=None, asset=None, latest=True): + """ + :param username: 用户名 + :param asset: 对象 + :param latest: 是否是最新记录 + :return: 元素为的可迭代对象( or ) + """ + pass + + @classmethod + @abstractmethod + def create(cls, **kwargs): + """ + :param kwargs: + { + name, username, asset, comment, password, public_key, private_key, + (org_id) + } + :return: 对象 + """ + pass + + @classmethod + def raise_does_not_exist(cls, name): + raise cls.ObjectDoesNotExist(cls.MSG_NOT_EXIST.format(name)) + + @classmethod + def raise_multiple_return(cls, name, length): + raise cls.MultipleObjectsReturned(cls.MSG_MULTIPLE.format(name, length)) diff --git a/apps/assets/backends/external/__init__.py b/apps/assets/backends/external/__init__.py new file mode 100644 index 000000000..ec51c5a2b --- /dev/null +++ b/apps/assets/backends/external/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# diff --git a/apps/assets/backends/external/db.py b/apps/assets/backends/external/db.py new file mode 100644 index 000000000..f3f6c16d6 --- /dev/null +++ b/apps/assets/backends/external/db.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# + +from assets.models import AuthBook + +from ..base import BaseBackend + + +class AuthBookBackend(BaseBackend): + + @classmethod + def filter(cls, username=None, asset=None, latest=True): + queryset = AuthBook.objects.all() + if username: + queryset = queryset.filter(username=username) + if asset: + queryset = queryset.filter(asset=asset) + if latest: + queryset = queryset.latest_version() + return queryset + + @classmethod + def create(cls, **kwargs): + auth_info = { + 'password': kwargs.pop('password', ''), + 'public_key': kwargs.pop('public_key', ''), + 'private_key': kwargs.pop('private_key', '') + } + obj = AuthBook.objects.create(**kwargs) + obj.set_auth(**auth_info) + return obj diff --git a/apps/assets/backends/external/utils.py b/apps/assets/backends/external/utils.py new file mode 100644 index 000000000..62be16c1d --- /dev/null +++ b/apps/assets/backends/external/utils.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# + +# from django.conf import settings + +from .db import AuthBookBackend +# from .vault import VaultBackend + + +def get_backend(): + default_backend = AuthBookBackend + + # if settings.BACKEND_ASSET_USER_AUTH_VAULT: + # return VaultBackend + + return default_backend diff --git a/apps/assets/backends/external/vault.py b/apps/assets/backends/external/vault.py new file mode 100644 index 000000000..da7583458 --- /dev/null +++ b/apps/assets/backends/external/vault.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# + +from ..base import BaseBackend + + +class VaultBackend(BaseBackend): + + @classmethod + def get(cls, username, asset): + pass + + @classmethod + def filter(cls, username=None, asset=None, latest=True): + pass + + @classmethod + def create(cls, **kwargs): + pass diff --git a/apps/assets/backends/internal/__init__.py b/apps/assets/backends/internal/__init__.py new file mode 100644 index 000000000..f19a64d9a --- /dev/null +++ b/apps/assets/backends/internal/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# + + diff --git a/apps/assets/backends/internal/admin_user.py b/apps/assets/backends/internal/admin_user.py new file mode 100644 index 000000000..e67b41fc9 --- /dev/null +++ b/apps/assets/backends/internal/admin_user.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# + +from assets.models import Asset + +from ..base import BaseBackend +from .utils import construct_authbook_object + + +class AdminUserBackend(BaseBackend): + + @classmethod + def filter(cls, username=None, asset=None, **kwargs): + instances = cls.construct_authbook_objects(username, asset) + return instances + + @classmethod + def _get_assets(cls, asset): + if not asset: + assets = Asset.objects.all().prefetch_related('admin_user') + else: + assets = [asset] + return assets + + @classmethod + def construct_authbook_objects(cls, username, asset): + instances = [] + assets = cls._get_assets(asset) + for asset in assets: + if username and asset.admin_user.username != username: + continue + instance = construct_authbook_object(asset.admin_user, asset) + instances.append(instance) + return instances + + @classmethod + def create(cls, **kwargs): + raise cls.NotSupportError("Not support create") diff --git a/apps/assets/backends/internal/asset_user.py b/apps/assets/backends/internal/asset_user.py new file mode 100644 index 000000000..8502cf5d9 --- /dev/null +++ b/apps/assets/backends/internal/asset_user.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# + +from ..base import BaseBackend +from .admin_user import AdminUserBackend +from .system_user import SystemUserBackend + + +class AssetUserBackend(BaseBackend): + @classmethod + def filter(cls, username=None, asset=None, **kwargs): + admin_user_instances = AdminUserBackend.filter(username, asset) + system_user_instances = SystemUserBackend.filter(username, asset) + instances = cls._merge_instances(admin_user_instances, system_user_instances) + return instances + + @classmethod + def _merge_instances(cls, admin_user_instances, system_user_instances): + admin_user_instances_keyword_list = [ + {'username': instance.username, 'asset': instance.asset} + for instance in admin_user_instances + ] + instances = [ + instance for instance in system_user_instances + if instance.keyword not in admin_user_instances_keyword_list + ] + admin_user_instances.extend(instances) + return admin_user_instances + + @classmethod + def create(cls, **kwargs): + raise cls.NotSupportError("Not support create") diff --git a/apps/assets/backends/internal/system_user.py b/apps/assets/backends/internal/system_user.py new file mode 100644 index 000000000..52b22215c --- /dev/null +++ b/apps/assets/backends/internal/system_user.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# + +import itertools + +from assets.models import Asset + +from ..base import BaseBackend +from .utils import construct_authbook_object + + +class SystemUserBackend(BaseBackend): + + @classmethod + def filter(cls, username=None, asset=None, **kwargs): + instances = cls.construct_authbook_objects(username, asset) + return instances + + @classmethod + def _distinct_system_users_by_username(cls, system_users): + system_users = sorted( + system_users, + key=lambda su: (su.username, su.priority, su.date_updated), + reverse=True, + ) + results = itertools.groupby(system_users, key=lambda su: su.username) + system_users = [next(result[1]) for result in results] + return system_users + + @classmethod + def _filter_system_users_by_username(cls, system_users, username): + _system_users = cls._distinct_system_users_by_username(system_users) + if username: + _system_users = [su for su in _system_users if username == su.username] + return _system_users + + @classmethod + def _construct_authbook_objects(cls, system_users, asset): + instances = [] + for system_user in system_users: + instance = construct_authbook_object(system_user, asset) + instances.append(instance) + return instances + + @classmethod + def _get_assets_with_system_users(cls, asset=None): + """ + { 'asset': set(, , ...) } + """ + if not asset: + _assets = Asset.objects.all().prefetch_related('systemuser_set') + else: + _assets = [asset] + + assets = {asset: set(asset.systemuser_set.all()) for asset in _assets} + return assets + + @classmethod + def construct_authbook_objects(cls, username, asset): + """ + :return: [, , ...] + """ + instances = [] + assets = cls._get_assets_with_system_users(asset) + for _asset, _system_users in assets.items(): + _system_users = cls._filter_system_users_by_username(_system_users, username) + _instances = cls._construct_authbook_objects(_system_users, _asset) + instances.extend(_instances) + return instances + + @classmethod + def create(cls, **kwargs): + raise Exception("Not support create") + + diff --git a/apps/assets/backends/internal/utils.py b/apps/assets/backends/internal/utils.py new file mode 100644 index 000000000..65b4fa821 --- /dev/null +++ b/apps/assets/backends/internal/utils.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# + +from assets.models import AuthBook + + +def construct_authbook_object(asset_user, asset): + """ + 作用: 将对象构造成为对象并返回 + + :param asset_user: 对象 + :param asset: 对象 + :return: 对象 + """ + fields = [ + 'id', 'name', 'username', 'comment', 'org_id', + '_password', '_private_key', '_public_key', + 'date_created', 'date_updated', 'created_by' + ] + + obj = AuthBook(asset=asset, version=0, is_latest=True) + for field in fields: + value = getattr(asset_user, field) + setattr(obj, field, value) + return obj + diff --git a/apps/assets/backends/multi.py b/apps/assets/backends/multi.py new file mode 100644 index 000000000..785176885 --- /dev/null +++ b/apps/assets/backends/multi.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# + +from .base import BaseBackend + +from .external.utils import get_backend +from .internal.asset_user import AssetUserBackend + + +class AssetUserManager(BaseBackend): + """ + 资产用户管理器 + """ + external_backend = get_backend() + internal_backend = AssetUserBackend + + @classmethod + def filter(cls, username=None, asset=None, **kwargs): + external_instance = list(cls.external_backend.filter(username, asset)) + internal_instance = list(cls.internal_backend.filter(username, asset)) + instances = cls._merge_instances(external_instance, internal_instance) + return instances + + @classmethod + def create(cls, **kwargs): + instance = cls.external_backend.create(**kwargs) + return instance + + @classmethod + def _merge_instances(cls, external_instances, internal_instances): + external_instances_keyword_list = [ + {'username': instance.username, 'asset': instance.asset} + for instance in external_instances + ] + instances = [ + instance for instance in internal_instances + if instance.keyword not in external_instances_keyword_list + ] + external_instances.extend(instances) + return external_instances diff --git a/apps/assets/const.py b/apps/assets/const.py index 7cee7aedd..901ade1ff 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -32,6 +32,18 @@ TEST_SYSTEM_USER_CONN_TASKS = [ } ] + +ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}_{}' +TEST_ASSET_USER_CONN_TASKS = [ + { + "name": "ping", + "action": { + "module": "ping", + } + } +] + + TASK_OPTIONS = { 'timeout': 10, 'forks': 10, diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index c60830fba..b87a18796 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -7,3 +7,4 @@ from .node import * from .asset import * from .cmd_filter import * from .utils import * +from .authbook import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 244c2b7e7..5ad9830f3 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -197,6 +197,7 @@ class Asset(OrgModelMixin): def get_auth_info(self): if self.admin_user: + self.admin_user.load_specific_asset_auth(self) return { 'username': self.admin_user.username, 'password': self.admin_user.password, @@ -232,6 +233,7 @@ class Asset(OrgModelMixin): """ data = self.to_json() if self.admin_user: + self.admin_user.load_specific_asset_auth(self) admin_user = self.admin_user data.update({ 'username': admin_user.username, diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py new file mode 100644 index 000000000..713c8b41c --- /dev/null +++ b/apps/assets/models/authbook.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# + +from django.db import models +from django.utils.translation import ugettext as _ +from django.core.cache import cache + +from orgs.mixins import OrgManager + +from .base import AssetUser +from ..const import ASSET_USER_CONN_CACHE_KEY + +__all__ = ['AuthBook'] + + +class AuthBookQuerySet(models.QuerySet): + + def latest_version(self): + return self.filter(is_latest=True) + + +class AuthBookManager(OrgManager): + pass + + +class AuthBook(AssetUser): + asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) + is_latest = models.BooleanField(default=False, verbose_name=_('Latest version')) + version = models.IntegerField(default=1, verbose_name=_('Version')) + + objects = AuthBookManager.from_queryset(AuthBookQuerySet)() + + class Meta: + verbose_name = _('AuthBook') + + def _set_latest(self): + self._remove_pre_obj_latest() + self.is_latest = True + self.save() + + def _get_pre_obj(self): + pre_obj = self.__class__.objects.filter( + username=self.username, asset=self.asset).latest_version().first() + return pre_obj + + def _remove_pre_obj_latest(self): + pre_obj = self._get_pre_obj() + if pre_obj: + pre_obj.is_latest = False + pre_obj.save() + + def _set_version(self): + pre_obj = self._get_pre_obj() + if pre_obj: + self.version = pre_obj.version + 1 + else: + self.version = 1 + self.save() + + def set_version_and_latest(self): + self._set_version() + self._set_latest() + + @property + def _conn_cache_key(self): + return ASSET_USER_CONN_CACHE_KEY.format(self.id, self.asset.id) + + @property + def connectivity(self): + value = cache.get(self._conn_cache_key, self.UNKNOWN) + return value + + @connectivity.setter + def connectivity(self, value): + _connectivity = self.UNKNOWN + + for host in value.get('dark', {}).keys(): + if host == self.asset.hostname: + _connectivity = self.UNREACHABLE + + for host in value.get('contacted', {}).keys(): + if host == self.asset.hostname: + _connectivity = self.REACHABLE + + cache.set(self._conn_cache_key, _connectivity, 3600) + + @property + def keyword(self): + return {'username': self.username, 'asset': self.asset} + + def __str__(self): + return '{}@{}'.format(self.username, self.asset) + diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 37e099e99..30d8ae0f5 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -9,13 +9,17 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen +from common.utils import ( + get_signer, ssh_key_string_to_obj, ssh_key_gen, get_logger +) from common.validators import alphanumeric from orgs.mixins import OrgModelMixin from .utils import private_key_validator signer = get_signer() +logger = get_logger(__file__) + class AssetUser(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) @@ -45,8 +49,8 @@ class AssetUser(OrgModelMixin): @password.setter def password(self, password_raw): - raise AttributeError("Using set_auth do that") - # self._password = signer.sign(password_raw) + # raise AttributeError("Using set_auth do that") + self._password = signer.sign(password_raw) @property def private_key(self): @@ -55,8 +59,8 @@ class AssetUser(OrgModelMixin): @private_key.setter def private_key(self, private_key_raw): - raise AttributeError("Using set_auth do that") - # self._private_key = signer.sign(private_key_raw) + # raise AttributeError("Using set_auth do that") + self._private_key = signer.sign(private_key_raw) @property def private_key_obj(self): @@ -88,6 +92,11 @@ class AssetUser(OrgModelMixin): else: return None + @public_key.setter + def public_key(self, public_key_raw): + # raise AttributeError("Using set_auth do that") + self._public_key = signer.sign(public_key_raw) + @property def public_key_obj(self): if self.public_key: @@ -115,6 +124,25 @@ class AssetUser(OrgModelMixin): def get_auth(self, asset=None): pass + def load_specific_asset_auth(self, asset): + from ..backends.multi import AssetUserManager + try: + other = AssetUserManager.get(username=self.username, asset=asset) + except Exception as e: + logger.error(e, exc_info=True) + else: + self._merge_auth(other) + + def _merge_auth(self, other): + if not other: + return + if other.password: + self.password = other.password + if other.public_key: + self.public_key = other.public_key + if other.private_key: + self.private_key = other.private_key + def clear_auth(self): self._password = '' self._private_key = '' diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index ad4439be3..f9866688d 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -8,3 +8,4 @@ from .system_user import * from .node import * from .domain import * from .cmd_filter import * +from .asset_user import * diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py new file mode 100644 index 000000000..f7345437e --- /dev/null +++ b/apps/assets/serializers/asset_user.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# + +from django.utils.translation import ugettext as _ +from rest_framework import serializers + +from ..models import AuthBook +from ..backends.multi import AssetUserManager + +__all__ = [ + 'AssetUserSerializer', 'AssetUserAuthInfoSerializer', +] + + +class AssetUserSerializer(serializers.ModelSerializer): + + password = serializers.CharField( + max_length=256, allow_blank=True, allow_null=True, write_only=True, + required=False, help_text=_('Password') + ) + public_key = serializers.CharField( + max_length=4096, allow_blank=True, allow_null=True, write_only=True, + required=False, help_text=_('Public key') + ) + private_key = serializers.CharField( + max_length=4096, allow_blank=True, allow_null=True, write_only=True, + required=False, help_text=_('Private key') + ) + + class Meta: + model = AuthBook + read_only_fields = ( + 'date_created', 'date_updated', 'created_by', + 'is_latest', 'version', 'connectivity', + ) + fields = '__all__' + extra_kwargs = { + 'username': {'required': True} + } + + def get_field_names(self, declared_fields, info): + fields = super().get_field_names(declared_fields, info) + fields = [f for f in fields if not f.startswith('_') and f != 'id'] + fields.extend(['connectivity']) + return fields + + def create(self, validated_data): + kwargs = { + 'name': validated_data.get('name'), + 'username': validated_data.get('username'), + 'asset': validated_data.get('asset'), + 'comment': validated_data.get('comment', ''), + 'org_id': validated_data.get('org_id', ''), + 'password': validated_data.get('password'), + 'public_key': validated_data.get('public_key'), + 'private_key': validated_data.get('private_key') + } + instance = AssetUserManager.create(**kwargs) + return instance + + +class AssetUserAuthInfoSerializer(serializers.ModelSerializer): + class Meta: + model = AuthBook + fields = ['password', 'private_key', 'public_key'] diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 7d3b67f6c..4afb97e75 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -5,9 +5,12 @@ from django.db.models.signals import post_save, m2m_changed, post_delete from django.dispatch import receiver from common.utils import get_logger -from .models import Asset, SystemUser, Node -from .tasks import update_assets_hardware_info_util, \ - test_asset_connectivity_util, push_system_user_to_assets +from .models import Asset, SystemUser, Node, AuthBook +from .tasks import ( + update_assets_hardware_info_util, + test_asset_connectivity_util, + push_system_user_to_assets +) logger = get_logger(__file__) @@ -109,3 +112,10 @@ def on_node_assets_changed(sender, instance=None, **kwargs): def on_node_update_or_created(sender, instance=None, created=False, **kwargs): if instance and not created: instance.expire_full_value() + + +@receiver(post_save, sender=AuthBook) +def on_auth_book_created(sender, instance=None, created=False, **kwargs): + if created: + logger.debug('Receive create auth book object signal.') + instance.set_version_and_latest() diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 4a166eada..d6dc58a8e 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -26,16 +26,22 @@ disk_pattern = re.compile(r'^hd|sd|xvd|vd') PERIOD_TASK = os.environ.get("PERIOD_TASK", "on") +def check_asset_can_run_ansible(asset): + if not asset.is_active: + msg = _("Asset has been disabled, skipped: {}").format(asset) + logger.info(msg) + return False + if not asset.support_ansible(): + msg = _("Asset may not be support ansible, skipped: {}").format(asset) + logger.info(msg) + return False + return True + + def clean_hosts(assets): clean_assets = [] for asset in assets: - if not asset.is_active: - msg = _("Asset has been disabled, skipped: {}").format(asset) - logger.info(msg) - continue - if not asset.support_ansible(): - msg = _("Asset may not be support ansible, skipped: {}").format(asset) - logger.info(msg) + if not check_asset_can_run_ansible(asset): continue clean_assets.append(asset) if not clean_assets: @@ -259,7 +265,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name): task, created = update_or_create_ansible_task( task_name, hosts=hosts, tasks=tasks, pattern='all', options=const.TASK_OPTIONS, - run_as=system_user, created_by=system_user.org_id, + run_as=system_user.username, created_by=system_user.org_id, ) result = task.run() set_system_user_connectivity_info(system_user, result) @@ -370,16 +376,18 @@ def push_system_user_util(system_user, assets, task_name): logger.info(msg) return - tasks = get_push_system_user_tasks(system_user) hosts = clean_hosts(assets) if not hosts: return {} - task, created = update_or_create_ansible_task( - task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, - created_by=system_user.org_id, - ) - return task.run() + for host in hosts: + system_user.load_specific_asset_auth(host) + tasks = get_push_system_user_tasks(system_user) + task, created = update_or_create_ansible_task( + task_name=task_name, hosts=[host], tasks=tasks, pattern='all', + options=const.TASK_OPTIONS, run_as_admin=True, + created_by=system_user.org_id, + ) + task.run() @shared_task @@ -415,6 +423,43 @@ def test_admin_user_connectability_period(): pass +@shared_task +def set_asset_user_connectivity_info(asset_user, result): + summary = result[1] + asset_user.connectivity = summary + + +@shared_task +def test_asset_user_connectivity_util(asset_user, task_name): + """ + :param asset_user: 对象 + :param task_name: + :return: + """ + from ops.utils import update_or_create_ansible_task + tasks = const.TEST_ASSET_USER_CONN_TASKS + if not check_asset_can_run_ansible(asset_user.asset): + return + + task, created = update_or_create_ansible_task( + task_name, hosts=[asset_user.asset], tasks=tasks, pattern='all', + options=const.TASK_OPTIONS, + run_as=asset_user.username, created_by=asset_user.org_id + ) + result = task.run() + set_asset_user_connectivity_info(asset_user, result) + + +@shared_task +def test_asset_users_connectivity_manual(asset_users): + """ + :param asset_users: 对象 + """ + for asset_user in asset_users: + task_name = _("Test asset user connectivity: {}").format(asset_user) + test_asset_user_connectivity_util(asset_user, task_name) + + # @shared_task # @register_as_period_task(interval=3600) # @after_app_ready_start diff --git a/apps/assets/templates/assets/_asset_user_auth_modal.html b/apps/assets/templates/assets/_asset_user_auth_modal.html new file mode 100644 index 000000000..be615ce52 --- /dev/null +++ b/apps/assets/templates/assets/_asset_user_auth_modal.html @@ -0,0 +1,28 @@ +{% extends '_modal.html' %} +{% load i18n %} +{% block modal_id %}asset_user_auth_modal{% endblock %} +{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %} +{% block modal_body %} +
+ {% csrf_token %} +
+ +
+

+
+
+
+ +
+

+
+
+
+ +
+ +
+
+
+{% endblock %} +{% block modal_confirm_id %}btn_asset_user_auth_modal_confirm{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index 7a9882563..d22c5406f 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -84,6 +84,7 @@ + {% include 'assets/_asset_user_auth_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/templates/assets/asset_asset_user_list.html b/apps/assets/templates/assets/asset_asset_user_list.html new file mode 100644 index 000000000..540be02d8 --- /dev/null +++ b/apps/assets/templates/assets/asset_asset_user_list.html @@ -0,0 +1,218 @@ +{% extends 'base.html' %} +{% load common_tags %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} +{% endblock %} + +{% block content %} +
+
+
+
+ +
+
+
+
+ {% trans 'Asset users of' %} {{ asset.hostname }} +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + +
{% trans 'Username' %}{% trans 'Version' %}{% trans 'Reachable' %}{% trans 'Date updated' %}{% trans 'Action' %}
+
+
+
+
+
+
+ {% trans 'Quick modify' %} +
+
+ + + {% if asset.protocol == 'ssh' %} + + + + + {% endif %} + +
{% trans 'Test connective' %}: + + + +
+
+
+
+
+
+
+
+
+ {% include 'assets/_asset_user_auth_modal.html' %} +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index c766f583c..4ff3bdf75 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -19,6 +19,9 @@
  • {% trans 'Asset detail' %}
  • +
  • + {% trans 'Asset user list' %} +
  • {% if user.is_superuser %}
  • {% trans 'Update' %} @@ -32,7 +35,7 @@
    -
    +
    {{ asset.hostname }} @@ -139,7 +142,7 @@
    {% if user.is_superuser or user.is_org_admin %} -
    +
    {% trans 'Quick modify' %} diff --git a/apps/assets/templates/assets/system_user_asset.html b/apps/assets/templates/assets/system_user_asset.html index 9bcf6b5aa..4ffdf2a91 100644 --- a/apps/assets/templates/assets/system_user_asset.html +++ b/apps/assets/templates/assets/system_user_asset.html @@ -132,6 +132,7 @@
    + {% include 'assets/_asset_user_auth_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 4aceda2e8..6ad54cf7e 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -17,6 +17,7 @@ router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'domain', api.DomainViewSet, 'domain') router.register(r'gateway', api.GatewayViewSet, 'gateway') router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter') +router.register(r'asset-user', api.AssetUserViewSet, 'asset-user') cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter') cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') @@ -31,6 +32,12 @@ urlpatterns = [ path('assets//gateway/', api.AssetGatewayApi.as_view(), name='asset-gateway'), + path('asset-user/auth-info/', + api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'), + path('asset-user/test-connective/', + api.AssetUserTestConnectiveApi.as_view(), name='asset-user-connective'), + + path('admin-user//nodes/', api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'), path('admin-user//auth/', @@ -42,6 +49,8 @@ urlpatterns = [ path('system-user//auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'), + path('system-user//asset//auth-info/', + api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'), path('system-user//assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'), path('system-user//push/', @@ -79,6 +88,7 @@ urlpatterns = [ path('gateway//test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), + ] urlpatterns += router.urls + cmd_filter_router.urls diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 76651cfdb..f9f67b849 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -15,6 +15,8 @@ urlpatterns = [ path('asset//update/', views.AssetUpdateView.as_view(), name='asset-update'), path('asset//delete/', views.AssetDeleteView.as_view(), name='asset-delete'), path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'), + # Asset user view + path('asset//asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'), # User asset view path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'), diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 572772dc5..b7493e047 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -34,7 +34,7 @@ from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain __all__ = [ - 'AssetListView', 'AssetCreateView', 'AssetUpdateView', + 'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView', 'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView', 'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView', ] @@ -56,6 +56,20 @@ class AssetListView(AdminUserRequiredMixin, TemplateView): return super().get_context_data(**kwargs) +class AssetUserListView(AdminUserRequiredMixin, DetailView): + model = Asset + context_object_name = 'asset' + template_name = 'assets/asset_asset_user_list.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Asset user list'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + class UserAssetListView(LoginRequiredMixin, TemplateView): template_name = 'assets/user_asset_list.html' diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 1147e07e8..b7e78e6df 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -572,3 +572,6 @@ LOGIN_LOG_KEEP_DAYS = CONFIG.LOGIN_LOG_KEEP_DAYS # User or user group permission cache time, default 3600 seconds ASSETS_PERM_CACHE_TIME = CONFIG.ASSETS_PERM_CACHE_TIME + +# Asset user auth external backend, default AuthBook backend +BACKEND_ASSET_USER_AUTH_VAULT = False diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index a9d91262d..cb390f14d 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 49634dcb3..542b7d674 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-02-26 16:49+0800\n" +"POT-Creation-Date: 2019-03-14 16:26+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -30,8 +30,8 @@ msgid "Test if the assets under the node are connectable: {}" msgstr "测试节点下资产是否可连接: {}" #: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133 -#: assets/templates/assets/asset_detail.html:191 -#: assets/templates/assets/asset_detail.html:199 +#: assets/templates/assets/asset_detail.html:194 +#: assets/templates/assets/asset_detail.html:202 #: assets/templates/assets/system_user_asset.html:95 perms/models.py:32 msgid "Nodes" msgstr "节点管理" @@ -39,7 +39,7 @@ msgstr "节点管理" #: assets/forms/asset.py:30 assets/forms/asset.py:66 assets/forms/asset.py:105 #: assets/forms/asset.py:109 assets/models/asset.py:84 #: assets/models/cluster.py:19 assets/models/user.py:91 -#: assets/templates/assets/asset_detail.html:77 templates/_nav.html:24 +#: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24 #: xpack/plugins/cloud/models.py:124 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:67 #: xpack/plugins/orgs/templates/orgs/org_list.html:18 @@ -52,15 +52,15 @@ msgstr "管理用户" #: assets/templates/assets/asset_list.html:81 #: assets/templates/assets/asset_update.html:41 #: assets/templates/assets/asset_update.html:43 -#: assets/templates/assets/user_asset_list.html:34 +#: assets/templates/assets/user_asset_list.html:33 #: xpack/plugins/orgs/templates/orgs/org_list.html:20 msgid "Label" msgstr "标签" #: assets/forms/asset.py:37 assets/forms/asset.py:73 assets/models/asset.py:79 #: assets/models/domain.py:26 assets/models/domain.py:52 -#: assets/templates/assets/asset_detail.html:81 -#: assets/templates/assets/user_asset_list.html:157 +#: assets/templates/assets/asset_detail.html:84 +#: assets/templates/assets/user_asset_list.html:163 #: xpack/plugins/orgs/templates/orgs/org_list.html:17 msgid "Domain" msgstr "网域" @@ -104,16 +104,17 @@ msgstr "选择资产" #: assets/forms/asset.py:101 assets/models/asset.py:77 #: assets/models/domain.py:50 assets/templates/assets/admin_user_assets.html:50 -#: assets/templates/assets/asset_detail.html:69 +#: assets/templates/assets/asset_detail.html:72 #: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/system_user_asset.html:52 -#: assets/templates/assets/user_asset_list.html:152 +#: assets/templates/assets/user_asset_list.html:158 #: settings/templates/settings/replay_storage_create.html:59 msgid "Port" msgstr "端口" #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:277 assets/templates/assets/admin_user_list.html:28 +#: assets/models/asset.py:279 assets/models/authbook.py:27 +#: assets/templates/assets/admin_user_list.html:28 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/label_list.html:16 @@ -129,6 +130,7 @@ msgstr "端口" #: terminal/templates/terminal/command_list.html:73 #: terminal/templates/terminal/session_list.html:41 #: terminal/templates/terminal/session_list.html:72 +#: xpack/plugins/change_asset_password_plan/models.py:17 #: xpack/plugins/cloud/models.py:187 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 #: xpack/plugins/orgs/templates/orgs/org_list.html:16 @@ -153,7 +155,7 @@ msgstr "不能包含特殊字符" #: assets/templates/assets/label_list.html:14 #: assets/templates/assets/system_user_detail.html:58 #: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37 -#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:35 +#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 #: orgs/models.py:12 perms/models.py:28 #: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_list.html:53 @@ -165,13 +167,14 @@ msgstr "不能包含特殊字符" #: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22 #: terminal/models.py:233 terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 -#: users/models/user.py:55 users/templates/users/_select_user_modal.html:13 +#: users/models/user.py:54 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_detail.html:63 #: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_list.html:12 #: users/templates/users/user_list.html:23 #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:53 +#: xpack/plugins/change_asset_password_plan/models.py:15 #: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 #: xpack/plugins/cloud/templates/cloud/account_detail.html:52 #: xpack/plugins/cloud/templates/cloud/account_list.html:12 @@ -183,20 +186,23 @@ msgid "Name" msgstr "名称" #: assets/forms/domain.py:71 assets/forms/user.py:81 assets/forms/user.py:143 -#: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60 +#: assets/models/base.py:23 +#: assets/templates/assets/_asset_user_auth_modal.html:15 +#: assets/templates/assets/admin_user_detail.html:60 #: assets/templates/assets/admin_user_list.html:27 +#: assets/templates/assets/asset_asset_user_list.html:48 #: assets/templates/assets/domain_gateway_list.html:60 #: assets/templates/assets/system_user_detail.html:62 -#: assets/templates/assets/system_user_list.html:30 -#: audits/templates/audits/login_log_list.html:49 -#: perms/templates/perms/asset_permission_list.html:74 -#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15 -#: users/forms.py:33 users/models/authentication.py:77 users/models/user.py:53 -#: users/templates/users/_select_user_modal.html:14 +#: assets/templates/assets/system_user_list.html:30 audits/models.py:93 +#: audits/templates/audits/login_log_list.html:49 authentication/forms.py:11 +#: ops/models/adhoc.py:164 perms/templates/perms/asset_permission_list.html:74 +#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13 +#: users/models/user.py:52 users/templates/users/_select_user_modal.html:14 #: users/templates/users/login.html:64 users/templates/users/new_login.html:85 #: users/templates/users/user_detail.html:67 #: users/templates/users/user_list.html:24 #: users/templates/users/user_profile.html:47 +#: xpack/plugins/change_asset_password_plan/models.py:16 msgid "Username" msgstr "用户名" @@ -204,9 +210,12 @@ msgstr "用户名" msgid "Password or private key passphrase" msgstr "密码或密钥密码" -#: assets/forms/user.py:26 assets/models/base.py:24 settings/forms.py:103 -#: users/forms.py:17 users/forms.py:35 users/forms.py:47 -#: users/templates/users/login.html:67 users/templates/users/new_login.html:90 +#: assets/forms/user.py:26 assets/models/base.py:24 +#: assets/serializers/asset_user.py:19 +#: assets/templates/assets/_asset_user_auth_modal.html:21 +#: authentication/forms.py:13 settings/forms.py:103 users/forms.py:15 +#: users/forms.py:27 users/templates/users/login.html:67 +#: users/templates/users/new_login.html:90 #: users/templates/users/reset_password.html:53 #: users/templates/users/user_create.html:10 #: users/templates/users/user_password_authentication.html:18 @@ -214,10 +223,13 @@ msgstr "密码或密钥密码" #: users/templates/users/user_profile_update.html:40 #: users/templates/users/user_pubkey_update.html:40 #: users/templates/users/user_update.html:20 +#: xpack/plugins/change_asset_password_plan/models.py:19 msgid "Password" msgstr "密码" -#: assets/forms/user.py:29 users/models/user.py:82 +#: assets/forms/user.py:29 assets/serializers/asset_user.py:27 +#: users/models/user.py:81 +#: xpack/plugins/change_asset_password_plan/models.py:20 msgid "Private key" msgstr "ssh私钥" @@ -264,72 +276,73 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" #: assets/models/asset.py:74 assets/models/domain.py:49 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/admin_user_assets.html:49 -#: assets/templates/assets/asset_detail.html:61 +#: assets/templates/assets/asset_detail.html:64 #: assets/templates/assets/asset_list.html:93 #: assets/templates/assets/domain_gateway_list.html:57 #: assets/templates/assets/system_user_asset.html:51 -#: assets/templates/assets/user_asset_list.html:46 -#: assets/templates/assets/user_asset_list.html:151 +#: assets/templates/assets/user_asset_list.html:45 +#: assets/templates/assets/user_asset_list.html:157 #: audits/templates/audits/login_log_list.html:52 -#: perms/templates/perms/asset_permission_asset.html:55 settings/forms.py:132 +#: perms/templates/perms/asset_permission_asset.html:55 settings/forms.py:133 #: users/templates/users/user_granted_asset.html:45 #: users/templates/users/user_group_granted_asset.html:45 msgid "IP" msgstr "IP" #: assets/models/asset.py:75 assets/templates/assets/_asset_list_modal.html:45 +#: assets/templates/assets/_asset_user_auth_modal.html:9 #: assets/templates/assets/admin_user_assets.html:48 -#: assets/templates/assets/asset_detail.html:57 +#: assets/templates/assets/asset_detail.html:60 #: assets/templates/assets/asset_list.html:92 #: assets/templates/assets/system_user_asset.html:50 -#: assets/templates/assets/user_asset_list.html:45 -#: assets/templates/assets/user_asset_list.html:150 +#: assets/templates/assets/user_asset_list.html:44 +#: assets/templates/assets/user_asset_list.html:156 #: perms/templates/perms/asset_permission_asset.html:54 -#: perms/templates/perms/asset_permission_list.html:77 settings/forms.py:131 +#: perms/templates/perms/asset_permission_list.html:77 settings/forms.py:132 #: users/templates/users/user_granted_asset.html:44 #: users/templates/users/user_group_granted_asset.html:44 msgid "Hostname" msgstr "主机名" #: assets/models/asset.py:76 assets/models/domain.py:51 -#: assets/models/user.py:136 assets/templates/assets/asset_detail.html:73 +#: assets/models/user.py:136 assets/templates/assets/asset_detail.html:76 #: assets/templates/assets/domain_gateway_list.html:59 #: assets/templates/assets/system_user_detail.html:70 #: assets/templates/assets/system_user_list.html:31 -#: assets/templates/assets/user_asset_list.html:153 +#: assets/templates/assets/user_asset_list.html:159 #: terminal/templates/terminal/session_list.html:75 msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:78 assets/templates/assets/asset_detail.html:105 -#: assets/templates/assets/user_asset_list.html:154 +#: assets/models/asset.py:78 assets/templates/assets/asset_detail.html:108 +#: assets/templates/assets/user_asset_list.html:160 msgid "Platform" msgstr "系统平台" #: assets/models/asset.py:81 assets/models/cmd_filter.py:21 #: assets/models/domain.py:54 assets/models/label.py:22 -#: assets/templates/assets/asset_detail.html:113 -#: assets/templates/assets/user_asset_list.html:158 +#: assets/templates/assets/asset_detail.html:116 +#: assets/templates/assets/user_asset_list.html:164 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:65 +#: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:68 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:121 +#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:124 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:85 +#: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:88 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:89 +#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:92 msgid "Model" msgstr "型号" -#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:117 +#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:120 msgid "Serial number" msgstr "序列号" @@ -350,7 +363,7 @@ msgstr "CPU核数" msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:97 +#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:100 msgid "Memory" msgstr "内存" @@ -362,8 +375,8 @@ msgstr "硬盘大小" msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:103 assets/templates/assets/asset_detail.html:109 -#: assets/templates/assets/user_asset_list.html:155 +#: assets/models/asset.py:103 assets/templates/assets/asset_detail.html:112 +#: assets/templates/assets/user_asset_list.html:161 msgid "OS" msgstr "操作系统" @@ -380,7 +393,7 @@ msgid "Hostname raw" msgstr "主机名原始" #: assets/models/asset.py:108 assets/templates/assets/asset_create.html:34 -#: assets/templates/assets/asset_detail.html:228 +#: assets/templates/assets/asset_detail.html:231 #: assets/templates/assets/asset_update.html:39 templates/_nav.html:26 msgid "Labels" msgstr "标签管理" @@ -389,13 +402,13 @@ msgstr "标签管理" #: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 #: assets/models/cmd_filter.py:58 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:68 -#: assets/templates/assets/asset_detail.html:125 +#: assets/templates/assets/asset_detail.html:128 #: assets/templates/assets/cmd_filter_detail.html:77 #: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/system_user_detail.html:100 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:37 #: perms/models.py:90 perms/templates/perms/asset_permission_detail.html:98 -#: users/models/user.py:96 users/templates/users/user_detail.html:111 +#: users/models/user.py:95 users/templates/users/user_detail.html:111 #: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 msgid "Created by" msgstr "创建者" @@ -424,7 +437,7 @@ msgstr "创建日期" #: assets/models/domain.py:53 assets/models/group.py:23 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/admin_user_list.html:32 -#: assets/templates/assets/asset_detail.html:133 +#: assets/templates/assets/asset_detail.html:136 #: assets/templates/assets/cmd_filter_detail.html:65 #: assets/templates/assets/cmd_filter_list.html:27 #: assets/templates/assets/cmd_filter_rule_list.html:62 @@ -433,16 +446,17 @@ msgstr "创建日期" #: assets/templates/assets/domain_list.html:28 #: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_list.html:37 -#: assets/templates/assets/user_asset_list.html:159 ops/models/adhoc.py:43 +#: assets/templates/assets/user_asset_list.html:165 ops/models/adhoc.py:43 #: orgs/models.py:17 perms/models.py:39 perms/models.py:92 #: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34 #: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63 -#: users/models/group.py:15 users/models/user.py:88 +#: users/models/group.py:15 users/models/user.py:87 #: users/templates/users/user_detail.html:127 #: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_list.html:14 -#: users/templates/users/user_profile.html:134 xpack/plugins/cloud/models.py:54 -#: xpack/plugins/cloud/models.py:125 +#: users/templates/users/user_profile.html:134 +#: xpack/plugins/change_asset_password_plan/models.py:26 +#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 #: xpack/plugins/cloud/templates/cloud/account_detail.html:72 #: xpack/plugins/cloud/templates/cloud/account_list.html:15 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:71 @@ -461,6 +475,7 @@ msgstr "不可达" #: assets/models/asset.py:118 assets/models/base.py:35 #: assets/templates/assets/admin_user_assets.html:51 #: assets/templates/assets/admin_user_list.html:29 +#: assets/templates/assets/asset_asset_user_list.html:50 #: assets/templates/assets/asset_list.html:95 #: assets/templates/assets/system_user_asset.html:53 #: assets/templates/assets/system_user_list.html:34 @@ -469,10 +484,26 @@ msgid "Reachable" msgstr "可连接" #: assets/models/asset.py:119 assets/models/base.py:36 -#: xpack/plugins/license/models.py:78 +#: authentication/utils.py:9 xpack/plugins/license/models.py:78 msgid "Unknown" msgstr "未知" +#: assets/models/authbook.py:28 ops/templates/ops/task_detail.html:72 +msgid "Latest version" +msgstr "最新版本" + +#: assets/models/authbook.py:29 +#: assets/templates/assets/asset_asset_user_list.html:49 +#: ops/templates/ops/adhoc_history.html:58 +#: ops/templates/ops/adhoc_history_detail.html:57 +#: ops/templates/ops/task_adhoc.html:58 ops/templates/ops/task_history.html:64 +msgid "Version" +msgstr "版本" + +#: assets/models/authbook.py:34 +msgid "AuthBook" +msgstr "" + #: assets/models/base.py:25 msgid "SSH private key" msgstr "ssh密钥" @@ -489,7 +520,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:74 +#: assets/models/cluster.py:22 users/models/user.py:73 #: users/templates/users/user_detail.html:76 msgid "Phone" msgstr "手机" @@ -515,7 +546,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:441 +#: users/models/user.py:457 msgid "System" msgstr "系统" @@ -596,6 +627,7 @@ msgstr "每行一个命令" #: assets/models/cmd_filter.py:54 #: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/admin_user_list.html:33 +#: assets/templates/assets/asset_asset_user_list.html:52 #: assets/templates/assets/asset_list.html:96 #: assets/templates/assets/cmd_filter_list.html:28 #: assets/templates/assets/cmd_filter_rule_list.html:63 @@ -607,7 +639,7 @@ msgstr "每行一个命令" #: audits/templates/audits/operate_log_list.html:41 #: audits/templates/audits/operate_log_list.html:67 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 -#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:42 +#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34 #: perms/templates/perms/asset_permission_list.html:60 #: settings/templates/settings/terminal_setting.html:82 #: settings/templates/settings/terminal_setting.html:104 @@ -657,8 +689,8 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:303 -#: users/models/user.py:33 users/models/user.py:429 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:283 +#: users/models/user.py:32 users/models/user.py:445 #: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:13 users/views/user.py:386 #: xpack/plugins/orgs/forms.py:26 @@ -680,7 +712,7 @@ msgstr "分类" msgid "Key" msgstr "键" -#: assets/models/node.py:127 +#: assets/models/node.py:128 msgid "New node" msgstr "新节点" @@ -699,18 +731,19 @@ msgstr "手动登录" #: assets/views/admin_user.py:29 assets/views/admin_user.py:47 #: assets/views/admin_user.py:63 assets/views/admin_user.py:78 #: assets/views/admin_user.py:102 assets/views/asset.py:50 -#: assets/views/asset.py:89 assets/views/asset.py:133 assets/views/asset.py:150 -#: assets/views/asset.py:174 assets/views/cmd_filter.py:30 -#: assets/views/cmd_filter.py:46 assets/views/cmd_filter.py:62 -#: assets/views/cmd_filter.py:78 assets/views/cmd_filter.py:97 -#: assets/views/cmd_filter.py:130 assets/views/cmd_filter.py:163 -#: assets/views/domain.py:29 assets/views/domain.py:45 -#: assets/views/domain.py:61 assets/views/domain.py:74 -#: assets/views/domain.py:98 assets/views/domain.py:126 -#: assets/views/domain.py:145 assets/views/label.py:26 assets/views/label.py:43 -#: assets/views/label.py:69 assets/views/system_user.py:28 -#: assets/views/system_user.py:44 assets/views/system_user.py:60 -#: assets/views/system_user.py:74 templates/_nav.html:19 +#: assets/views/asset.py:66 assets/views/asset.py:103 assets/views/asset.py:147 +#: assets/views/asset.py:164 assets/views/asset.py:188 +#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46 +#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78 +#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130 +#: assets/views/cmd_filter.py:163 assets/views/domain.py:29 +#: assets/views/domain.py:45 assets/views/domain.py:61 +#: assets/views/domain.py:74 assets/views/domain.py:98 +#: assets/views/domain.py:126 assets/views/domain.py:145 +#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69 +#: assets/views/system_user.py:28 assets/views/system_user.py:44 +#: assets/views/system_user.py:60 assets/views/system_user.py:74 +#: templates/_nav.html:19 msgid "Assets" msgstr "资产管理" @@ -733,7 +766,7 @@ msgstr "Shell" msgid "Login mode" msgstr "登录模式" -#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:156 +#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:162 #: audits/models.py:19 audits/templates/audits/ftp_log_list.html:49 #: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48 #: perms/models.py:33 perms/models.py:87 @@ -755,71 +788,85 @@ msgstr "系统用户" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/tasks.py:33 +#: assets/serializers/asset_user.py:23 users/forms.py:230 +#: users/models/user.py:84 users/templates/users/first_login.html:42 +#: users/templates/users/user_password_update.html:46 +#: users/templates/users/user_profile.html:68 +#: users/templates/users/user_profile_update.html:43 +#: users/templates/users/user_pubkey_update.html:43 +#: xpack/plugins/change_asset_password_plan/models.py:21 +msgid "Public key" +msgstr "ssh公钥" + +#: assets/tasks.py:31 msgid "Asset has been disabled, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" -#: assets/tasks.py:37 +#: assets/tasks.py:35 msgid "Asset may not be support ansible, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" -#: assets/tasks.py:42 +#: assets/tasks.py:48 msgid "No assets matched, stop task" msgstr "没有匹配到资产,结束任务" -#: assets/tasks.py:67 +#: assets/tasks.py:73 msgid "Get asset info failed: {}" msgstr "获取资产信息失败:{}" -#: assets/tasks.py:117 +#: assets/tasks.py:123 msgid "Update some assets hardware info" msgstr "更新资产硬件信息" -#: assets/tasks.py:134 +#: assets/tasks.py:140 msgid "Update asset hardware info: {}" msgstr "更新资产硬件信息: {}" -#: assets/tasks.py:159 +#: assets/tasks.py:165 msgid "Test assets connectivity" msgstr "测试资产可连接性" -#: assets/tasks.py:183 +#: assets/tasks.py:189 msgid "Test assets connectivity: {}" msgstr "测试资产可连接性: {}" -#: assets/tasks.py:225 +#: assets/tasks.py:231 msgid "Test admin user connectivity period: {}" msgstr "定期测试管理账号可连接性: {}" -#: assets/tasks.py:232 +#: assets/tasks.py:238 msgid "Test admin user connectivity: {}" msgstr "测试管理行号可连接性: {}" -#: assets/tasks.py:271 +#: assets/tasks.py:277 msgid "Test system user connectivity: {}" msgstr "测试系统用户可连接性: {}" -#: assets/tasks.py:278 +#: assets/tasks.py:284 msgid "Test system user connectivity: {} => {}" msgstr "测试系统用户可连接性: {} => {}" -#: assets/tasks.py:291 +#: assets/tasks.py:297 msgid "Test system user connectivity period: {}" msgstr "定期测试系统用户可连接性: {}" -#: assets/tasks.py:368 +#: assets/tasks.py:374 msgid "" "Push system user task skip, auto push not enable or protocol is not ssh: {}" msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}" -#: assets/tasks.py:388 assets/tasks.py:402 +#: assets/tasks.py:396 assets/tasks.py:410 msgid "Push system users to assets: {}" msgstr "推送系统用户到入资产: {}" -#: assets/tasks.py:394 +#: assets/tasks.py:402 msgid "Push system users to asset: {} => {}" msgstr "推送系统用户到入资产: {} => {}" +#: assets/tasks.py:459 +msgid "Test asset user connectivity: {}" +msgstr "测试资产用户可连接性: {}" + #: assets/templates/assets/_asset_group_bulk_update_modal.html:5 msgid "Update asset group" msgstr "更新用户组" @@ -835,7 +882,7 @@ msgstr "选择资产" #: assets/templates/assets/_asset_group_bulk_update_modal.html:21 #: assets/templates/assets/cmd_filter_detail.html:89 #: assets/templates/assets/cmd_filter_list.html:26 -#: assets/templates/assets/user_asset_list.html:48 +#: assets/templates/assets/user_asset_list.html:47 #: users/templates/users/user_granted_asset.html:47 msgid "System users" msgstr "系统用户" @@ -875,6 +922,14 @@ msgstr "如果设置了id,则会使用该行信息更新该id的资产" msgid "Asset list" msgstr "资产列表" +#: assets/templates/assets/_asset_user_auth_modal.html:4 +msgid "Update asset user auth" +msgstr "更新资产用户认证信息" + +#: assets/templates/assets/_asset_user_auth_modal.html:23 +msgid "Please input password" +msgstr "请输入密码" + #: assets/templates/assets/_system_user.html:37 #: assets/templates/assets/asset_create.html:16 #: assets/templates/assets/asset_update.html:21 @@ -971,7 +1026,8 @@ msgid "Submit" msgstr "提交" #: assets/templates/assets/_user_asset_detail_modal.html:11 -#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:175 +#: assets/templates/assets/asset_asset_user_list.html:17 +#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:189 msgid "Asset detail" msgstr "资产详情" @@ -1015,22 +1071,47 @@ msgid "Quick update" msgstr "快速更新" #: assets/templates/assets/admin_user_assets.html:70 -#: assets/templates/assets/asset_detail.html:176 +#: assets/templates/assets/asset_asset_user_list.html:71 +#: assets/templates/assets/asset_detail.html:179 msgid "Test connective" msgstr "测试可连接性" #: assets/templates/assets/admin_user_assets.html:73 -#: assets/templates/assets/admin_user_assets.html:113 -#: assets/templates/assets/asset_detail.html:179 +#: assets/templates/assets/admin_user_assets.html:114 +#: assets/templates/assets/asset_asset_user_list.html:74 +#: assets/templates/assets/asset_asset_user_list.html:122 +#: assets/templates/assets/asset_detail.html:182 #: assets/templates/assets/system_user_asset.html:75 -#: assets/templates/assets/system_user_asset.html:163 +#: assets/templates/assets/system_user_asset.html:164 #: assets/templates/assets/system_user_detail.html:151 msgid "Test" msgstr "测试" +#: assets/templates/assets/admin_user_assets.html:115 +#: assets/templates/assets/asset_asset_user_list.html:120 +#: assets/templates/assets/system_user_asset.html:166 +msgid "Update auth" +msgstr "更新认证" + +#: assets/templates/assets/admin_user_assets.html:191 +#: assets/templates/assets/asset_asset_user_list.html:176 +#: assets/templates/assets/asset_detail.html:311 +#: assets/templates/assets/system_user_asset.html:350 +#: users/templates/users/user_detail.html:307 +#: users/templates/users/user_detail.html:334 +#: xpack/plugins/interface/views.py:31 +msgid "Update successfully!" +msgstr "更新成功" + +#: assets/templates/assets/admin_user_assets.html:194 +#: assets/templates/assets/asset_asset_user_list.html:179 +#: assets/templates/assets/system_user_asset.html:353 +msgid "Update failed!" +msgstr "更新失败" + #: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_list.html:88 -#: assets/templates/assets/asset_detail.html:24 +#: assets/templates/assets/asset_detail.html:27 #: assets/templates/assets/asset_list.html:177 #: assets/templates/assets/cmd_filter_detail.html:29 #: assets/templates/assets/cmd_filter_list.html:57 @@ -1062,7 +1143,7 @@ msgstr "更新" #: assets/templates/assets/admin_user_detail.html:28 #: assets/templates/assets/admin_user_list.html:89 -#: assets/templates/assets/asset_detail.html:28 +#: assets/templates/assets/asset_detail.html:31 #: assets/templates/assets/asset_list.html:178 #: assets/templates/assets/cmd_filter_detail.html:33 #: assets/templates/assets/cmd_filter_list.html:58 @@ -1074,7 +1155,7 @@ msgstr "更新" #: assets/templates/assets/label_list.html:39 #: assets/templates/assets/system_user_detail.html:30 #: assets/templates/assets/system_user_list.html:93 audits/models.py:33 -#: ops/templates/ops/task_list.html:72 +#: ops/templates/ops/task_list.html:64 #: perms/templates/perms/asset_permission_detail.html:34 #: perms/templates/perms/asset_permission_list.html:178 #: settings/templates/settings/terminal_setting.html:90 @@ -1104,7 +1185,7 @@ msgid "Select nodes" msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 -#: assets/templates/assets/asset_detail.html:208 +#: assets/templates/assets/asset_detail.html:211 #: assets/templates/assets/asset_list.html:636 #: assets/templates/assets/cmd_filter_detail.html:106 #: assets/templates/assets/system_user_asset.html:112 @@ -1155,6 +1236,29 @@ msgstr "创建管理用户" msgid "Ratio" msgstr "比例" +#: assets/templates/assets/asset_asset_user_list.html:20 +#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:67 +msgid "Asset user list" +msgstr "资产用户列表" + +#: assets/templates/assets/asset_asset_user_list.html:28 +msgid "Asset users of" +msgstr "资产用户" + +#: assets/templates/assets/asset_asset_user_list.html:51 +#: assets/templates/assets/cmd_filter_detail.html:73 +msgid "Date updated" +msgstr "更新日期" + +#: assets/templates/assets/asset_asset_user_list.html:64 +#: assets/templates/assets/asset_detail.html:148 +#: terminal/templates/terminal/session_detail.html:81 +#: users/templates/users/user_detail.html:138 +#: users/templates/users/user_profile.html:146 +#: xpack/plugins/license/templates/license/license_detail.html:93 +msgid "Quick modify" +msgstr "快速修改" + #: assets/templates/assets/asset_bulk_update.html:8 #: users/templates/users/user_bulk_update.html:8 msgid "Select properties that need to be modified" @@ -1165,30 +1269,22 @@ msgstr "选择需要修改属性" msgid "Select all" msgstr "全选" -#: assets/templates/assets/asset_detail.html:93 +#: assets/templates/assets/asset_detail.html:96 msgid "CPU" msgstr "CPU" -#: assets/templates/assets/asset_detail.html:101 +#: assets/templates/assets/asset_detail.html:104 msgid "Disk" msgstr "硬盘" -#: assets/templates/assets/asset_detail.html:129 +#: assets/templates/assets/asset_detail.html:132 #: users/templates/users/user_detail.html:115 #: users/templates/users/user_profile.html:104 msgid "Date joined" msgstr "创建日期" -#: assets/templates/assets/asset_detail.html:145 -#: terminal/templates/terminal/session_detail.html:81 -#: users/templates/users/user_detail.html:138 -#: users/templates/users/user_profile.html:146 -#: xpack/plugins/license/templates/license/license_detail.html:93 -msgid "Quick modify" -msgstr "快速修改" - -#: assets/templates/assets/asset_detail.html:151 -#: assets/templates/assets/user_asset_list.html:47 perms/models.py:34 +#: assets/templates/assets/asset_detail.html:154 +#: assets/templates/assets/user_asset_list.html:46 perms/models.py:34 #: perms/models.py:88 #: perms/templates/perms/asset_permission_create_update.html:52 #: perms/templates/perms/asset_permission_detail.html:120 @@ -1201,21 +1297,14 @@ msgstr "快速修改" msgid "Active" msgstr "激活中" -#: assets/templates/assets/asset_detail.html:168 +#: assets/templates/assets/asset_detail.html:171 msgid "Refresh hardware" msgstr "更新硬件信息" -#: assets/templates/assets/asset_detail.html:171 +#: assets/templates/assets/asset_detail.html:174 msgid "Refresh" msgstr "刷新" -#: assets/templates/assets/asset_detail.html:308 -#: users/templates/users/user_detail.html:307 -#: users/templates/users/user_detail.html:334 -#: xpack/plugins/interface/views.py:31 -msgid "Update successfully!" -msgstr "更新成功" - #: assets/templates/assets/asset_list.html:8 msgid "" "The left side is the asset tree, right click to create, delete, and change " @@ -1225,7 +1314,7 @@ msgstr "" "左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的," "右侧是属于该节点下的资产" -#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:90 +#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:104 msgid "Create asset" msgstr "创建资产" @@ -1378,10 +1467,6 @@ msgstr "配置" msgid "Rules" msgstr "规则" -#: assets/templates/assets/cmd_filter_detail.html:73 -msgid "Date updated" -msgstr "更新日期" - #: assets/templates/assets/cmd_filter_detail.html:97 msgid "Binding to system user" msgstr "绑定到系统用户" @@ -1500,7 +1585,7 @@ msgid "Push system user now" msgstr "立刻推送系统" #: assets/templates/assets/system_user_asset.html:84 -#: assets/templates/assets/system_user_asset.html:161 +#: assets/templates/assets/system_user_asset.html:162 #: assets/templates/assets/system_user_detail.html:142 msgid "Push" msgstr "推送" @@ -1585,23 +1670,23 @@ msgstr "更新管理用户" msgid "Admin user detail" msgstr "管理用户详情" -#: assets/views/asset.py:64 templates/_nav_user.html:4 +#: assets/views/asset.py:78 templates/_nav_user.html:4 msgid "My assets" msgstr "我的资产" -#: assets/views/asset.py:104 +#: assets/views/asset.py:118 msgid "Bulk update asset success" msgstr "批量更新资产成功" -#: assets/views/asset.py:134 +#: assets/views/asset.py:148 msgid "Bulk update asset" msgstr "批量更新资产" -#: assets/views/asset.py:151 +#: assets/views/asset.py:165 msgid "Update asset" msgstr "更新资产" -#: assets/views/asset.py:292 +#: assets/views/asset.py:306 msgid "already exists" msgstr "已经存在" @@ -1695,9 +1780,10 @@ msgstr "操作" msgid "Filename" msgstr "文件名" -#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76 +#: audits/models.py:22 audits/models.py:89 +#: audits/templates/audits/ftp_log_list.html:76 #: ops/templates/ops/command_execution_list.html:64 -#: ops/templates/ops/task_list.html:39 users/models/authentication.py:73 +#: ops/templates/ops/task_list.html:31 #: users/templates/users/user_detail.html:458 xpack/plugins/cloud/api.py:62 msgid "Success" msgstr "成功" @@ -1719,6 +1805,79 @@ msgstr "资源" msgid "Change by" msgstr "修改者" +#: audits/models.py:69 users/templates/users/user_detail.html:98 +msgid "Disabled" +msgstr "禁用" + +#: audits/models.py:70 settings/models.py:33 +#: users/templates/users/user_detail.html:96 +msgid "Enabled" +msgstr "启用" + +#: audits/models.py:71 audits/models.py:81 +msgid "-" +msgstr "" + +#: audits/models.py:82 +msgid "Username/password check failed" +msgstr "用户名/密码 校验失败" + +#: audits/models.py:83 +msgid "MFA authentication failed" +msgstr "MFA 认证失败" + +#: audits/models.py:84 +msgid "Username does not exist" +msgstr "用户名不存在" + +#: audits/models.py:85 +msgid "Password expired" +msgstr "密码过期" + +#: audits/models.py:90 xpack/plugins/cloud/models.py:164 +#: xpack/plugins/cloud/models.py:178 +msgid "Failed" +msgstr "失败" + +#: audits/models.py:94 +msgid "Login type" +msgstr "登录方式" + +#: audits/models.py:95 +msgid "Login ip" +msgstr "登录IP" + +#: audits/models.py:96 +msgid "Login city" +msgstr "登录城市" + +#: audits/models.py:97 +msgid "User agent" +msgstr "Agent" + +#: audits/models.py:98 audits/templates/audits/login_log_list.html:54 +#: users/forms.py:142 users/models/user.py:76 +#: users/templates/users/first_login.html:45 +msgid "MFA" +msgstr "MFA" + +#: audits/models.py:99 audits/templates/audits/login_log_list.html:55 +#: xpack/plugins/cloud/models.py:172 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69 +msgid "Reason" +msgstr "原因" + +#: audits/models.py:100 audits/templates/audits/login_log_list.html:56 +#: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:188 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 +msgid "Status" +msgstr "状态" + +#: audits/models.py:101 +msgid "Date login" +msgstr "登录日期" + #: audits/templates/audits/ftp_log_list.html:77 #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 @@ -1740,7 +1899,7 @@ msgstr "选择用户" #: audits/templates/audits/password_change_log_list.html:42 #: ops/templates/ops/command_execution_list.html:42 #: ops/templates/ops/command_execution_list.html:47 -#: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26 +#: ops/templates/ops/task_list.html:13 ops/templates/ops/task_list.html:18 #: templates/_base_list.html:43 templates/_header_bar.html:8 #: terminal/templates/terminal/command_list.html:60 #: terminal/templates/terminal/session_list.html:61 @@ -1767,28 +1926,8 @@ msgstr "Agent" msgid "City" msgstr "城市" -#: audits/templates/audits/login_log_list.html:54 users/forms.py:162 -#: users/models/authentication.py:82 users/models/user.py:77 -#: users/templates/users/first_login.html:45 -msgid "MFA" -msgstr "MFA" - -#: audits/templates/audits/login_log_list.html:55 -#: users/models/authentication.py:83 xpack/plugins/cloud/models.py:172 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69 -msgid "Reason" -msgstr "原因" - -#: audits/templates/audits/login_log_list.html:56 -#: users/models/authentication.py:84 xpack/plugins/cloud/models.py:171 -#: xpack/plugins/cloud/models.py:188 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 -msgid "Status" -msgstr "状态" - #: audits/templates/audits/login_log_list.html:57 -#: ops/templates/ops/task_list.html:40 +#: ops/templates/ops/task_list.html:32 msgid "Date" msgstr "日期" @@ -1800,31 +1939,128 @@ msgstr "日期" msgid "Datetime" msgstr "日期" -#: audits/views.py:71 audits/views.py:115 audits/views.py:151 -#: audits/views.py:195 audits/views.py:226 templates/_nav.html:72 +#: audits/views.py:70 audits/views.py:114 audits/views.py:150 +#: audits/views.py:194 audits/views.py:225 templates/_nav.html:72 msgid "Audits" msgstr "日志审计" -#: audits/views.py:72 templates/_nav.html:76 +#: audits/views.py:71 templates/_nav.html:76 msgid "FTP log" msgstr "FTP日志" -#: audits/views.py:116 templates/_nav.html:77 +#: audits/views.py:115 templates/_nav.html:77 msgid "Operate log" msgstr "操作日志" -#: audits/views.py:152 templates/_nav.html:78 +#: audits/views.py:151 templates/_nav.html:78 msgid "Password change log" msgstr "改密日志" -#: audits/views.py:196 templates/_nav.html:75 +#: audits/views.py:195 templates/_nav.html:75 msgid "Login log" msgstr "登录日志" -#: audits/views.py:227 ops/views/command.py:44 +#: audits/views.py:226 ops/views/command.py:44 msgid "Command execution list" msgstr "命令执行列表" +#: authentication/api/auth.py:46 users/templates/users/login.html:52 +#: users/templates/users/new_login.html:71 +msgid "Log in frequently and try again later" +msgstr "登录频繁, 稍后重试" + +#: authentication/api/auth.py:64 +msgid "The user {} password has expired, please update." +msgstr "用户 {} 密码已经过期,请更新。" + +#: authentication/api/auth.py:83 +msgid "Please carry seed value and conduct MFA secondary certification" +msgstr "请携带seed值, 进行MFA二次认证" + +#: authentication/api/auth.py:163 +msgid "Please verify the user name and password first" +msgstr "请先进行用户名和密码验证" + +#: authentication/api/auth.py:168 +msgid "MFA certification failed" +msgstr "MFA认证失败" + +#: authentication/backends/api.py:52 +msgid "Invalid signature header. No credentials provided." +msgstr "" + +#: authentication/backends/api.py:55 +msgid "Invalid signature header. Signature string should not contain spaces." +msgstr "" + +#: authentication/backends/api.py:62 +msgid "Invalid signature header. Format like AccessKeyId:Signature" +msgstr "" + +#: authentication/backends/api.py:66 +msgid "" +"Invalid signature header. Signature string should not contain invalid " +"characters." +msgstr "" + +#: authentication/backends/api.py:86 authentication/backends/api.py:102 +msgid "Invalid signature." +msgstr "" + +#: authentication/backends/api.py:93 +msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" +msgstr "" + +#: authentication/backends/api.py:98 +msgid "Expired, more than 15 minutes" +msgstr "" + +#: authentication/backends/api.py:105 +msgid "User disabled." +msgstr "用户已禁用" + +#: authentication/backends/api.py:120 +msgid "Invalid token header. No credentials provided." +msgstr "" + +#: authentication/backends/api.py:123 +msgid "Invalid token header. Sign string should not contain spaces." +msgstr "" + +#: authentication/backends/api.py:130 +msgid "" +"Invalid token header. Sign string should not contain invalid characters." +msgstr "" + +#: authentication/backends/api.py:140 +msgid "Invalid token or cache refreshed." +msgstr "" + +#: authentication/forms.py:29 users/forms.py:21 +msgid "MFA code" +msgstr "MFA 验证码" + +#: authentication/models.py:33 +msgid "Private Token" +msgstr "ssh密钥" + +#: authentication/views/login.py:75 +msgid "Please enable cookies and try again." +msgstr "设置你的浏览器支持cookie" + +#: authentication/views/login.py:167 users/views/user.py:532 +#: users/views/user.py:557 +msgid "MFA code invalid, or ntp sync server time" +msgstr "MFA验证码不正确,或者服务器端时间不对" + +#: authentication/views/login.py:198 +msgid "Logout success" +msgstr "退出登录成功" + +#: authentication/views/login.py:199 +msgid "Logout success, return login page" +msgstr "退出登录成功,返回到登录页面" + #: common/const.py:6 #, python-format msgid "%(name)s was created successfully" @@ -1873,19 +2109,20 @@ msgstr "" msgid "Waiting task start" msgstr "等待任务开始" -#: ops/models/adhoc.py:38 +#: ops/models/adhoc.py:38 xpack/plugins/change_asset_password_plan/models.py:22 msgid "Interval" msgstr "间隔" -#: ops/models/adhoc.py:38 settings/forms.py:150 +#: ops/models/adhoc.py:38 settings/forms.py:151 +#: xpack/plugins/change_asset_password_plan/models.py:22 msgid "Units: seconds" msgstr "单位: 秒" -#: ops/models/adhoc.py:39 +#: ops/models/adhoc.py:39 xpack/plugins/change_asset_password_plan/models.py:23 msgid "Crontab" msgstr "Crontab" -#: ops/models/adhoc.py:39 +#: ops/models/adhoc.py:39 xpack/plugins/change_asset_password_plan/models.py:23 msgid "5 * * * *" msgstr "" @@ -1908,7 +2145,7 @@ msgstr "选项" #: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:53 #: ops/templates/ops/command_execution_list.html:58 -#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38 +#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:30 #: settings/templates/settings/command_storage_create.html:49 msgid "Hosts" msgstr "主机" @@ -1951,7 +2188,7 @@ msgid "End time" msgstr "完成时间" #: ops/models/adhoc.py:325 ops/templates/ops/adhoc_history.html:57 -#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41 +#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:33 msgid "Time" msgstr "时间" @@ -1997,7 +2234,7 @@ msgid "Version detail" msgstr "版本详情" #: ops/templates/ops/adhoc_detail.html:22 -#: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:127 +#: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:122 msgid "Version run history" msgstr "执行历史" @@ -2008,7 +2245,7 @@ msgstr "执行历史" msgid "Run as" msgstr "运行用户" -#: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:36 +#: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:28 msgid "Run times" msgstr "执行次数" @@ -2055,13 +2292,7 @@ msgstr "执行历史" msgid "F/S/T" msgstr "失败/成功/总" -#: ops/templates/ops/adhoc_history.html:58 -#: ops/templates/ops/adhoc_history_detail.html:57 -#: ops/templates/ops/task_adhoc.html:58 ops/templates/ops/task_history.html:64 -msgid "Version" -msgstr "版本" - -#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:140 +#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:135 msgid "Run history detail" msgstr "执行历史详情" @@ -2137,12 +2368,12 @@ msgid "Finished" msgstr "结束" #: ops/templates/ops/task_adhoc.html:19 ops/templates/ops/task_detail.html:20 -#: ops/templates/ops/task_history.html:19 ops/views/adhoc.py:75 +#: ops/templates/ops/task_history.html:19 ops/views/adhoc.py:70 msgid "Task detail" msgstr "任务详情" #: ops/templates/ops/task_adhoc.html:22 ops/templates/ops/task_detail.html:23 -#: ops/templates/ops/task_history.html:22 ops/views/adhoc.py:88 +#: ops/templates/ops/task_history.html:22 ops/views/adhoc.py:83 msgid "Task versions" msgstr "任务各版本" @@ -2164,24 +2395,20 @@ msgstr "版本" msgid "Total versions" msgstr "版本数量" -#: ops/templates/ops/task_detail.html:72 -msgid "Latest version" -msgstr "最新版本" - #: ops/templates/ops/task_detail.html:92 msgid "Contents" msgstr "内容" -#: ops/templates/ops/task_list.html:37 +#: ops/templates/ops/task_list.html:29 msgid "Versions" msgstr "版本" -#: ops/templates/ops/task_list.html:71 +#: ops/templates/ops/task_list.html:63 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:52 msgid "Run" msgstr "执行" -#: ops/templates/ops/task_list.html:125 +#: ops/templates/ops/task_list.html:117 msgid "Task start: " msgstr "任务开始: " @@ -2189,17 +2416,17 @@ msgstr "任务开始: " msgid "Update task content: {}" msgstr "更新任务内容: {}" -#: ops/views/adhoc.py:49 ops/views/adhoc.py:74 ops/views/adhoc.py:87 -#: ops/views/adhoc.py:100 ops/views/adhoc.py:113 ops/views/adhoc.py:126 -#: ops/views/adhoc.py:139 ops/views/command.py:43 ops/views/command.py:67 +#: ops/views/adhoc.py:44 ops/views/adhoc.py:69 ops/views/adhoc.py:82 +#: ops/views/adhoc.py:95 ops/views/adhoc.py:108 ops/views/adhoc.py:121 +#: ops/views/adhoc.py:134 ops/views/command.py:43 ops/views/command.py:67 msgid "Ops" msgstr "作业中心" -#: ops/views/adhoc.py:50 templates/_nav.html:66 +#: ops/views/adhoc.py:45 templates/_nav.html:66 msgid "Task list" msgstr "任务列表" -#: ops/views/adhoc.py:101 +#: ops/views/adhoc.py:96 msgid "Task run history" msgstr "执行历史" @@ -2216,7 +2443,7 @@ msgstr "组织管理" #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:75 #: perms/templates/perms/asset_permission_list.html:122 templates/_nav.html:14 -#: users/forms.py:273 users/models/group.py:26 users/models/user.py:61 +#: users/forms.py:253 users/models/group.py:26 users/models/user.py:60 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:213 #: users/templates/users/user_list.html:26 @@ -2234,7 +2461,7 @@ msgstr "资产和节点至少选一个" #: perms/models.py:36 perms/models.py:89 #: perms/templates/perms/asset_permission_detail.html:90 -#: users/models/user.py:93 users/templates/users/user_detail.html:107 +#: users/models/user.py:92 users/templates/users/user_detail.html:107 #: users/templates/users/user_profile.html:116 msgid "Date expired" msgstr "失效日期" @@ -2435,7 +2662,7 @@ msgstr "SMTP密码" msgid "Some provider use token except password" msgstr "一些邮件提供商需要输入的是Token" -#: settings/forms.py:86 settings/forms.py:124 +#: settings/forms.py:86 settings/forms.py:125 msgid "Use SSL" msgstr "使用SSL" @@ -2467,20 +2694,20 @@ msgstr "用户OU" msgid "Use | split User OUs" msgstr "使用|分隔各OU" -#: settings/forms.py:111 +#: settings/forms.py:112 msgid "User search filter" msgstr "用户过滤器" -#: settings/forms.py:112 +#: settings/forms.py:113 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" -#: settings/forms.py:115 +#: settings/forms.py:116 msgid "User attr map" msgstr "LDAP属性映射" -#: settings/forms.py:117 +#: settings/forms.py:118 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -2488,43 +2715,43 @@ msgstr "" "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的属性" -#: settings/forms.py:126 +#: settings/forms.py:127 msgid "Enable LDAP auth" msgstr "启用LDAP认证" -#: settings/forms.py:135 +#: settings/forms.py:136 msgid "All" msgstr "全部" -#: settings/forms.py:136 +#: settings/forms.py:137 msgid "Auto" msgstr "自动" -#: settings/forms.py:143 +#: settings/forms.py:144 msgid "Password auth" msgstr "密码认证" -#: settings/forms.py:146 +#: settings/forms.py:147 msgid "Public key auth" msgstr "密钥认证" -#: settings/forms.py:149 +#: settings/forms.py:150 msgid "Heartbeat interval" msgstr "心跳间隔" -#: settings/forms.py:153 +#: settings/forms.py:154 msgid "List sort by" msgstr "资产列表排序" -#: settings/forms.py:156 +#: settings/forms.py:157 msgid "List page size" msgstr "资产分页每页数量" -#: settings/forms.py:159 +#: settings/forms.py:160 msgid "Session keep duration" msgstr "会话保留时长" -#: settings/forms.py:160 +#: settings/forms.py:161 msgid "" "Units: days, Session, record, command will be delete if more than duration, " "only in database" @@ -2532,54 +2759,54 @@ msgstr "" "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" "受影响)" -#: settings/forms.py:164 +#: settings/forms.py:165 msgid "Telnet login regex" msgstr "Telnet 成功正则表达式" -#: settings/forms.py:165 +#: settings/forms.py:166 msgid "ex: Last\\s*login|success|成功" msgstr "" "登录telnet服务器成功后的提示正则表达式,如: Last\\s*login|success|成功 " -#: settings/forms.py:176 +#: settings/forms.py:177 msgid "MFA Secondary certification" msgstr "MFA 二次认证" -#: settings/forms.py:178 +#: settings/forms.py:179 msgid "" "After opening, the user login must use MFA secondary authentication (valid " "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: settings/forms.py:184 +#: settings/forms.py:185 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: settings/forms.py:188 +#: settings/forms.py:189 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: settings/forms.py:190 +#: settings/forms.py:191 msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" -#: settings/forms.py:196 +#: settings/forms.py:197 msgid "Connection max idle time" msgstr "SSH最大空闲时间" -#: settings/forms.py:198 +#: settings/forms.py:199 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" -#: settings/forms.py:204 +#: settings/forms.py:205 msgid "Password expiration time" msgstr "密码过期时间" -#: settings/forms.py:207 +#: settings/forms.py:208 msgid "" "Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " @@ -2589,55 +2816,50 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: settings/forms.py:216 +#: settings/forms.py:217 msgid "Password minimum length" msgstr "密码最小长度 " -#: settings/forms.py:220 +#: settings/forms.py:221 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: settings/forms.py:222 +#: settings/forms.py:223 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: settings/forms.py:227 +#: settings/forms.py:228 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: settings/forms.py:228 +#: settings/forms.py:229 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: settings/forms.py:233 +#: settings/forms.py:234 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: settings/forms.py:234 +#: settings/forms.py:235 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: settings/forms.py:239 +#: settings/forms.py:240 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: settings/forms.py:240 +#: settings/forms.py:241 msgid "" "After opening, the user password changes and resets must contain special " "characters" msgstr "开启后,用户密码修改、重置必须包含特殊字符" -#: settings/models.py:33 users/models/authentication.py:54 -#: users/templates/users/user_detail.html:96 -msgid "Enabled" -msgstr "启用" - #: settings/models.py:126 users/templates/users/reset_password.html:68 #: users/templates/users/user_profile.html:20 msgid "Setting" @@ -2832,7 +3054,7 @@ msgstr "文档" msgid "Commercial support" msgstr "商业支持" -#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:141 +#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:121 #: users/templates/users/_user.html:43 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 @@ -2926,7 +3148,7 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43 #: users/views/group.py:59 users/views/group.py:75 users/views/group.py:91 -#: users/views/login.py:360 users/views/user.py:68 users/views/user.py:83 +#: users/views/login.py:151 users/views/user.py:68 users/views/user.py:83 #: users/views/user.py:113 users/views/user.py:194 users/views/user.py:355 #: users/views/user.py:405 users/views/user.py:445 msgid "Users" @@ -3374,87 +3596,11 @@ msgid "" "You should use your ssh client tools connect terminal: {}

    {}" msgstr "你可以使用ssh客户端工具连接终端" -#: users/api/auth.py:40 users/templates/users/login.html:52 -#: users/templates/users/new_login.html:71 -msgid "Log in frequently and try again later" -msgstr "登录频繁, 稍后重试" - -#: users/api/auth.py:67 -msgid "The user {} password has expired, please update." -msgstr "用户 {} 密码已经过期,请更新。" - -#: users/api/auth.py:92 -msgid "Please carry seed value and conduct MFA secondary certification" -msgstr "请携带seed值, 进行MFA二次认证" - -#: users/api/auth.py:204 -msgid "Please verify the user name and password first" -msgstr "请先进行用户名和密码验证" - -#: users/api/auth.py:216 -msgid "MFA certification failed" -msgstr "MFA认证失败" - #: users/api/user.py:145 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/authentication.py:53 -msgid "Invalid signature header. No credentials provided." -msgstr "" - -#: users/authentication.py:56 -msgid "Invalid signature header. Signature string should not contain spaces." -msgstr "" - -#: users/authentication.py:63 -msgid "Invalid signature header. Format like AccessKeyId:Signature" -msgstr "" - -#: users/authentication.py:67 -msgid "" -"Invalid signature header. Signature string should not contain invalid " -"characters." -msgstr "" - -#: users/authentication.py:87 users/authentication.py:103 -msgid "Invalid signature." -msgstr "" - -#: users/authentication.py:94 -msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" -msgstr "" - -#: users/authentication.py:99 -msgid "Expired, more than 15 minutes" -msgstr "" - -#: users/authentication.py:106 -msgid "User disabled." -msgstr "用户已禁用" - -#: users/authentication.py:121 -msgid "Invalid token header. No credentials provided." -msgstr "" - -#: users/authentication.py:124 -msgid "Invalid token header. Sign string should not contain spaces." -msgstr "" - -#: users/authentication.py:131 -msgid "" -"Invalid token header. Sign string should not contain invalid characters." -msgstr "" - -#: users/authentication.py:142 -msgid "Invalid token or cache refreshed." -msgstr "" - -#: users/forms.py:41 -msgid "MFA code" -msgstr "MFA 验证码" - -#: users/forms.py:52 users/models/user.py:65 +#: users/forms.py:32 users/models/user.py:64 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:25 @@ -3462,31 +3608,31 @@ msgstr "MFA 验证码" msgid "Role" msgstr "角色" -#: users/forms.py:55 users/forms.py:220 +#: users/forms.py:35 users/forms.py:200 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:56 users/forms.py:221 +#: users/forms.py:36 users/forms.py:201 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:57 +#: users/forms.py:37 msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:71 users/templates/users/user_detail.html:221 +#: users/forms.py:51 users/templates/users/user_detail.html:221 msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:105 users/forms.py:235 +#: users/forms.py:85 users/forms.py:215 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:109 users/forms.py:239 users/serializers/v1.py:38 +#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:38 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:147 +#: users/forms.py:127 msgid "" "Tip: when enabled, you will enter the MFA binding process the next time you " "log in. you can also directly bind in \"personal information -> quick " @@ -3495,11 +3641,11 @@ msgstr "" "提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修" "改->更改MFA设置)中直接绑定!" -#: users/forms.py:157 +#: users/forms.py:137 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全." -#: users/forms.py:167 +#: users/forms.py:147 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -3508,163 +3654,101 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:174 users/templates/users/first_login.html:48 +#: users/forms.py:154 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:130 msgid "Finish" msgstr "完成" -#: users/forms.py:180 +#: users/forms.py:160 msgid "Old password" msgstr "原来密码" -#: users/forms.py:185 +#: users/forms.py:165 msgid "New password" msgstr "新密码" -#: users/forms.py:190 +#: users/forms.py:170 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:200 +#: users/forms.py:180 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:208 +#: users/forms.py:188 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:218 +#: users/forms.py:198 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:222 +#: users/forms.py:202 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:250 users/models/user.py:85 -#: users/templates/users/first_login.html:42 -#: users/templates/users/user_password_update.html:46 -#: users/templates/users/user_profile.html:68 -#: users/templates/users/user_profile_update.html:43 -#: users/templates/users/user_pubkey_update.html:43 -msgid "Public key" -msgstr "ssh公钥" - -#: users/forms.py:256 users/forms.py:261 users/forms.py:307 +#: users/forms.py:236 users/forms.py:241 users/forms.py:287 #: xpack/plugins/orgs/forms.py:30 msgid "Select users" msgstr "选择用户" -#: users/models/authentication.py:39 -msgid "Private Token" -msgstr "ssh密钥" - -#: users/models/authentication.py:53 users/templates/users/user_detail.html:98 -msgid "Disabled" -msgstr "禁用" - -#: users/models/authentication.py:55 users/models/authentication.py:65 -msgid "-" -msgstr "" - -#: users/models/authentication.py:66 -msgid "Username/password check failed" -msgstr "用户名/密码 校验失败" - -#: users/models/authentication.py:67 -msgid "MFA authentication failed" -msgstr "MFA 认证失败" - -#: users/models/authentication.py:68 -msgid "Username does not exist" -msgstr "用户名不存在" - -#: users/models/authentication.py:69 -msgid "Password expired" -msgstr "密码过期" - -#: users/models/authentication.py:74 xpack/plugins/cloud/models.py:164 -#: xpack/plugins/cloud/models.py:178 -msgid "Failed" -msgstr "失败" - -#: users/models/authentication.py:78 -msgid "Login type" -msgstr "登录方式" - -#: users/models/authentication.py:79 -msgid "Login ip" -msgstr "登录IP" - -#: users/models/authentication.py:80 -msgid "Login city" -msgstr "登录城市" - -#: users/models/authentication.py:81 -msgid "User agent" -msgstr "Agent" - -#: users/models/authentication.py:85 -msgid "Date login" -msgstr "登录日期" - -#: users/models/user.py:32 users/models/user.py:437 +#: users/models/user.py:31 users/models/user.py:453 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:34 +#: users/models/user.py:33 msgid "Application" msgstr "应用程序" -#: users/models/user.py:37 users/templates/users/user_profile.html:92 +#: users/models/user.py:36 users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:159 #: users/templates/users/user_profile.html:162 msgid "Disable" msgstr "禁用" -#: users/models/user.py:38 users/templates/users/user_profile.html:90 +#: users/models/user.py:37 users/templates/users/user_profile.html:90 #: users/templates/users/user_profile.html:166 msgid "Enable" msgstr "启用" -#: users/models/user.py:39 users/templates/users/user_profile.html:88 +#: users/models/user.py:38 users/templates/users/user_profile.html:88 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:57 users/templates/users/user_detail.html:71 +#: users/models/user.py:56 users/templates/users/user_detail.html:71 #: users/templates/users/user_profile.html:59 msgid "Email" msgstr "邮件" -#: users/models/user.py:68 +#: users/models/user.py:67 msgid "Avatar" msgstr "头像" -#: users/models/user.py:71 users/templates/users/user_detail.html:82 +#: users/models/user.py:70 users/templates/users/user_detail.html:82 msgid "Wechat" msgstr "微信" -#: users/models/user.py:100 users/templates/users/user_detail.html:103 +#: users/models/user.py:99 users/templates/users/user_detail.html:103 #: users/templates/users/user_list.html:27 #: users/templates/users/user_profile.html:100 msgid "Source" msgstr "用户来源" -#: users/models/user.py:104 +#: users/models/user.py:103 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:128 users/templates/users/user_update.html:22 -#: users/views/login.py:254 users/views/login.py:313 users/views/user.py:418 +#: users/models/user.py:129 users/templates/users/user_update.html:22 +#: users/views/login.py:45 users/views/login.py:104 users/views/user.py:418 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/models/user.py:440 +#: users/models/user.py:456 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/serializers/v2.py:40 +#: users/serializers/v2.py:34 msgid "name not unique" msgstr "名称重复" @@ -3880,7 +3964,7 @@ msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry" msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry" #: users/templates/users/reset_password.html:46 -#: users/templates/users/user_detail.html:373 users/utils.py:78 +#: users/templates/users/user_detail.html:373 users/utils.py:77 msgid "Reset password" msgstr "重置密码" @@ -4204,11 +4288,11 @@ msgstr "新的公钥已设置成功,请下载对应的私钥" msgid "Update user" msgstr "更新用户" -#: users/utils.py:39 +#: users/utils.py:38 msgid "Create account successfully" msgstr "创建账户成功" -#: users/utils.py:41 +#: users/utils.py:40 #, python-format msgid "" "\n" @@ -4253,7 +4337,7 @@ msgstr "" "
    \n" " " -#: users/utils.py:80 +#: users/utils.py:79 #, python-format msgid "" "\n" @@ -4297,11 +4381,11 @@ msgstr "" "
    \n" " " -#: users/utils.py:111 +#: users/utils.py:110 msgid "Security notice" msgstr "安全通知" -#: users/utils.py:113 +#: users/utils.py:112 #, python-format msgid "" "\n" @@ -4350,11 +4434,11 @@ msgstr "" "
    \n" " " -#: users/utils.py:149 +#: users/utils.py:148 msgid "SSH Key Reset" msgstr "重置ssh密钥" -#: users/utils.py:151 +#: users/utils.py:150 #, python-format msgid "" "\n" @@ -4379,15 +4463,15 @@ msgstr "" "
    \n" " " -#: users/utils.py:184 +#: users/utils.py:183 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:186 +#: users/utils.py:185 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:199 +#: users/utils.py:198 msgid "Password or SSH public key invalid" msgstr "密码或密钥不合法" @@ -4403,56 +4487,40 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:80 -msgid "Please enable cookies and try again." -msgstr "设置你的浏览器支持cookie" - -#: users/views/login.py:202 users/views/user.py:532 users/views/user.py:557 -msgid "MFA code invalid, or ntp sync server time" -msgstr "MFA验证码不正确,或者服务器端时间不对" - -#: users/views/login.py:234 -msgid "Logout success" -msgstr "退出登录成功" - -#: users/views/login.py:235 -msgid "Logout success, return login page" -msgstr "退出登录成功,返回到登录页面" - -#: users/views/login.py:251 +#: users/views/login.py:42 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:267 +#: users/views/login.py:58 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:268 +#: users/views/login.py:59 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:281 +#: users/views/login.py:72 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:282 +#: users/views/login.py:73 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:297 users/views/login.py:316 +#: users/views/login.py:88 users/views/login.py:107 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:309 +#: users/views/login.py:100 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:322 users/views/user.py:128 users/views/user.py:428 +#: users/views/login.py:113 users/views/user.py:128 users/views/user.py:428 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:360 +#: users/views/login.py:151 msgid "First login" msgstr "首次登录" diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index 7e21d156d..98ee94acc 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -4,11 +4,16 @@ from .ansible.inventory import BaseInventory from assets.utils import get_assets_by_id_list, get_system_user_by_id +from common.utils import get_logger + __all__ = [ 'JMSInventory' ] +logger = get_logger(__file__) + + class JMSInventory(BaseInventory): """ JMS Inventory is the manager with jumpserver assets, so you can @@ -18,7 +23,7 @@ class JMSInventory(BaseInventory): """ :param host_id_list: ["test1", ] :param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同 - :param run_as: 是否统一使用某个系统用户去执行 + :param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username) :param become_info: 是否become成某个用户去执行 """ self.assets = assets @@ -33,8 +38,8 @@ class JMSInventory(BaseInventory): host_list.append(info) if run_as: - run_user_info = self.get_run_user_info() for host in host_list: + run_user_info = self.get_run_user_info(host) host.update(run_user_info) if become_info: @@ -69,12 +74,20 @@ class JMSInventory(BaseInventory): info["groups"].append("domain_"+asset.domain.name) return info - def get_run_user_info(self): - system_user = self.run_as - if not system_user: + def get_run_user_info(self, host): + from assets.backends.multi import AssetUserManager + + if not self.run_as: + return {} + + try: + asset = self.assets.get(id=host.get('id')) + run_user = AssetUserManager.get(self.run_as, asset) + except Exception as e: + logger.error(e, exc_info=True) return {} else: - return system_user._to_secret_json() + return run_user._to_secret_json() @staticmethod def make_proxy_command(asset): diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index e9d264ed1..f4d67b945 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -149,7 +149,7 @@ class AdHoc(models.Model): _options: ansible options, more see ops.ansible.runner.Options _hosts: ["hostname1", "hostname2"], hostname must be unique key of cmdb run_as_admin: if true, then need get every host admin user run it, because every host may be have different admin user, so we choise host level - run_as: if not run as admin, it run it as a system/common user from cmdb + run_as: username(Add the uniform AssetUserManager and change it to username) _become: May be using become [sudo, su] options. {method: "sudo", user: "user", pass: "pass"] pattern: Even if we set _hosts, We only use that to make inventory, We also can set `patter` to run task on match hosts """ @@ -161,7 +161,7 @@ class AdHoc(models.Model): _hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2'] hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host")) run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin')) - run_as = models.ForeignKey('assets.SystemUser', null=True, on_delete=models.CASCADE) + run_as = models.CharField(max_length=64, default='', null=True, verbose_name=_('Username')) _become = models.CharField(max_length=1024, default='', verbose_name=_("Become")) created_by = models.CharField(max_length=64, default='', null=True, verbose_name=_('Create by')) date_created = models.DateTimeField(auto_now_add=True, db_index=True)