diff --git a/apps/applications/api/__init__.py b/apps/applications/api/__init__.py index a11b4966a..3258614fd 100644 --- a/apps/applications/api/__init__.py +++ b/apps/applications/api/__init__.py @@ -1,4 +1,4 @@ from .application import * -from .application_user import * +from .account import * from .mixin import * from .remote_app import * diff --git a/apps/applications/api/account.py b/apps/applications/api/account.py new file mode 100644 index 000000000..87308e44c --- /dev/null +++ b/apps/applications/api/account.py @@ -0,0 +1,74 @@ +# coding: utf-8 +# + +from django_filters import rest_framework as filters +from django.conf import settings +from django.db.models import F, Value, CharField +from django.db.models.functions import Concat +from django.http import Http404 + +from common.drf.filters import BaseFilterSet +from common.drf.api import JMSModelViewSet +from common.utils import unique +from perms.models import ApplicationPermission +from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify +from .. import serializers + + +class AccountFilterSet(BaseFilterSet): + username = filters.CharFilter(field_name='username') + app = filters.CharFilter(field_name='applications', lookup_expr='exact') + app_name = filters.CharFilter(field_name='app_name', lookup_expr='exact') + app_type = filters.CharFilter(field_name='app_type', lookup_expr='exact') + app_category = filters.CharFilter(field_name='app_category', lookup_expr='exact') + + class Meta: + model = ApplicationPermission + fields = [] + + +class ApplicationAccountViewSet(JMSModelViewSet): + permission_classes = (IsOrgAdmin, ) + search_fields = ['username', 'app_name'] + filterset_class = AccountFilterSet + filterset_fields = ['username', 'app_name', 'app_type', 'app_category'] + serializer_class = serializers.ApplicationAccountSerializer + + http_method_names = ['get', 'put', 'patch', 'options'] + + def get_queryset(self): + queryset = ApplicationPermission.objects.all() \ + .annotate(uid=Concat( + 'applications', Value('_'), 'system_users', output_field=CharField() + )) \ + .annotate(systemuser=F('system_users')) \ + .annotate(systemuser_display=F('system_users__name')) \ + .annotate(username=F('system_users__username')) \ + .annotate(password=F('system_users__password')) \ + .annotate(app=F('applications')) \ + .annotate(app_name=F("applications__name")) \ + .annotate(app_category=F("applications__category")) \ + .annotate(app_type=F("applications__type"))\ + .values('username', 'password', 'systemuser', 'systemuser_display', + 'app', 'app_name', 'app_category', 'app_type', 'uid') + return queryset + + def get_object(self): + obj = self.get_queryset().filter( + uid=self.kwargs['pk'] + ).first() + if not obj: + raise Http404() + return obj + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + queryset_list = unique(queryset, key=lambda x: (x['app'], x['systemuser'])) + return queryset_list + + +class ApplicationAccountSecretViewSet(ApplicationAccountViewSet): + serializer_class = serializers.ApplicationAccountSecretSerializer + permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] + http_method_names = ['get', 'options'] + diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index 009376333..235f0e789 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -1,7 +1,11 @@ # coding: utf-8 # -from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins.api import OrgBulkModelViewSet +from rest_framework.decorators import action +from rest_framework.response import Response + +from common.tree import TreeNodeSerializer from ..hands import IsOrgAdminOrAppUser from .. import serializers from ..models import Application @@ -19,4 +23,15 @@ class ApplicationViewSet(OrgBulkModelViewSet): } search_fields = ('name', 'type', 'category') permission_classes = (IsOrgAdminOrAppUser,) - serializer_class = serializers.ApplicationSerializer + serializer_classes = { + 'default': serializers.ApplicationSerializer, + 'get_tree': TreeNodeSerializer + } + + @action(methods=['GET'], detail=False, url_path='tree') + def get_tree(self, request, *args, **kwargs): + show_count = request.query_params.get('show_count', '1') == '1' + queryset = self.filter_queryset(self.get_queryset()) + tree_nodes = Application.create_tree_nodes(queryset, show_count=show_count) + serializer = self.get_serializer(tree_nodes, many=True) + return Response(serializer.data) diff --git a/apps/applications/api/application_user.py b/apps/applications/api/application_user.py deleted file mode 100644 index 93bbc29a5..000000000 --- a/apps/applications/api/application_user.py +++ /dev/null @@ -1,55 +0,0 @@ -# coding: utf-8 -# - -from rest_framework import generics -from django.conf import settings - -from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify -from .. import serializers -from ..models import Application, ApplicationUser -from perms.models import ApplicationPermission - - -class ApplicationUserListApi(generics.ListAPIView): - permission_classes = (IsOrgAdmin, ) - filterset_fields = ('name', 'username') - search_fields = filterset_fields - serializer_class = serializers.ApplicationUserSerializer - _application = None - - @property - def application(self): - if self._application is None: - app_id = self.request.query_params.get('application_id') - if app_id: - self._application = Application.objects.get(id=app_id) - return self._application - - def get_serializer_context(self): - context = super().get_serializer_context() - context.update({ - 'application': self.application - }) - return context - - def get_queryset(self): - queryset = ApplicationUser.objects.none() - if not self.application: - return queryset - system_user_ids = ApplicationPermission.objects.filter(applications=self.application)\ - .values_list('system_users', flat=True) - if not system_user_ids: - return queryset - queryset = ApplicationUser.objects.filter(id__in=system_user_ids) - return queryset - - -class ApplicationUserAuthInfoListApi(ApplicationUserListApi): - serializer_class = serializers.ApplicationUserWithAuthInfoSerializer - http_method_names = ['get'] - permission_classes = [IsOrgAdminOrAppUser] - - def get_permissions(self): - if settings.SECURITY_VIEW_AUTH_NEED_MFA: - self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] - return super().get_permissions() diff --git a/apps/applications/api/mixin.py b/apps/applications/api/mixin.py index f79902b72..cd603199b 100644 --- a/apps/applications/api/mixin.py +++ b/apps/applications/api/mixin.py @@ -1,89 +1,52 @@ -from orgs.models import Organization +from django.utils.translation import ugettext as _ +from common.tree import TreeNode +from orgs.models import Organization +from ..models import Application __all__ = ['SerializeApplicationToTreeNodeMixin'] class SerializeApplicationToTreeNodeMixin: - - @staticmethod - def _serialize_db(db): - return { - 'id': db.id, - 'name': db.name, - 'title': db.name, - 'pId': '', - 'open': False, - 'iconSkin': 'database', - 'meta': {'type': 'database_app'} - } - - @staticmethod - def _serialize_remote_app(remote_app): - return { - 'id': remote_app.id, - 'name': remote_app.name, - 'title': remote_app.name, - 'pId': '', - 'open': False, - 'isParent': False, - 'iconSkin': 'chrome', - 'meta': {'type': 'remote_app'} - } - - @staticmethod - def _serialize_cloud(cloud): - return { - 'id': cloud.id, - 'name': cloud.name, - 'title': cloud.name, - 'pId': '', - 'open': False, - 'isParent': False, - 'iconSkin': 'k8s', - 'meta': {'type': 'k8s_app'} - } - - def _serialize_application(self, application): - method_name = f'_serialize_{application.category}' - data = getattr(self, method_name)(application) - data.update({ - 'pId': application.org.id, - 'org_name': application.org_name - }) - return data - - def serialize_applications(self, applications): - data = [self._serialize_application(application) for application in applications] - return data - - @staticmethod - def _serialize_organization(org): - return { - 'id': org.id, - 'name': org.name, - 'title': org.name, - 'pId': '', - 'open': True, - 'isParent': True, - 'meta': { - 'type': 'node' - } - } - - def serialize_organizations(self, organizations): - data = [self._serialize_organization(org) for org in organizations] - return data - @staticmethod def filter_organizations(applications): organization_ids = set(applications.values_list('org_id', flat=True)) organizations = [Organization.get_instance(org_id) for org_id in organization_ids] return organizations + @staticmethod + def create_root_node(): + name = _('My applications') + node = TreeNode(**{ + 'id': 'applications', + 'name': name, + 'title': name, + 'pId': '', + 'open': True, + 'isParent': True, + 'meta': { + 'type': 'root' + } + }) + return node + def serialize_applications_with_org(self, applications): + root_node = self.create_root_node() + tree_nodes = [root_node] organizations = self.filter_organizations(applications) - data_organizations = self.serialize_organizations(organizations) - data_applications = self.serialize_applications(applications) - data = data_organizations + data_applications - return data + + for i, org in enumerate(organizations): + # 组织节点 + org_node = org.as_tree_node(pid=root_node.id) + tree_nodes.append(org_node) + org_applications = applications.filter(org_id=org.id) + count = org_applications.count() + org_node.name += '({})'.format(count) + + # 各应用节点 + apps_nodes = Application.create_tree_nodes( + queryset=org_applications, root_node=org_node, + show_empty=False + ) + tree_nodes += apps_nodes + return tree_nodes diff --git a/apps/applications/const.py b/apps/applications/const.py index 151a65b28..ee150da31 100644 --- a/apps/applications/const.py +++ b/apps/applications/const.py @@ -1,11 +1,10 @@ # coding: utf-8 # - from django.db.models import TextChoices from django.utils.translation import ugettext_lazy as _ -class ApplicationCategoryChoices(TextChoices): +class AppCategory(TextChoices): db = 'db', _('Database') remote_app = 'remote_app', _('Remote app') cloud = 'cloud', 'Cloud' @@ -15,7 +14,7 @@ class ApplicationCategoryChoices(TextChoices): return dict(cls.choices).get(category, '') -class ApplicationTypeChoices(TextChoices): +class AppType(TextChoices): # db category mysql = 'mysql', 'MySQL' oracle = 'oracle', 'Oracle' @@ -31,19 +30,38 @@ class ApplicationTypeChoices(TextChoices): # cloud category k8s = 'k8s', 'Kubernetes' + @classmethod + def category_types_mapper(cls): + return { + AppCategory.db: [cls.mysql, cls.oracle, cls.pgsql, cls.mariadb], + AppCategory.remote_app: [cls.chrome, cls.mysql_workbench, cls.vmware_client, cls.custom], + AppCategory.cloud: [cls.k8s] + } + + @classmethod + def type_category_mapper(cls): + mapper = {} + for category, tps in cls.category_types_mapper().items(): + for tp in tps: + mapper[tp] = category + return mapper + @classmethod def get_label(cls, tp): return dict(cls.choices).get(tp, '') @classmethod def db_types(cls): - return [cls.mysql.value, cls.oracle.value, cls.pgsql.value, cls.mariadb.value] + return [tp.value for tp in cls.category_types_mapper()[AppCategory.db]] @classmethod def remote_app_types(cls): - return [cls.chrome.value, cls.mysql_workbench.value, cls.vmware_client.value, cls.custom.value] + return [tp.value for tp in cls.category_types_mapper()[AppCategory.remote_app]] @classmethod def cloud_types(cls): - return [cls.k8s.value] + return [tp.value for tp in cls.category_types_mapper()[AppCategory.cloud]] + + + diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index c9374181d..a7964451d 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -1,19 +1,170 @@ from django.db import models +from django.db.models import Count from django.utils.translation import ugettext_lazy as _ from orgs.mixins.models import OrgModelMixin from common.mixins import CommonModelMixin +from common.tree import TreeNode from assets.models import Asset, SystemUser from .. import const -class Application(CommonModelMixin, OrgModelMixin): +class ApplicationTreeNodeMixin: + id: str + name: str + type: str + category: str + + @classmethod + def create_choice_node(cls, c, pid, tp, opened=False, counts=None, + show_empty=True, show_count=True): + count = counts.get(c.name, 0) + if count == 0 and not show_empty: + return None + name = c.name + label = c.label + if count is not None and show_count: + label = '{} ({})'.format(label, count) + data = { + 'id': name, + 'name': label, + 'title': label, + 'pId': pid, + 'isParent': bool(count), + 'open': opened, + 'iconSkin': '', + 'meta': { + 'type': tp, + } + } + return TreeNode(**data) + + @classmethod + def create_root_tree_node(cls, queryset, show_count=True): + count = queryset.count() if show_count else None + root_id = 'applications' + root_name = _('Applications') + if count is not None and show_count: + root_name = '{} ({})'.format(root_name, count) + node = TreeNode(**{ + 'id': root_id, + 'name': root_name, + 'title': root_name, + 'pId': '', + 'isParent': True, + 'open': True, + 'iconSkin': '', + 'meta': { + 'type': 'applications_root', + } + }) + return node + + @classmethod + def create_category_tree_nodes(cls, root_node, counts=None, show_empty=True, show_count=True): + nodes = [] + categories = const.AppType.category_types_mapper().keys() + for category in categories: + node = cls.create_choice_node( + category, pid=root_node.id, tp='category', + counts=counts, opened=True, show_empty=show_empty, + show_count=show_count + ) + if not node: + continue + nodes.append(node) + return nodes + + @classmethod + def create_types_tree_nodes(cls, counts, show_empty=True, show_count=True): + nodes = [] + type_category_mapper = const.AppType.type_category_mapper() + for tp in const.AppType.type_category_mapper().keys(): + category = type_category_mapper.get(tp) + node = cls.create_choice_node( + tp, pid=category.name, tp='type', counts=counts, + show_empty=show_empty, show_count=show_count + ) + if not node: + continue + nodes.append(node) + return nodes + + @staticmethod + def get_tree_node_counts(queryset): + counts = {'applications': queryset.count()} + category_counts = queryset.values('category') \ + .annotate(count=Count('id')) \ + .order_by() + for item in category_counts: + counts[item['category']] = item['count'] + + type_counts = queryset.values('type') \ + .annotate(count=Count('id')) \ + .order_by() + for item in type_counts: + counts[item['type']] = item['count'] + return counts + + @classmethod + def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True): + counts = cls.get_tree_node_counts(queryset) if show_count else {} + tree_nodes = [] + + # 根节点有可能是组织名称 + if root_node is None: + root_node = cls.create_root_tree_node(queryset, show_count=show_count) + tree_nodes.append(root_node) + + # 类别的节点 + tree_nodes += cls.create_category_tree_nodes( + root_node, counts, show_empty=show_empty, + show_count=show_count + ) + + # 类型的节点 + tree_nodes += cls.create_types_tree_nodes( + counts, show_empty=show_empty, show_count=show_count + ) + + # 应用的节点 + for app in queryset: + tree_nodes.append(app.as_tree_node()) + return tree_nodes + + def as_tree_node(self): + icon_skin_category_mapper = { + 'remote_app': 'chrome', + 'db': 'database', + 'cloud': 'cloud' + } + icon_skin = icon_skin_category_mapper.get(self.category, 'file') + node = TreeNode(**{ + 'id': str(self.id), + 'name': self.name, + 'title': self.name, + 'pId': str(self.type), + 'isParent': False, + 'open': False, + 'iconSkin': icon_skin, + 'meta': { + 'type': 'application', + 'data': { + 'category': self.category, + 'type': self.type, + } + } + }) + return node + + +class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): name = models.CharField(max_length=128, verbose_name=_('Name')) category = models.CharField( - max_length=16, choices=const.ApplicationCategoryChoices.choices, verbose_name=_('Category') + max_length=16, choices=const.AppCategory.choices, verbose_name=_('Category') ) type = models.CharField( - max_length=16, choices=const.ApplicationTypeChoices.choices, verbose_name=_('Type') + max_length=16, choices=const.AppType.choices, verbose_name=_('Type') ) domain = models.ForeignKey( 'assets.Domain', null=True, blank=True, related_name='applications', @@ -35,7 +186,7 @@ class Application(CommonModelMixin, OrgModelMixin): @property def category_remote_app(self): - return self.category == const.ApplicationCategoryChoices.remote_app.value + return self.category == const.AppCategory.remote_app.value def get_rdp_remote_app_setting(self): from applications.serializers.attrs import get_serializer_class_by_application_type diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index 39f7172a0..8e0c414a1 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -6,12 +6,12 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import MethodSerializer from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping -from assets.serializers import SystemUserSerializer from .. import models +from .. import const __all__ = [ 'ApplicationSerializer', 'ApplicationSerializerMixin', - 'ApplicationUserSerializer', 'ApplicationUserWithAuthInfoSerializer' + 'ApplicationAccountSerializer', 'ApplicationAccountSecretSerializer' ] @@ -52,9 +52,8 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri model = models.Application fields_mini = ['id', 'name'] fields_small = fields_mini + [ - 'category', 'category_display', 'type', 'type_display', 'attrs', - 'date_created', 'date_updated', - 'created_by', 'comment' + 'category', 'category_display', 'type', 'type_display', + 'attrs', 'date_created', 'date_updated', 'created_by', 'comment' ] fields_fk = ['domain'] fields = fields_small + fields_fk @@ -68,41 +67,34 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri return _attrs -class ApplicationUserSerializer(SystemUserSerializer): - application_name = serializers.SerializerMethodField(label=_('Application name')) - application_category = serializers.SerializerMethodField(label=_('Application category')) - application_type = serializers.SerializerMethodField(label=_('Application type')) +class ApplicationAccountSerializer(serializers.Serializer): + username = serializers.ReadOnlyField(label=_("Username")) + password = serializers.CharField(write_only=True, label=_("Password")) + systemuser = serializers.ReadOnlyField(label=_('System user')) + systemuser_display = serializers.ReadOnlyField(label=_("System user display")) + app = serializers.ReadOnlyField(label=_('App')) + uid = serializers.ReadOnlyField(label=_("Union id")) + app_name = serializers.ReadOnlyField(label=_("Application name"), read_only=True) + app_category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True) + app_category_display = serializers.SerializerMethodField(label=_('Category')) + app_type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True) + app_type_display = serializers.SerializerMethodField(label=_('Type')) - class Meta(SystemUserSerializer.Meta): - model = models.ApplicationUser - fields_mini = [ - 'id', 'application_name', 'application_category', 'application_type', 'name', 'username' - ] - fields_small = fields_mini + [ - 'protocol', 'login_mode', 'login_mode_display', 'priority', - "username_same_with_user", 'comment', - ] - fields = fields_small - extra_kwargs = { - 'login_mode_display': {'label': _('Login mode display')}, - 'created_by': {'read_only': True}, - } + category_mapper = dict(const.AppCategory.choices) + type_mapper = dict(const.AppType.choices) - @property - def application(self): - return self.context['application'] + def create(self, validated_data): + pass - def get_application_name(self, obj): - return self.application.name + def update(self, instance, validated_data): + pass - def get_application_category(self, obj): - return self.application.get_category_display() + def get_app_category_display(self, obj): + return self.category_mapper.get(obj['app_category']) - def get_application_type(self, obj): - return self.application.get_type_display() + def get_app_type_display(self, obj): + return self.type_mapper.get(obj['app_type']) -class ApplicationUserWithAuthInfoSerializer(ApplicationUserSerializer): - - class Meta(ApplicationUserSerializer.Meta): - fields = ApplicationUserSerializer.Meta.fields + ['password', 'token'] +class ApplicationAccountSecretSerializer(ApplicationAccountSerializer): + password = serializers.CharField(write_only=False, label=_("Password")) diff --git a/apps/applications/serializers/attrs/attrs.py b/apps/applications/serializers/attrs/attrs.py index 18d420fd1..09b7f48b1 100644 --- a/apps/applications/serializers/attrs/attrs.py +++ b/apps/applications/serializers/attrs/attrs.py @@ -14,9 +14,9 @@ __all__ = [ # --------------------------------------------------- category_serializer_classes_mapping = { - const.ApplicationCategoryChoices.db.value: application_category.DBSerializer, - const.ApplicationCategoryChoices.remote_app.value: application_category.RemoteAppSerializer, - const.ApplicationCategoryChoices.cloud.value: application_category.CloudSerializer, + const.AppCategory.db.value: application_category.DBSerializer, + const.AppCategory.remote_app.value: application_category.RemoteAppSerializer, + const.AppCategory.cloud.value: application_category.CloudSerializer, } # define `attrs` field `type serializers mapping` @@ -24,17 +24,17 @@ category_serializer_classes_mapping = { type_serializer_classes_mapping = { # db - const.ApplicationTypeChoices.mysql.value: application_type.MySQLSerializer, - const.ApplicationTypeChoices.mariadb.value: application_type.MariaDBSerializer, - const.ApplicationTypeChoices.oracle.value: application_type.OracleSerializer, - const.ApplicationTypeChoices.pgsql.value: application_type.PostgreSerializer, + const.AppType.mysql.value: application_type.MySQLSerializer, + const.AppType.mariadb.value: application_type.MariaDBSerializer, + const.AppType.oracle.value: application_type.OracleSerializer, + const.AppType.pgsql.value: application_type.PostgreSerializer, # remote-app - const.ApplicationTypeChoices.chrome.value: application_type.ChromeSerializer, - const.ApplicationTypeChoices.mysql_workbench.value: application_type.MySQLWorkbenchSerializer, - const.ApplicationTypeChoices.vmware_client.value: application_type.VMwareClientSerializer, - const.ApplicationTypeChoices.custom.value: application_type.CustomSerializer, + const.AppType.chrome.value: application_type.ChromeSerializer, + const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer, + const.AppType.vmware_client.value: application_type.VMwareClientSerializer, + const.AppType.custom.value: application_type.CustomSerializer, # cloud - const.ApplicationTypeChoices.k8s.value: application_type.K8SSerializer + const.AppType.k8s.value: application_type.K8SSerializer } diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py index fb5d08228..0fc0ec0fe 100644 --- a/apps/applications/urls/api_urls.py +++ b/apps/applications/urls/api_urls.py @@ -10,12 +10,14 @@ app_name = 'applications' router = BulkRouter() router.register(r'applications', api.ApplicationViewSet, 'application') +router.register(r'accounts', api.ApplicationAccountViewSet, 'application-account') +router.register(r'account-secrets', api.ApplicationAccountSecretViewSet, 'application-account-secret') urlpatterns = [ path('remote-apps//connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'), - path('application-users/', api.ApplicationUserListApi.as_view(), name='application-user'), - path('application-user-auth-infos/', api.ApplicationUserAuthInfoListApi.as_view(), name='application-user-auth-info') + # path('accounts/', api.ApplicationAccountViewSet.as_view(), name='application-account'), + # path('account-secrets/', api.ApplicationAccountSecretViewSet.as_view(), name='application-account-secret') ] diff --git a/apps/assets/api/accounts.py b/apps/assets/api/accounts.py index 20b76a28a..ecd397dd6 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -1,5 +1,4 @@ from django.db.models import F, Q -from django.conf import settings from rest_framework.decorators import action from django_filters import rest_framework as filters from rest_framework.response import Response @@ -87,11 +86,6 @@ class AccountSecretsViewSet(AccountViewSet): permission_classes = (IsOrgAdmin, NeedMFAVerify) http_method_names = ['get'] - def get_permissions(self): - if not settings.SECURITY_VIEW_AUTH_NEED_MFA: - self.permission_classes = [IsOrgAdminOrAppUser] - return super().get_permissions() - class AccountTaskCreateAPI(CreateAPIView): permission_classes = (IsOrgAdminOrAppUser,) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 914db0db9..ee802e311 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -60,10 +60,10 @@ class ProtocolMixin: @classmethod def get_protocol_by_application_type(cls, app_type): - from applications.const import ApplicationTypeChoices + from applications.const import AppType if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS: protocol = app_type - elif app_type in ApplicationTypeChoices.remote_app_types(): + elif app_type in AppType.remote_app_types(): protocol = cls.Protocol.rdp else: protocol = None diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index f566bf87f..df0fdb72d 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -28,6 +28,7 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): }, 'private_key': {'write_only': True}, 'public_key': {'write_only': True}, + 'systemuser_display': {'label': _('System user display')} } ref_name = 'AssetAccountSerializer' diff --git a/apps/authentication/api/sso.py b/apps/authentication/api/sso.py index 04149e9a5..66febfbd7 100644 --- a/apps/authentication/api/sso.py +++ b/apps/authentication/api/sso.py @@ -11,7 +11,7 @@ from rest_framework.permissions import AllowAny from common.utils.timezone import utcnow from common.const.http import POST, GET -from common.drf.api import JmsGenericViewSet +from common.drf.api import JMSGenericViewSet from common.drf.serializers import EmptySerializer from common.permissions import IsSuperUser from common.utils import reverse @@ -26,7 +26,7 @@ NEXT_URL = 'next' AUTH_KEY = 'authkey' -class SSOViewSet(AuthMixin, JmsGenericViewSet): +class SSOViewSet(AuthMixin, JMSGenericViewSet): queryset = SSOToken.objects.all() serializer_classes = { 'login_url': SSOTokenSerializer, diff --git a/apps/common/drf/api.py b/apps/common/drf/api.py index 43765f822..fddd37939 100644 --- a/apps/common/drf/api.py +++ b/apps/common/drf/api.py @@ -1,4 +1,4 @@ -from rest_framework.viewsets import GenericViewSet, ModelViewSet +from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet, ViewSet from rest_framework_bulk import BulkModelViewSet from ..mixins.api import ( @@ -15,19 +15,23 @@ class CommonMixin(SerializerMixin, pass -class JmsGenericViewSet(CommonMixin, - GenericViewSet): +class JMSGenericViewSet(CommonMixin, GenericViewSet): pass -class JMSModelViewSet(CommonMixin, - ModelViewSet): +class JMSViewSet(CommonMixin, ViewSet): pass -class JMSBulkModelViewSet(CommonMixin, - AllowBulkDestroyMixin, - BulkModelViewSet): +class JMSModelViewSet(CommonMixin, ModelViewSet): + pass + + +class JMSReadOnlyModelViewSet(CommonMixin, ReadOnlyModelViewSet): + pass + + +class JMSBulkModelViewSet(CommonMixin, AllowBulkDestroyMixin, BulkModelViewSet): pass diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index 13323df0d..f7eb35dc2 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -24,6 +24,7 @@ from ..utils import lazyproperty __all__ = [ 'JSONResponseMixin', 'CommonApiMixin', 'AsyncApiMixin', 'RelationMixin', 'QuerySetMixin', 'ExtraFilterFieldsMixin', 'RenderToJsonMixin', + 'SerializerMixin', 'AllowBulkDestroyMixin', 'PaginatedResponseMixin' ] diff --git a/apps/common/permissions.py b/apps/common/permissions.py index 1fced6478..176455f66 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -112,6 +112,9 @@ class UserCanUpdateSSHKey(permissions.BasePermission): class NeedMFAVerify(permissions.BasePermission): def has_permission(self, request, view): + if not settings.SECURITY_VIEW_AUTH_NEED_MFA: + return True + mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0) if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL: return True diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 0e910cf16..1a9a79c05 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -273,3 +273,17 @@ def bulk_get(d, *keys, default=None): for key in keys: values.append(d.get(key, default)) return values + + +def unique(objects, key=None): + seen = OrderedDict() + + if key is None: + key = lambda item: item + + for obj in objects: + v = key(obj) + if v not in seen: + seen[v] = obj + return list(seen.values()) + diff --git a/apps/jumpserver/views/celery_flower.py b/apps/jumpserver/views/celery_flower.py index befd3c5f0..0ec4a0fe1 100644 --- a/apps/jumpserver/views/celery_flower.py +++ b/apps/jumpserver/views/celery_flower.py @@ -20,7 +20,7 @@ def celery_flower_view(request, path): try: response = proxy_view(request, remote_url) except Exception as e: - msg = _("

Flow service unavailable, check it

") + \ + msg = _("

Flower service unavailable, check it

") + \ '

{}
'.format(e) response = HttpResponse(msg) return response diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 384cce4cd..64901bb1d 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-07-26 19:02+0800\n" +"POT-Creation-Date: 2021-07-27 15:57+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -18,11 +18,11 @@ msgstr "" "X-Generator: Poedit 2.4.3\n" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 -#: applications/models/application.py:11 assets/models/asset.py:139 +#: applications/models/application.py:162 assets/models/asset.py:139 #: assets/models/base.py:175 assets/models/cluster.py:18 #: assets/models/cmd_filter.py:21 assets/models/domain.py:21 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 -#: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29 +#: orgs/models.py:24 perms/models/base.py:49 settings/models.py:29 #: terminal/models/storage.py:23 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:32 #: users/models/group.py:15 users/models/user.py:551 @@ -52,17 +52,17 @@ msgstr "激活中" # msgid "Date created" # msgstr "创建日期" -#: acls/models/base.py:32 applications/models/application.py:24 +#: acls/models/base.py:32 applications/models/application.py:175 #: assets/models/asset.py:144 assets/models/asset.py:220 #: assets/models/base.py:180 assets/models/cluster.py:29 #: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:64 #: assets/models/domain.py:22 assets/models/domain.py:53 #: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37 -#: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34 +#: orgs/models.py:27 perms/models/base.py:57 settings/models.py:34 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/ticket.py:73 users/models/group.py:16 #: users/models/user.py:584 xpack/plugins/change_auth_plan/models.py:77 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:108 +#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:98 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" @@ -94,7 +94,7 @@ msgstr "动作" #: acls/serializers/login_acl.py:33 assets/models/label.py:15 #: audits/models.py:36 audits/models.py:56 audits/models.py:69 #: audits/serializers.py:93 authentication/models.py:44 -#: authentication/models.py:97 orgs/models.py:18 orgs/models.py:418 +#: authentication/models.py:97 orgs/models.py:19 orgs/models.py:433 #: perms/models/base.py:50 templates/index.html:78 #: terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 @@ -126,8 +126,8 @@ msgstr "系统用户" #: terminal/backends/command/serializers.py:13 terminal/models/session.py:40 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 -#: xpack/plugins/change_auth_plan/models.py:290 -#: xpack/plugins/cloud/models.py:212 +#: xpack/plugins/change_auth_plan/models.py:282 +#: xpack/plugins/cloud/models.py:202 msgid "Asset" msgstr "资产" @@ -140,7 +140,7 @@ msgstr "审批人" msgid "Login asset confirm" msgstr "登录资产复核" -#: acls/serializers/login_acl.py:18 xpack/plugins/cloud/serializers.py:165 +#: acls/serializers/login_acl.py:18 msgid "IP address invalid: `{}`" msgstr "IP 地址无效: `{}`" @@ -172,6 +172,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_asset_acl.py:51 +#: applications/serializers/application.py:71 #: applications/serializers/attrs/application_type/chrome.py:20 #: applications/serializers/attrs/application_type/custom.py:21 #: applications/serializers/attrs/application_type/mysql_workbench.py:30 @@ -181,8 +182,8 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:549 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models.py:47 -#: xpack/plugins/change_auth_plan/models.py:286 -#: xpack/plugins/cloud/serializers.py:67 +#: xpack/plugins/change_auth_plan/models.py:278 +#: xpack/plugins/cloud/serializers.py:65 msgid "Username" msgstr "用户名" @@ -228,21 +229,31 @@ msgstr "组织 `{}` 不存在" msgid "None of the reviewers belong to Organization `{}`" msgstr "所有复核人都不属于组织 `{}`" -#: applications/const.py:9 +#: applications/api/mixin.py:19 templates/_nav_user.html:10 +msgid "My applications" +msgstr "我的应用" + +#: applications/const.py:8 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:26 msgid "Database" msgstr "数据库" -#: applications/const.py:10 +#: applications/const.py:9 msgid "Remote app" msgstr "远程应用" -#: applications/const.py:29 +#: applications/const.py:28 msgid "Custom" msgstr "自定义" -#: applications/models/application.py:13 assets/models/label.py:21 +#: applications/models/application.py:46 templates/_nav.html:60 +msgid "Applications" +msgstr "应用管理" + +#: applications/models/application.py:164 +#: applications/serializers/application.py:78 +#: applications/serializers/application.py:79 assets/models/label.py:21 #: perms/models/application_permission.py:20 #: perms/serializers/application/permission.py:16 #: perms/serializers/application/user_permission.py:33 @@ -250,7 +261,9 @@ msgstr "自定义" msgid "Category" msgstr "类别" -#: applications/models/application.py:16 assets/models/cmd_filter.py:53 +#: applications/models/application.py:167 +#: applications/serializers/application.py:80 +#: applications/serializers/application.py:81 assets/models/cmd_filter.py:53 #: assets/models/user.py:202 perms/models/application_permission.py:23 #: perms/serializers/application/permission.py:17 #: perms/serializers/application/user_permission.py:34 @@ -260,12 +273,12 @@ msgstr "类别" msgid "Type" msgstr "类型" -#: applications/models/application.py:20 assets/models/asset.py:188 +#: applications/models/application.py:171 assets/models/asset.py:188 #: assets/models/domain.py:27 assets/models/domain.py:52 msgid "Domain" msgstr "网域" -#: applications/models/application.py:22 xpack/plugins/cloud/models.py:33 +#: applications/models/application.py:173 xpack/plugins/cloud/models.py:33 msgid "Attrs" msgstr "" @@ -278,29 +291,63 @@ msgid "Type(Dispaly)" msgstr "类型 (显示名称)" #: applications/serializers/application.py:72 +#: applications/serializers/application.py:100 +#: applications/serializers/attrs/application_type/chrome.py:23 +#: applications/serializers/attrs/application_type/custom.py:25 +#: applications/serializers/attrs/application_type/mysql_workbench.py:34 +#: applications/serializers/attrs/application_type/vmware_client.py:30 +#: assets/models/base.py:177 audits/signals_handler.py:63 +#: authentication/forms.py:22 +#: authentication/templates/authentication/login.html:164 +#: settings/serializers/settings.py:94 users/forms/profile.py:21 +#: users/templates/users/user_otp_check_password.html:13 +#: users/templates/users/user_password_update.html:43 +#: users/templates/users/user_password_verify.html:18 +#: xpack/plugins/change_auth_plan/models.py:68 +#: xpack/plugins/change_auth_plan/models.py:190 +#: xpack/plugins/change_auth_plan/models.py:285 +#: xpack/plugins/cloud/serializers.py:67 +msgid "Password" +msgstr "密码" + +#: applications/serializers/application.py:73 assets/models/authbook.py:16 +#: assets/models/user.py:277 audits/models.py:39 +#: perms/models/application_permission.py:31 +#: perms/models/asset_permission.py:101 templates/_nav.html:45 +#: terminal/backends/command/models.py:20 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42 +#: users/templates/users/_granted_assets.html:27 +#: users/templates/users/user_asset_permission.html:42 +#: users/templates/users/user_asset_permission.html:76 +#: users/templates/users/user_asset_permission.html:159 +#: users/templates/users/user_database_app_permission.html:40 +#: users/templates/users/user_database_app_permission.html:67 +msgid "System user" +msgstr "系统用户" + +#: applications/serializers/application.py:74 assets/serializers/account.py:31 +msgid "System user display" +msgstr "系统用户显示" + +#: applications/serializers/application.py:75 +msgid "App" +msgstr "应用" + +#: applications/serializers/application.py:76 +msgid "Union id" +msgstr "联合ID" + +#: applications/serializers/application.py:77 msgid "Application name" msgstr "应用名称" -#: applications/serializers/application.py:73 -msgid "Application category" -msgstr "应用类别" - -#: applications/serializers/application.py:74 -msgid "Application type" -msgstr "应用类型" - -#: applications/serializers/application.py:87 -#: assets/serializers/system_user.py:53 assets/serializers/system_user.py:201 -msgid "Login mode display" -msgstr "认证方式(显示名称)" - #: applications/serializers/attrs/application_category/cloud.py:9 #: assets/models/cluster.py:40 msgid "Cluster" msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:65 +#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:63 msgid "Host" msgstr "主机" @@ -310,7 +357,7 @@ msgstr "主机" #: applications/serializers/attrs/application_type/oracle.py:11 #: applications/serializers/attrs/application_type/pgsql.py:11 #: assets/models/asset.py:185 assets/models/domain.py:50 -#: xpack/plugins/cloud/serializers.py:66 +#: xpack/plugins/cloud/serializers.py:64 msgid "Port" msgstr "端口" @@ -326,24 +373,6 @@ msgstr "应用路径" msgid "Target URL" msgstr "目标URL" -#: applications/serializers/attrs/application_type/chrome.py:23 -#: applications/serializers/attrs/application_type/custom.py:25 -#: applications/serializers/attrs/application_type/mysql_workbench.py:34 -#: applications/serializers/attrs/application_type/vmware_client.py:30 -#: assets/models/base.py:177 audits/signals_handler.py:63 -#: authentication/forms.py:22 -#: authentication/templates/authentication/login.html:164 -#: settings/serializers/settings.py:94 users/forms/profile.py:21 -#: users/templates/users/user_otp_check_password.html:13 -#: users/templates/users/user_password_update.html:43 -#: users/templates/users/user_password_verify.html:18 -#: xpack/plugins/change_auth_plan/models.py:68 -#: xpack/plugins/change_auth_plan/models.py:194 -#: xpack/plugins/change_auth_plan/models.py:293 -#: xpack/plugins/cloud/serializers.py:69 -msgid "Password" -msgstr "密码" - #: applications/serializers/attrs/application_type/custom.py:13 msgid "Operating parameter" msgstr "运行参数" @@ -392,7 +421,6 @@ msgstr "系统平台" #: assets/models/asset.py:186 assets/serializers/asset.py:65 #: perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers.py:183 msgid "Protocols" msgstr "协议组" @@ -411,7 +439,7 @@ msgstr "激活" #: assets/models/asset.py:193 assets/models/cluster.py:19 #: assets/models/user.py:191 assets/models/user.py:326 templates/_nav.html:44 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers.py:205 +#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:179 msgid "Admin user" msgstr "特权用户" @@ -486,10 +514,10 @@ msgstr "标签管理" #: assets/models/asset.py:218 assets/models/base.py:183 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cmd_filter.py:67 assets/models/group.py:21 -#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:24 -#: orgs/models.py:422 perms/models/base.py:55 users/models/user.py:592 +#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:25 +#: orgs/models.py:437 perms/models/base.py:55 users/models/user.py:592 #: users/serializers/group.py:33 xpack/plugins/change_auth_plan/models.py:81 -#: xpack/plugins/cloud/models.py:114 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" @@ -499,26 +527,12 @@ msgstr "创建者" #: assets/models/cluster.py:26 assets/models/domain.py:24 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50 -#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:25 -#: orgs/models.py:420 perms/models/base.py:56 users/models/group.py:18 -#: users/models/user.py:774 xpack/plugins/cloud/models.py:117 +#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26 +#: orgs/models.py:435 perms/models/base.py:56 users/models/group.py:18 +#: users/models/user.py:774 xpack/plugins/cloud/models.py:107 msgid "Date created" msgstr "创建日期" -#: assets/models/authbook.py:16 assets/models/user.py:277 audits/models.py:39 -#: perms/models/application_permission.py:31 -#: perms/models/asset_permission.py:101 templates/_nav.html:45 -#: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42 -#: users/templates/users/_granted_assets.html:27 -#: users/templates/users/user_asset_permission.html:42 -#: users/templates/users/user_asset_permission.html:76 -#: users/templates/users/user_asset_permission.html:159 -#: users/templates/users/user_database_app_permission.html:40 -#: users/templates/users/user_database_app_permission.html:67 -msgid "System user" -msgstr "系统用户" - #: assets/models/authbook.py:17 msgid "Version" msgstr "版本" @@ -548,20 +562,20 @@ msgid "Date verified" msgstr "校验日期" #: assets/models/base.py:178 xpack/plugins/change_auth_plan/models.py:72 -#: xpack/plugins/change_auth_plan/models.py:201 -#: xpack/plugins/change_auth_plan/models.py:300 +#: xpack/plugins/change_auth_plan/models.py:197 +#: xpack/plugins/change_auth_plan/models.py:292 msgid "SSH private key" msgstr "SSH密钥" #: assets/models/base.py:179 xpack/plugins/change_auth_plan/models.py:75 -#: xpack/plugins/change_auth_plan/models.py:197 -#: xpack/plugins/change_auth_plan/models.py:296 +#: xpack/plugins/change_auth_plan/models.py:193 +#: xpack/plugins/change_auth_plan/models.py:288 msgid "SSH public key" msgstr "SSH公钥" #: assets/models/base.py:182 assets/models/gathered_user.py:20 #: common/db/models.py:73 common/mixins/models.py:51 ops/models/adhoc.py:39 -#: orgs/models.py:421 +#: orgs/models.py:436 msgid "Date updated" msgstr "更新日期" @@ -713,7 +727,7 @@ msgstr "ssh私钥" #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 -#: xpack/plugins/cloud/models.py:93 xpack/plugins/cloud/serializers.py:206 +#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:180 msgid "Node" msgstr "节点" @@ -866,6 +880,10 @@ msgstr "密钥指纹" msgid "Nodes amount" msgstr "节点数量" +#: assets/serializers/system_user.py:53 assets/serializers/system_user.py:201 +msgid "Login mode display" +msgstr "认证方式(显示名称)" + #: assets/serializers/system_user.py:55 msgid "Ad domain" msgstr "Ad 网域" @@ -1066,8 +1084,8 @@ msgstr "成功" #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:74 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:40 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:78 -#: xpack/plugins/change_auth_plan/models.py:181 -#: xpack/plugins/change_auth_plan/models.py:315 +#: xpack/plugins/change_auth_plan/models.py:177 +#: xpack/plugins/change_auth_plan/models.py:307 #: xpack/plugins/gathered_user/models.py:76 msgid "Date start" msgstr "开始日期" @@ -1138,13 +1156,13 @@ msgstr "用户代理" msgid "MFA" msgstr "多因子认证" -#: audits/models.py:106 xpack/plugins/change_auth_plan/models.py:311 -#: xpack/plugins/cloud/models.py:171 +#: audits/models.py:106 xpack/plugins/change_auth_plan/models.py:303 +#: xpack/plugins/cloud/models.py:161 msgid "Reason" msgstr "原因" #: audits/models.py:107 tickets/models/ticket.py:47 -#: xpack/plugins/cloud/models.py:167 xpack/plugins/cloud/models.py:216 +#: xpack/plugins/cloud/models.py:157 xpack/plugins/cloud/models.py:206 msgid "Status" msgstr "状态" @@ -1178,7 +1196,7 @@ msgid "Hosts display" msgstr "主机名称" #: audits/serializers.py:89 ops/models/command.py:26 -#: xpack/plugins/cloud/models.py:165 +#: xpack/plugins/cloud/models.py:155 msgid "Result" msgstr "结果" @@ -1391,7 +1409,7 @@ msgstr "{ApplicationPermission} *添加了* {SystemUser}" msgid "{ApplicationPermission} *REMOVE* {SystemUser}" msgstr "{ApplicationPermission} *移除了* {SystemUser}" -#: authentication/api/connection_token.py:258 +#: authentication/api/connection_token.py:268 msgid "Invalid token" msgstr "无效的令牌" @@ -1694,7 +1712,7 @@ msgstr "OpenID" #: authentication/templates/authentication/login.html:205 msgid "CAS" -msgstr "" +msgstr "CAS" #: authentication/templates/authentication/login_otp.html:17 msgid "One-time password" @@ -1896,7 +1914,7 @@ msgstr "您的请求超时了" #: common/exceptions.py:35 msgid "M2M reverse not allowed" -msgstr "" +msgstr "多对多反向是不被允许的" #: common/exceptions.py:41 msgid "Is referenced by other objects and cannot be deleted" @@ -1904,35 +1922,35 @@ msgstr "被其他对象关联,不能删除" #: common/exceptions.py:47 msgid "This action require verify your MFA" -msgstr "" +msgstr "这个操作需要验证 MFA" #: common/fields/model.py:80 msgid "Marshal dict data to char field" -msgstr "" +msgstr "编码 dict 为 char" #: common/fields/model.py:84 msgid "Marshal dict data to text field" -msgstr "" +msgstr "编码 dict 为 text" #: common/fields/model.py:96 msgid "Marshal list data to char field" -msgstr "" +msgstr "编码 list 为 char" #: common/fields/model.py:100 msgid "Marshal list data to text field" -msgstr "" +msgstr "编码 list 为 text" #: common/fields/model.py:104 msgid "Marshal data to char field" -msgstr "" +msgstr "编码数据为 char" #: common/fields/model.py:108 msgid "Marshal data to text field" -msgstr "" +msgstr "编码数据为 text" #: common/fields/model.py:150 msgid "Encrypt field using Secret Key" -msgstr "" +msgstr "加密的字段" #: common/message/backends/exceptions.py:23 msgid "Network error, please contact system administrator" @@ -1942,17 +1960,17 @@ msgstr "网络错误,请联系系统管理员" msgid "WeCom error, please contact system administrator" msgstr "企业微信错误,请联系系统管理员" -#: common/mixins/api.py:57 +#: common/mixins/api.py:58 msgid "Request file format may be wrong" msgstr "上传的文件格式错误 或 其它类型资源的文件" #: common/mixins/models.py:33 msgid "is discard" -msgstr "" +msgstr "忽略的" #: common/mixins/models.py:34 msgid "discard time" -msgstr "" +msgstr "忽略时间" #: common/utils/ipip/utils.py:15 msgid "Invalid ip" @@ -1975,8 +1993,8 @@ msgid "JumpServer Open Source Bastion Host" msgstr "JumpServer 开源堡垒机" #: jumpserver/views/celery_flower.py:23 -msgid "

Flow service unavailable, check it

" -msgstr "" +msgid "

Flower service unavailable, check it

" +msgstr "Flower 服务不可用,请检查" #: jumpserver/views/other.py:25 msgid "" @@ -2031,7 +2049,7 @@ msgid "Regularly perform" msgstr "定期执行" #: ops/mixin.py:106 ops/mixin.py:147 -#: xpack/plugins/change_auth_plan/serializers.py:55 +#: xpack/plugins/change_auth_plan/serializers.py:51 msgid "Periodic perform" msgstr "定时执行" @@ -2110,8 +2128,8 @@ msgstr "开始时间" msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:246 xpack/plugins/change_auth_plan/models.py:184 -#: xpack/plugins/change_auth_plan/models.py:318 +#: ops/models/adhoc.py:246 xpack/plugins/change_auth_plan/models.py:180 +#: xpack/plugins/change_auth_plan/models.py:310 #: xpack/plugins/gathered_user/models.py:79 msgid "Time" msgstr "时间" @@ -2177,25 +2195,25 @@ msgstr "当前组织 ({}) 不能被删除" msgid "The organization have resource ({}) cannot be deleted" msgstr "组织存在资源 ({}) 不能被删除" -#: orgs/mixins/models.py:45 orgs/mixins/serializers.py:25 orgs/models.py:36 -#: orgs/models.py:417 orgs/serializers.py:106 +#: orgs/mixins/models.py:45 orgs/mixins/serializers.py:25 orgs/models.py:37 +#: orgs/models.py:432 orgs/serializers.py:106 #: tickets/serializers/ticket/ticket.py:83 msgid "Organization" msgstr "组织" -#: orgs/models.py:16 +#: orgs/models.py:17 msgid "Organization administrator" msgstr "组织管理员" -#: orgs/models.py:17 +#: orgs/models.py:18 msgid "Organization auditor" msgstr "组织审计员" -#: orgs/models.py:30 +#: orgs/models.py:31 msgid "GLOBAL" msgstr "全局组织" -#: orgs/models.py:419 users/models/user.py:561 +#: orgs/models.py:434 users/models/user.py:561 #: users/templates/users/_select_user_modal.html:15 msgid "Role" msgstr "角色" @@ -2476,7 +2494,7 @@ msgstr "LDAP 地址" #: settings/serializers/settings.py:91 msgid "eg: ldap://localhost:389" -msgstr "" +msgstr "如: ldap://localhost:389" #: settings/serializers/settings.py:93 msgid "Bind DN" @@ -2845,7 +2863,7 @@ msgstr "用户页面" #: templates/_header_bar.html:78 msgid "API Key" -msgstr "" +msgstr "API Key" #: templates/_header_bar.html:79 msgid "Logout" @@ -2948,10 +2966,6 @@ msgstr "命令过滤" msgid "Platform list" msgstr "平台列表" -#: templates/_nav.html:60 -msgid "Applications" -msgstr "应用管理" - #: templates/_nav.html:64 templates/_nav.html:82 templates/_nav_user.html:16 msgid "RemoteApp" msgstr "远程应用" @@ -3037,7 +3051,7 @@ msgstr "改密日志" #: templates/_nav.html:163 msgid "XPack" -msgstr "" +msgstr "XPack" #: templates/_nav.html:171 msgid "Account list" @@ -3055,10 +3069,6 @@ msgstr "系统设置" msgid "My assets" msgstr "我的资产" -#: templates/_nav_user.html:10 -msgid "My Applications" -msgstr "我的应用" - #: templates/_nav_user.html:31 msgid "Command execution" msgstr "命令执行" @@ -3540,18 +3550,18 @@ msgstr "桶名称" #: terminal/serializers/storage.py:30 msgid "Access key" -msgstr "" +msgstr "Access key" #: terminal/serializers/storage.py:34 msgid "Secret key" -msgstr "" +msgstr "Secret key" #: terminal/serializers/storage.py:39 terminal/serializers/storage.py:51 #: terminal/serializers/storage.py:81 terminal/serializers/storage.py:91 msgid "Endpoint" msgstr "端点" -#: terminal/serializers/storage.py:66 xpack/plugins/cloud/models.py:209 +#: terminal/serializers/storage.py:66 xpack/plugins/cloud/models.py:199 msgid "Region" msgstr "地域" @@ -3595,7 +3605,7 @@ msgstr "文档类型" msgid "Ignore Certificate Verification" msgstr "忽略证书认证" -#: terminal/serializers/terminal.py:73 terminal/serializers/terminal.py:81 +#: terminal/serializers/terminal.py:78 terminal/serializers/terminal.py:86 msgid "Not found" msgstr "没有发现" @@ -4141,7 +4151,7 @@ msgid "Set password" msgstr "设置密码" #: users/serializers/user.py:27 xpack/plugins/change_auth_plan/models.py:61 -#: xpack/plugins/change_auth_plan/serializers.py:30 +#: xpack/plugins/change_auth_plan/serializers.py:29 msgid "Password strategy" msgstr "密码策略" @@ -4773,7 +4783,7 @@ msgstr "重置密码成功,返回到登录页面" #: xpack/plugins/change_auth_plan/meta.py:9 #: xpack/plugins/change_auth_plan/models.py:89 -#: xpack/plugins/change_auth_plan/models.py:188 +#: xpack/plugins/change_auth_plan/models.py:184 msgid "Change auth plan" msgstr "改密计划" @@ -4793,73 +4803,60 @@ msgstr "所有资产使用不同的随机密码" msgid "Password rules" msgstr "密码规则" -#: xpack/plugins/change_auth_plan/models.py:176 -msgid "Manual trigger" -msgstr "手动触发" - -#: xpack/plugins/change_auth_plan/models.py:177 -msgid "Timing trigger" -msgstr "定时触发" - -#: xpack/plugins/change_auth_plan/models.py:191 +#: xpack/plugins/change_auth_plan/models.py:187 msgid "Change auth plan snapshot" msgstr "改密计划快照" -#: xpack/plugins/change_auth_plan/models.py:205 -#: xpack/plugins/change_auth_plan/serializers.py:132 -msgid "Trigger mode" -msgstr "触发模式" - -#: xpack/plugins/change_auth_plan/models.py:210 -#: xpack/plugins/change_auth_plan/models.py:304 +#: xpack/plugins/change_auth_plan/models.py:202 +#: xpack/plugins/change_auth_plan/models.py:296 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:277 +#: xpack/plugins/change_auth_plan/models.py:269 msgid "Ready" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:278 +#: xpack/plugins/change_auth_plan/models.py:270 msgid "Preflight check" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:279 +#: xpack/plugins/change_auth_plan/models.py:271 msgid "Change auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:280 +#: xpack/plugins/change_auth_plan/models.py:272 msgid "Verify auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:281 +#: xpack/plugins/change_auth_plan/models.py:273 msgid "Keep auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:282 +#: xpack/plugins/change_auth_plan/models.py:274 msgid "Finished" msgstr "结束" -#: xpack/plugins/change_auth_plan/models.py:308 +#: xpack/plugins/change_auth_plan/models.py:300 msgid "Step" msgstr "步骤" -#: xpack/plugins/change_auth_plan/models.py:325 +#: xpack/plugins/change_auth_plan/models.py:317 msgid "Change auth plan task" msgstr "改密计划任务" -#: xpack/plugins/change_auth_plan/serializers.py:56 +#: xpack/plugins/change_auth_plan/serializers.py:52 msgid "Run times" msgstr "执行次数" -#: xpack/plugins/change_auth_plan/serializers.py:72 +#: xpack/plugins/change_auth_plan/serializers.py:68 msgid "* Please enter custom password" msgstr "* 请输入自定义密码" -#: xpack/plugins/change_auth_plan/serializers.py:82 +#: xpack/plugins/change_auth_plan/serializers.py:78 msgid "* Please enter the correct password length" msgstr "* 请输入正确的密码长度" -#: xpack/plugins/change_auth_plan/serializers.py:85 +#: xpack/plugins/change_auth_plan/serializers.py:81 msgid "* Password length range 6-30 bits" msgstr "* 密码长度范围 6-30 位" @@ -4967,47 +4964,43 @@ msgstr "云服务商" msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:82 xpack/plugins/cloud/serializers.py:204 +#: xpack/plugins/cloud/models.py:78 xpack/plugins/cloud/serializers.py:178 msgid "Account" msgstr "账户" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers.py:179 +#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:160 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:88 +#: xpack/plugins/cloud/models.py:84 msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers.py:186 -msgid "IP network segment group" -msgstr "IP网段组" - -#: xpack/plugins/cloud/models.py:105 xpack/plugins/cloud/serializers.py:208 +#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:182 msgid "Always update" msgstr "总是更新" -#: xpack/plugins/cloud/models.py:111 +#: xpack/plugins/cloud/models.py:101 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:122 xpack/plugins/cloud/models.py:163 +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/models.py:153 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:174 xpack/plugins/cloud/models.py:219 +#: xpack/plugins/cloud/models.py:164 xpack/plugins/cloud/models.py:209 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:199 +#: xpack/plugins/cloud/models.py:189 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:203 +#: xpack/plugins/cloud/models.py:193 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:206 +#: xpack/plugins/cloud/models.py:196 msgid "Instance" msgstr "实例" @@ -5151,56 +5144,47 @@ msgstr "西南-贵阳1" msgid "EU-Paris" msgstr "欧洲-巴黎" -#: xpack/plugins/cloud/serializers.py:21 +#: xpack/plugins/cloud/serializers.py:19 msgid "AccessKey ID" msgstr "" -#: xpack/plugins/cloud/serializers.py:24 +#: xpack/plugins/cloud/serializers.py:22 msgid "AccessKey Secret" msgstr "" -#: xpack/plugins/cloud/serializers.py:30 +#: xpack/plugins/cloud/serializers.py:28 msgid "Client ID" msgstr "" -#: xpack/plugins/cloud/serializers.py:33 +#: xpack/plugins/cloud/serializers.py:31 msgid "Client Secret" msgstr "" -#: xpack/plugins/cloud/serializers.py:36 +#: xpack/plugins/cloud/serializers.py:34 msgid "Tenant ID" msgstr "" -#: xpack/plugins/cloud/serializers.py:39 +#: xpack/plugins/cloud/serializers.py:37 msgid "Subscription ID" msgstr "" -#: xpack/plugins/cloud/serializers.py:51 +#: xpack/plugins/cloud/serializers.py:49 msgid "This field is required" msgstr "这个字段是必填项" -#: xpack/plugins/cloud/serializers.py:85 xpack/plugins/cloud/serializers.py:89 +#: xpack/plugins/cloud/serializers.py:83 xpack/plugins/cloud/serializers.py:87 msgid "API Endpoint" msgstr "API 端点" -#: xpack/plugins/cloud/serializers.py:171 -msgid "" -"The IP address that is first matched to will be used as the IP of the " -"created asset.
The default * indicates a random match.
Format for " -"comma-delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" -msgstr "" -"第一个匹配到的 IP 地址将被用作创建的资产的 IP。
默认值 * 表示随机匹配。" -"
格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20" - -#: xpack/plugins/cloud/serializers.py:177 +#: xpack/plugins/cloud/serializers.py:158 msgid "History count" msgstr "执行次数" -#: xpack/plugins/cloud/serializers.py:178 +#: xpack/plugins/cloud/serializers.py:159 msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/serializers.py:207 +#: xpack/plugins/cloud/serializers.py:181 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定时执行" @@ -5293,5 +5277,31 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Application category" +#~ msgstr "应用类别" + +#~ msgid "Application type" +#~ msgstr "应用类型" + +#~ msgid "Manual trigger" +#~ msgstr "手动触发" + +#~ msgid "Timing trigger" +#~ msgstr "定时触发" + +#~ msgid "Trigger mode" +#~ msgstr "触发模式" + +#~ msgid "IP network segment group" +#~ msgstr "IP网段组" + +#~ msgid "" +#~ "The IP address that is first matched to will be used as the IP of the " +#~ "created asset.
The default * indicates a random match.
Format for " +#~ "comma-delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" +#~ msgstr "" +#~ "第一个匹配到的 IP 地址将被用作创建的资产的 IP。
默认值 * 表示随机匹" +#~ "配。
格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20" + #~ msgid "Trigger" #~ msgstr "触发" diff --git a/apps/notifications/api/notifications.py b/apps/notifications/api/notifications.py index 7d176e7ae..5c726e201 100644 --- a/apps/notifications/api/notifications.py +++ b/apps/notifications/api/notifications.py @@ -4,7 +4,7 @@ from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status -from common.drf.api import JmsGenericViewSet +from common.drf.api import JMSGenericViewSet from notifications.notifications import system_msgs from notifications.models import SystemMsgSubscription from notifications.backends import BACKEND @@ -30,7 +30,7 @@ class BackendListView(APIView): class SystemMsgSubscriptionViewSet(ListModelMixin, UpdateModelMixin, - JmsGenericViewSet): + JMSGenericViewSet): lookup_field = 'message_type' queryset = SystemMsgSubscription.objects.all() serializer_classes = { diff --git a/apps/notifications/api/site_msgs.py b/apps/notifications/api/site_msgs.py index 2f8ba7e15..632101384 100644 --- a/apps/notifications/api/site_msgs.py +++ b/apps/notifications/api/site_msgs.py @@ -5,7 +5,7 @@ from rest_framework.decorators import action from common.http import is_true from common.permissions import IsValidUser from common.const.http import GET, PATCH, POST -from common.drf.api import JmsGenericViewSet +from common.drf.api import JMSGenericViewSet from ..serializers import ( SiteMessageDetailSerializer, SiteMessageIdsSerializer, SiteMessageSendSerializer, @@ -16,7 +16,7 @@ from ..filters import SiteMsgFilter __all__ = ('SiteMessageViewSet', ) -class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet): +class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JMSGenericViewSet): permission_classes = (IsValidUser,) serializer_classes = { 'default': SiteMessageDetailSerializer, diff --git a/apps/orgs/models.py b/apps/orgs/models.py index 9eb2eb9ad..f50c686e3 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty, settings from common.const import choices +from common.tree import TreeNode from common.db.models import TextChoices @@ -233,6 +234,20 @@ class Organization(models.Model): with tmp_to_org(self): return resource_model.objects.all().count() + def as_tree_node(self, pid, opened=True): + node = TreeNode(**{ + 'id': self.id, + 'name': self.name, + 'title': self.name, + 'pId': pid, + 'open': opened, + 'isParent': True, + 'meta': { + 'type': 'org' + } + }) + return node + def _convert_to_uuid_set(users): rst = set() diff --git a/apps/perms/api/application/user_permission/user_permission_applications.py b/apps/perms/api/application/user_permission/user_permission_applications.py index 17e5557e1..a88217e27 100644 --- a/apps/perms/api/application/user_permission/user_permission_applications.py +++ b/apps/perms/api/application/user_permission/user_permission_applications.py @@ -4,6 +4,7 @@ from rest_framework.generics import ListAPIView from rest_framework.response import Response from common.mixins.api import CommonApiMixin +from common.tree import TreeNodeSerializer from applications.api.mixin import ( SerializeApplicationToTreeNodeMixin ) @@ -52,11 +53,13 @@ class ApplicationsAsTreeMixin(SerializeApplicationToTreeNodeMixin): """ 将应用序列化成树的结构返回 """ + serializer_class = TreeNodeSerializer def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) - data = self.serialize_applications_with_org(queryset) - return Response(data=data) + tree_nodes = self.serialize_applications_with_org(queryset) + serializer = self.get_serializer(tree_nodes, many=True) + return Response(data=serializer.data) class UserAllGrantedApplicationsAsTreeApi(ApplicationsAsTreeMixin, UserAllGrantedApplicationsApi): diff --git a/apps/perms/models/application_permission.py b/apps/perms/models/application_permission.py index d16b154ae..a405e667c 100644 --- a/apps/perms/models/application_permission.py +++ b/apps/perms/models/application_permission.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty from .base import BasePermission from users.models import User -from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices +from applications.const import AppCategory, AppType __all__ = [ 'ApplicationPermission', @@ -17,10 +17,10 @@ __all__ = [ class ApplicationPermission(BasePermission): category = models.CharField( - max_length=16, choices=ApplicationCategoryChoices.choices, verbose_name=_('Category') + max_length=16, choices=AppCategory.choices, verbose_name=_('Category') ) type = models.CharField( - max_length=16, choices=ApplicationTypeChoices.choices, verbose_name=_('Type') + max_length=16, choices=AppType.choices, verbose_name=_('Type') ) applications = models.ManyToManyField( 'applications.Application', related_name='granted_by_permissions', blank=True, @@ -38,15 +38,15 @@ class ApplicationPermission(BasePermission): @property def category_remote_app(self): - return self.category == ApplicationCategoryChoices.remote_app.value + return self.category == AppCategory.remote_app.value @property def category_db(self): - return self.category == ApplicationCategoryChoices.db.value + return self.category == AppCategory.db.value @property def category_cloud(self): - return self.category == ApplicationCategoryChoices.cloud.value + return self.category == AppCategory.cloud.value @lazyproperty def users_amount(self): diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html index c1da1f58b..7e36f3dcc 100644 --- a/apps/templates/_nav_user.html +++ b/apps/templates/_nav_user.html @@ -7,7 +7,7 @@
  • - {% trans 'My Applications' %} + {% trans 'My applications' %}