diff --git a/apps/applications/api/__init__.py b/apps/applications/api/__init__.py index a707cfde6..0e6e940ee 100644 --- a/apps/applications/api/__init__.py +++ b/apps/applications/api/__init__.py @@ -1,2 +1,3 @@ from .remote_app import * from .database_app import * +from .k8s_app import * diff --git a/apps/applications/api/k8s_app.py b/apps/applications/api/k8s_app.py new file mode 100644 index 000000000..5cc63b546 --- /dev/null +++ b/apps/applications/api/k8s_app.py @@ -0,0 +1,20 @@ +# coding: utf-8 +# + +from orgs.mixins.api import OrgBulkModelViewSet + +from .. import models +from .. import serializers +from ..hands import IsOrgAdminOrAppUser + +__all__ = [ + 'K8sAppViewSet', +] + + +class K8sAppViewSet(OrgBulkModelViewSet): + model = models.K8sApp + filter_fields = ('name',) + search_fields = filter_fields + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = serializers.K8sAppSerializer diff --git a/apps/applications/migrations/0005_k8sapp.py b/apps/applications/migrations/0005_k8sapp.py new file mode 100644 index 000000000..3f6964a88 --- /dev/null +++ b/apps/applications/migrations/0005_k8sapp.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.13 on 2020-08-07 07:13 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0004_auto_20191218_1705'), + ] + + operations = [ + migrations.CreateModel( + name='K8sApp', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('type', models.CharField(choices=[('k8s', 'Kubernetes')], default='k8s', max_length=128, verbose_name='Type')), + ('cluster', models.CharField(max_length=1024, verbose_name='Cluster')), + ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), + ], + options={ + 'verbose_name': 'KubernetesApp', + 'ordering': ('name',), + 'unique_together': {('org_id', 'name')}, + }, + ), + ] diff --git a/apps/applications/models/__init__.py b/apps/applications/models/__init__.py index a707cfde6..0e6e940ee 100644 --- a/apps/applications/models/__init__.py +++ b/apps/applications/models/__init__.py @@ -1,2 +1,3 @@ from .remote_app import * from .database_app import * +from .k8s_app import * diff --git a/apps/applications/models/k8s_app.py b/apps/applications/models/k8s_app.py new file mode 100644 index 000000000..c4f0591ca --- /dev/null +++ b/apps/applications/models/k8s_app.py @@ -0,0 +1,27 @@ +from django.utils.translation import gettext_lazy as _ + +from common.db import models +from orgs.mixins.models import OrgModelMixin + + +class K8sApp(OrgModelMixin, models.JMSModel): + class TYPE(models.ChoiceSet): + K8S = 'k8s', _('Kubernetes') + + name = models.CharField(max_length=128, verbose_name=_('Name')) + type = models.CharField( + default=TYPE.K8S, choices=TYPE.choices, + max_length=128, verbose_name=_('Type') + ) + cluster = models.CharField(max_length=1024, verbose_name=_('Cluster')) + comment = models.TextField( + max_length=128, default='', blank=True, verbose_name=_('Comment') + ) + + def __str__(self): + return self.name + + class Meta: + unique_together = [('org_id', 'name'), ] + verbose_name = _('KubernetesApp') + ordering = ('name', ) diff --git a/apps/applications/serializers/__init__.py b/apps/applications/serializers/__init__.py index a707cfde6..0e6e940ee 100644 --- a/apps/applications/serializers/__init__.py +++ b/apps/applications/serializers/__init__.py @@ -1,2 +1,3 @@ from .remote_app import * from .database_app import * +from .k8s_app import * diff --git a/apps/applications/serializers/k8s_app.py b/apps/applications/serializers/k8s_app.py new file mode 100644 index 000000000..68fafbc86 --- /dev/null +++ b/apps/applications/serializers/k8s_app.py @@ -0,0 +1,22 @@ +from rest_framework import serializers + +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from .. import models + +__all__ = [ + 'K8sAppSerializer', +] + + +class K8sAppSerializer(BulkOrgResourceModelSerializer): + type_display = serializers.CharField(source='get_type_display', read_only=True) + + class Meta: + model = models.K8sApp + fields = [ + 'id', 'name', 'type', 'type_display', 'comment', 'created_by', + 'date_created', 'date_updated', 'cluster' + ] + read_only_fields = [ + 'id', 'created_by', 'date_created', 'date_updated', + ] diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py index 1186bf1a2..42d0fe524 100644 --- a/apps/applications/urls/api_urls.py +++ b/apps/applications/urls/api_urls.py @@ -12,6 +12,7 @@ app_name = 'applications' router = BulkRouter() router.register(r'remote-apps', api.RemoteAppViewSet, 'remote-app') router.register(r'database-apps', api.DatabaseAppViewSet, 'database-app') +router.register(r'k8s-apps', api.K8sAppViewSet, 'k8s-app') urlpatterns = [ path('remote-apps//connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'), diff --git a/apps/assets/migrations/0054_auto_20200807_1032.py b/apps/assets/migrations/0054_auto_20200807_1032.py new file mode 100644 index 000000000..288b78e25 --- /dev/null +++ b/apps/assets/migrations/0054_auto_20200807_1032.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.13 on 2020-08-07 02:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0053_auto_20200723_1232'), + ] + + operations = [ + migrations.AddField( + model_name='systemuser', + name='token', + field=models.TextField(default='', verbose_name='Token'), + ), + migrations.AlterField( + model_name='systemuser', + name='protocol', + field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc'), ('mysql', 'mysql'), ('k8s', 'k8s')], default='ssh', max_length=16, verbose_name='Protocol'), + ), + ] diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 7085a3b2b..d787cf7e7 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -91,12 +91,14 @@ class SystemUser(BaseUser): PROTOCOL_TELNET = 'telnet' PROTOCOL_VNC = 'vnc' PROTOCOL_MYSQL = 'mysql' + PROTOCOL_K8S = 'k8s' PROTOCOL_CHOICES = ( (PROTOCOL_SSH, 'ssh'), (PROTOCOL_RDP, 'rdp'), (PROTOCOL_TELNET, 'telnet'), (PROTOCOL_VNC, 'vnc'), (PROTOCOL_MYSQL, 'mysql'), + (PROTOCOL_K8S, 'k8s'), ) LOGIN_AUTO = 'auto' @@ -118,6 +120,7 @@ class SystemUser(BaseUser): login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True) sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) + token = models.TextField(default='', verbose_name=_('Token')) _prefer = 'system_user' def __str__(self): diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 7f3ad5372..1f2a05867 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -33,13 +33,14 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'login_mode', 'login_mode_display', 'priority', 'username_same_with_user', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', - 'auto_generate_key', 'sftp_root', + 'auto_generate_key', 'sftp_root', 'token', 'assets_amount', 'date_created', 'created_by' ] extra_kwargs = { 'password': {"write_only": True}, 'public_key': {"write_only": True}, 'private_key': {"write_only": True}, + 'token': {"write_only": True}, 'nodes_amount': {'label': _('Node')}, 'assets_amount': {'label': _('Asset')}, 'login_mode_display': {'label': _('Login mode display')}, @@ -169,7 +170,7 @@ class SystemUserWithAuthInfoSerializer(SystemUserSerializer): 'login_mode', 'login_mode_display', 'priority', 'username_same_with_user', 'auto_push', 'sudo', 'shell', 'comment', - 'auto_generate_key', 'sftp_root', + 'auto_generate_key', 'sftp_root', 'token' ] extra_kwargs = { 'nodes_amount': {'label': _('Node')}, diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 70a1cb7e6..27d124dba 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 d7e1584ed..6a8c43190 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -21,15 +21,15 @@ msgstr "" msgid "Custom" msgstr "自定义" -#: applications/models/database_app.py:18 applications/models/remote_app.py:21 -#: assets/models/asset.py:145 assets/models/base.py:232 -#: assets/models/cluster.py:18 assets/models/cmd_filter.py:21 -#: assets/models/domain.py:20 assets/models/group.py:20 -#: assets/models/label.py:18 ops/mixin.py:24 orgs/models.py:22 -#: perms/models/base.py:48 settings/models.py:27 terminal/models.py:26 -#: terminal/models.py:342 terminal/models.py:374 terminal/models.py:411 -#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:489 -#: users/templates/users/_select_user_modal.html:13 +#: applications/models/database_app.py:18 applications/models/k8s_app.py:11 +#: applications/models/remote_app.py:21 assets/models/asset.py:145 +#: assets/models/base.py:232 assets/models/cluster.py:18 +#: assets/models/cmd_filter.py:21 assets/models/domain.py:20 +#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 +#: orgs/models.py:22 perms/models/base.py:48 settings/models.py:27 +#: terminal/models.py:26 terminal/models.py:342 terminal/models.py:374 +#: terminal/models.py:411 users/forms/profile.py:20 users/models/group.py:15 +#: users/models/user.py:489 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 #: users/templates/users/user_database_app_permission.html:36 @@ -46,8 +46,9 @@ msgstr "自定义" msgid "Name" msgstr "名称" -#: applications/models/database_app.py:22 assets/models/cmd_filter.py:52 -#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:40 +#: applications/models/database_app.py:22 applications/models/k8s_app.py:14 +#: assets/models/cmd_filter.py:52 terminal/models.py:376 terminal/models.py:413 +#: tickets/models/ticket.py:40 #: users/templates/users/user_granted_database_app.html:35 msgid "Type" msgstr "类型" @@ -69,16 +70,16 @@ msgstr "数据库" # msgid "Date created" # msgstr "创建日期" -#: applications/models/database_app.py:33 applications/models/remote_app.py:45 -#: assets/models/asset.py:150 assets/models/asset.py:226 -#: assets/models/base.py:237 assets/models/cluster.py:29 -#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:57 -#: assets/models/domain.py:21 assets/models/domain.py:54 -#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37 -#: orgs/models.py:25 perms/models/base.py:56 settings/models.py:32 -#: terminal/models.py:36 terminal/models.py:381 terminal/models.py:418 -#: users/models/group.py:16 users/models/user.py:522 -#: users/templates/users/user_detail.html:115 +#: applications/models/database_app.py:33 applications/models/k8s_app.py:18 +#: applications/models/remote_app.py:45 assets/models/asset.py:150 +#: assets/models/asset.py:226 assets/models/base.py:237 +#: assets/models/cluster.py:29 assets/models/cmd_filter.py:23 +#: assets/models/cmd_filter.py:57 assets/models/domain.py:21 +#: assets/models/domain.py:54 assets/models/group.py:23 +#: assets/models/label.py:23 ops/models/adhoc.py:37 orgs/models.py:25 +#: perms/models/base.py:56 settings/models.py:32 terminal/models.py:36 +#: terminal/models.py:381 terminal/models.py:418 users/models/group.py:16 +#: users/models/user.py:522 users/templates/users/user_detail.html:115 #: users/templates/users/user_granted_database_app.html:38 #: users/templates/users/user_granted_remote_app.html:37 #: users/templates/users/user_group_detail.html:62 @@ -99,6 +100,19 @@ msgstr "备注" msgid "DatabaseApp" msgstr "数据库应用" +#: applications/models/k8s_app.py:9 +msgid "Kubernetes" +msgstr "" + +#: applications/models/k8s_app.py:16 assets/models/cluster.py:40 +msgid "Cluster" +msgstr "集群" + +#: applications/models/k8s_app.py:26 perms/models/k8s_app_permission.py:18 +#: perms/utils/k8s_app_permission.py:70 +msgid "KubernetesApp" +msgstr "Kubernetes应用" + #: applications/models/remote_app.py:23 assets/models/asset.py:352 #: assets/models/authbook.py:26 assets/models/gathered_user.py:14 #: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:47 @@ -219,12 +233,12 @@ msgid "Hostname" msgstr "主机名" #: assets/models/asset.py:190 assets/models/domain.py:52 -#: assets/models/user.py:114 terminal/serializers/session.py:29 +#: assets/models/user.py:116 terminal/serializers/session.py:29 msgid "Protocol" msgstr "协议" #: assets/models/asset.py:192 assets/serializers/asset.py:69 -#: perms/serializers/user_permission.py:60 +#: perms/serializers/user_permission.py:71 msgid "Protocols" msgstr "协议组" @@ -233,7 +247,7 @@ msgstr "协议组" msgid "Domain" msgstr "网域" -#: assets/models/asset.py:195 assets/models/user.py:109 +#: assets/models/asset.py:195 assets/models/user.py:111 #: perms/models/asset_permission.py:91 #: xpack/plugins/change_auth_plan/models.py:56 #: xpack/plugins/gathered_user/models.py:24 @@ -427,10 +441,6 @@ msgstr "系统" msgid "Default Cluster" msgstr "默认Cluster" -#: assets/models/cluster.py:40 -msgid "Cluster" -msgstr "集群" - #: assets/models/cluster.py:56 msgid "Beijing unicom" msgstr "北京联通" @@ -443,7 +453,7 @@ msgstr "北京电信" msgid "BGP full netcom" msgstr "BGP全网通" -#: assets/models/cmd_filter.py:33 assets/models/user.py:119 +#: assets/models/cmd_filter.py:33 assets/models/user.py:121 msgid "Command filter" msgstr "命令过滤器" @@ -468,7 +478,7 @@ msgstr "允许" msgid "Filter" msgstr "过滤器" -#: assets/models/cmd_filter.py:53 assets/models/user.py:113 +#: assets/models/cmd_filter.py:53 assets/models/user.py:115 msgid "Priority" msgstr "优先级" @@ -597,57 +607,62 @@ msgstr "键" msgid "Node" msgstr "节点" -#: assets/models/user.py:105 +#: assets/models/user.py:107 msgid "Automatic login" msgstr "自动登录" -#: assets/models/user.py:106 +#: assets/models/user.py:108 msgid "Manually login" msgstr "手动登录" -#: assets/models/user.py:108 +#: assets/models/user.py:110 msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/user.py:110 templates/_nav.html:39 +#: assets/models/user.py:112 templates/_nav.html:39 #: xpack/plugins/change_auth_plan/models.py:52 msgid "Assets" msgstr "资产管理" -#: assets/models/user.py:111 templates/_nav.html:17 +#: assets/models/user.py:113 templates/_nav.html:17 #: users/views/profile/password.py:42 users/views/profile/pubkey.py:36 msgid "Users" msgstr "用户管理" -#: assets/models/user.py:112 users/templates/users/user_group_list.html:90 +#: assets/models/user.py:114 users/templates/users/user_group_list.html:90 #: users/templates/users/user_profile.html:124 msgid "User groups" msgstr "用户组" -#: assets/models/user.py:115 +#: assets/models/user.py:117 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:116 +#: assets/models/user.py:118 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:117 +#: assets/models/user.py:119 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:118 +#: assets/models/user.py:120 msgid "Login mode" msgstr "登录模式" -#: assets/models/user.py:120 +#: assets/models/user.py:122 msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:195 audits/models.py:39 +#: assets/models/user.py:123 authentication/models.py:88 +msgid "Token" +msgstr "" + +#: assets/models/user.py:198 audits/models.py:39 #: perms/forms/asset_permission.py:95 perms/forms/remote_app_permission.py:49 #: perms/models/asset_permission.py:92 #: perms/models/database_app_permission.py:22 +#: perms/models/k8s_app_permission.py:22 #: perms/models/remote_app_permission.py:16 templates/_nav.html:45 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:14 terminal/models.py:189 @@ -1212,10 +1227,6 @@ msgstr "登录复核" msgid "City" msgstr "城市" -#: authentication/models.py:88 -msgid "Token" -msgstr "" - #: authentication/models.py:89 msgid "Expired" msgstr "过期时间" @@ -1780,6 +1791,10 @@ msgstr "失效日期" msgid "DatabaseApp permission" msgstr "数据库应用授权" +#: perms/models/k8s_app_permission.py:27 +msgid "KubernetesApp permission" +msgstr "Kubernetes应用授权" + #: perms/models/remote_app_permission.py:20 #: users/templates/users/_user_detail_nav_header.html:47 msgid "RemoteApp permission" diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index cba965d00..dc0282725 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -12,3 +12,6 @@ from .database_app_permission import * from .database_app_permission_relation import * from .user_database_app_permission import * from .system_user_permission import * +from .k8s_app_permission import * +from .k8s_app_permission_relation import * +from .user_k8s_app_permission import * diff --git a/apps/perms/api/k8s_app_permission.py b/apps/perms/api/k8s_app_permission.py new file mode 100644 index 000000000..b1111bd7b --- /dev/null +++ b/apps/perms/api/k8s_app_permission.py @@ -0,0 +1,21 @@ +# coding: utf-8 +# + +from orgs.mixins.api import OrgBulkModelViewSet + +from .. import models, serializers +from common.permissions import IsOrgAdmin + + +__all__ = ['K8sAppPermissionViewSet'] + + +class K8sAppPermissionViewSet(OrgBulkModelViewSet): + model = models.K8sAppPermission + serializer_classes = { + 'default': serializers.K8sAppPermissionSerializer, + 'display': serializers.K8sAppPermissionListSerializer + } + filter_fields = ('name',) + search_fields = filter_fields + permission_classes = (IsOrgAdmin,) diff --git a/apps/perms/api/k8s_app_permission_relation.py b/apps/perms/api/k8s_app_permission_relation.py new file mode 100644 index 000000000..443f32204 --- /dev/null +++ b/apps/perms/api/k8s_app_permission_relation.py @@ -0,0 +1,111 @@ +# coding: utf-8 +# +from rest_framework import generics +from django.db.models import F, Value +from django.db.models.functions import Concat +from django.shortcuts import get_object_or_404 + +from common.permissions import IsOrgAdmin +from .base import RelationViewSet +from .. import models, serializers + + +class K8sAppPermissionUserRelationViewSet(RelationViewSet): + serializer_class = serializers.K8sAppPermissionUserRelationSerializer + m2m_field = models.K8sAppPermission.users.field + permission_classes = (IsOrgAdmin,) + filter_fields = [ + 'id', 'user', 'k8sapppermission' + ] + search_fields = ('user__name', 'user__username', 'k8sapppermission__name') + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.annotate(user_display=F('user__name')) + return queryset + + +class K8sAppPermissionUserGroupRelationViewSet(RelationViewSet): + serializer_class = serializers.K8sAppPermissionUserGroupRelationSerializer + m2m_field = models.K8sAppPermission.user_groups.field + permission_classes = (IsOrgAdmin,) + filter_fields = [ + 'id', "usergroup", "k8sapppermission" + ] + search_fields = ["usergroup__name", "k8sapppermission__name"] + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset \ + .annotate(usergroup_display=F('usergroup__name')) + return queryset + + +class K8sAppPermissionAllUserListApi(generics.ListAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = serializers.K8sAppPermissionAllUserSerializer + filter_fields = ("username", "name") + search_fields = filter_fields + + def get_queryset(self): + pk = self.kwargs.get("pk") + perm = get_object_or_404(models.K8sAppPermission, pk=pk) + users = perm.get_all_users().only( + *self.serializer_class.Meta.only_fields + ) + return users + + +class K8sAppPermissionK8sAppRelationViewSet(RelationViewSet): + serializer_class = serializers.K8sAppPermissionK8sAppRelationSerializer + m2m_field = models.K8sAppPermission.k8s_apps.field + permission_classes = (IsOrgAdmin,) + filter_fields = [ + 'id', 'k8sapp', 'k8sapppermission', + ] + search_fields = [ + "id", "k8sapp__name", "k8sapppermission__name" + ] + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset \ + .annotate(k8sapp_display=F('k8sapp__name')) + return queryset + + +class K8sAppPermissionAllK8sAppListApi(generics.ListAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = serializers.K8sAppPermissionAllK8sAppSerializer + filter_fields = ("name",) + search_fields = filter_fields + + def get_queryset(self): + pk = self.kwargs.get("pk") + perm = get_object_or_404(models.K8sAppPermission, pk=pk) + database_apps = perm.get_all_k8s_apps().only( + *self.serializer_class.Meta.only_fields + ) + return database_apps + + +class K8sAppPermissionSystemUserRelationViewSet(RelationViewSet): + serializer_class = serializers.K8sAppPermissionSystemUserRelationSerializer + m2m_field = models.K8sAppPermission.system_users.field + permission_classes = (IsOrgAdmin,) + filter_fields = [ + 'id', 'systemuser', 'k8sapppermission' + ] + search_fields = [ + 'k8sapppermission__name', 'systemuser__name', 'systemuser__username' + ] + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.annotate( + systemuser_display=Concat( + F('systemuser__name'), Value('('), F('systemuser__username'), + Value(')') + ) + ) + return queryset diff --git a/apps/perms/api/user_k8s_app_permission.py b/apps/perms/api/user_k8s_app_permission.py new file mode 100644 index 000000000..60f28d84c --- /dev/null +++ b/apps/perms/api/user_k8s_app_permission.py @@ -0,0 +1,119 @@ +# coding: utf-8 +# + +import uuid +from django.shortcuts import get_object_or_404 +from rest_framework.views import APIView, Response +from common.permissions import IsOrgAdminOrAppUser, IsValidUser +from common.tree import TreeNodeSerializer +from orgs.mixins import generics +from users.models import User, UserGroup +from applications.serializers import K8sAppSerializer +from applications.models import K8sApp +from assets.models import SystemUser +from .. import utils, serializers +from .mixin import UserPermissionMixin + + +class UserGrantedK8sAppsApi(generics.ListAPIView): + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = K8sAppSerializer + filter_fields = ['id', 'name', 'type', 'comment'] + search_fields = ['name', 'comment'] + + def get_object(self): + user_id = self.kwargs.get('pk', '') + if user_id: + user = get_object_or_404(User, id=user_id) + else: + user = self.request.user + return user + + def get_queryset(self): + util = utils.K8sAppPermissionUtil(self.get_object()) + queryset = util.get_k8s_apps() + return queryset + + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() + + +class UserGrantedK8sAppsAsTreeApi(UserGrantedK8sAppsApi): + serializer_class = TreeNodeSerializer + permission_classes = (IsOrgAdminOrAppUser,) + + def get_serializer(self, k8s_apps, *args, **kwargs): + if k8s_apps is None: + k8s_apps = [] + only_k8s_app = self.request.query_params.get('only', '0') == '1' + tree_root = None + data = [] + if not only_k8s_app: + tree_root = utils.construct_k8s_apps_tree_root() + data.append(tree_root) + for k8s_app in k8s_apps: + node = utils.parse_k8s_app_to_tree_node(tree_root, k8s_app) + data.append(node) + data.sort() + return super().get_serializer(data, many=True) + + +class UserGrantedK8sAppSystemUsersApi(UserPermissionMixin, generics.ListAPIView): + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = serializers.K8sAppSystemUserSerializer + only_fields = serializers.K8sAppSystemUserSerializer.Meta.only_fields + + def get_queryset(self): + util = utils.K8sAppPermissionUtil(self.obj) + k8s_app_id = self.kwargs.get('k8s_app_id') + k8s_app = get_object_or_404(K8sApp, id=k8s_app_id) + system_users = util.get_k8s_app_system_users(k8s_app) + return system_users + + +# Validate + +class ValidateUserK8sAppPermissionApi(APIView): + permission_classes = (IsOrgAdminOrAppUser,) + + def get(self, request, *args, **kwargs): + user_id = request.query_params.get('user_id', '') + k8s_app_id = request.query_params.get('k8s_app_id', '') + system_user_id = request.query_params.get('system_user_id', '') + + try: + user_id = uuid.UUID(user_id) + k8s_app_id = uuid.UUID(k8s_app_id) + system_user_id = uuid.UUID(system_user_id) + except ValueError: + return Response({'msg': False}, status=403) + + user = get_object_or_404(User, id=user_id) + k8s_app = get_object_or_404(K8sApp, id=k8s_app_id) + system_user = get_object_or_404(SystemUser, id=system_user_id) + + util = utils.K8sAppPermissionUtil(user) + system_users = util.get_k8s_app_system_users(k8s_app) + if system_user in system_users: + return Response({'msg': True}, status=200) + + return Response({'msg': False}, status=403) + + +# UserGroup + +class UserGroupGrantedK8sAppsApi(generics.ListAPIView): + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = K8sAppSerializer + + def get_queryset(self): + queryset = [] + user_group_id = self.kwargs.get('pk') + if not user_group_id: + return queryset + user_group = get_object_or_404(UserGroup, id=user_group_id) + util = utils.K8sAppPermissionUtil(user_group) + queryset = util.get_k8s_apps() + return queryset diff --git a/apps/perms/migrations/0012_k8sapppermission.py b/apps/perms/migrations/0012_k8sapppermission.py new file mode 100644 index 000000000..c13b75ad1 --- /dev/null +++ b/apps/perms/migrations/0012_k8sapppermission.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.13 on 2020-08-07 07:13 + +import common.utils.django +from django.conf import settings +from django.db import migrations, models +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0054_auto_20200807_1032'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('applications', '0005_k8sapp'), + ('users', '0028_auto_20200728_1805'), + ('perms', '0011_auto_20200721_1739'), + ] + + operations = [ + migrations.CreateModel( + name='K8sAppPermission', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('is_active', models.BooleanField(default=True, verbose_name='Active')), + ('date_start', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Date start')), + ('date_expired', models.DateTimeField(db_index=True, default=common.utils.django.date_expired_default, verbose_name='Date expired')), + ('created_by', models.CharField(blank=True, max_length=128, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('k8s_apps', models.ManyToManyField(blank=True, related_name='granted_by_permissions', to='applications.K8sApp', verbose_name='KubernetesApp')), + ('system_users', models.ManyToManyField(related_name='granted_by_k8s_app_permissions', to='assets.SystemUser', verbose_name='System user')), + ('user_groups', models.ManyToManyField(blank=True, to='users.UserGroup', verbose_name='User group')), + ('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'KubernetesApp permission', + 'ordering': ('name',), + 'unique_together': {('org_id', 'name')}, + }, + ), + ] diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py index c6581b858..264c14787 100644 --- a/apps/perms/models/__init__.py +++ b/apps/perms/models/__init__.py @@ -4,3 +4,4 @@ from .asset_permission import * from .remote_app_permission import * from .database_app_permission import * +from .k8s_app_permission import * diff --git a/apps/perms/models/k8s_app_permission.py b/apps/perms/models/k8s_app_permission.py new file mode 100644 index 000000000..b6c7106bc --- /dev/null +++ b/apps/perms/models/k8s_app_permission.py @@ -0,0 +1,39 @@ +# coding: utf-8 +# + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from common.utils import lazyproperty +from .base import BasePermission + +__all__ = [ + 'K8sAppPermission', +] + + +class K8sAppPermission(BasePermission): + k8s_apps = models.ManyToManyField( + 'applications.K8sApp', related_name='granted_by_permissions', + blank=True, verbose_name=_("KubernetesApp") + ) + system_users = models.ManyToManyField( + 'assets.SystemUser', related_name='granted_by_k8s_app_permissions', + verbose_name=_("System user") + ) + + class Meta: + unique_together = [('org_id', 'name')] + verbose_name = _('KubernetesApp permission') + ordering = ('name',) + + def get_all_k8s_apps(self): + return self.k8s_apps.all() + + @lazyproperty + def k8s_apps_amount(self): + return self.k8s_apps.count() + + @lazyproperty + def system_users_amount(self): + return self.system_users.count() diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py index 43f221d6e..e233a4d5b 100644 --- a/apps/perms/serializers/__init__.py +++ b/apps/perms/serializers/__init__.py @@ -9,3 +9,5 @@ from .asset_permission_relation import * from .database_app_permission import * from .database_app_permission_relation import * from .base import * +from .k8s_app_permission import * +from .k8s_app_permission_relation import * diff --git a/apps/perms/serializers/k8s_app_permission.py b/apps/perms/serializers/k8s_app_permission.py new file mode 100644 index 000000000..0c836bc27 --- /dev/null +++ b/apps/perms/serializers/k8s_app_permission.py @@ -0,0 +1,50 @@ +# coding: utf-8 +# +from django.db.models import Count +from rest_framework import serializers + +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from .. import models + +__all__ = [ + 'K8sAppPermissionSerializer', 'K8sAppPermissionListSerializer' +] + + +class AmountMixin: + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate( + users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True), + k8s_apps_amount=Count('k8s_apps', distinct=True), + system_users_amount=Count('system_users', distinct=True) + ) + return queryset + + +class K8sAppPermissionSerializer(AmountMixin, BulkOrgResourceModelSerializer): + class Meta: + model = models.K8sAppPermission + fields = [ + 'id', 'name', 'users', 'user_groups', 'k8s_apps', 'system_users', + 'comment', 'is_active', 'date_start', 'date_expired', 'is_valid', + 'created_by', 'date_created', 'users_amount', 'user_groups_amount', + 'k8s_apps_amount', 'system_users_amount', + ] + read_only_fields = [ + 'created_by', 'date_created', 'users_amount', 'user_groups_amount', + 'k8s_apps_amount', 'system_users_amount', 'id' + ] + + +class K8sAppPermissionListSerializer(AmountMixin, BulkOrgResourceModelSerializer): + is_expired = serializers.BooleanField() + + class Meta: + model = models.K8sAppPermission + fields = [ + 'id', 'name', 'comment', 'is_active', 'users_amount', 'user_groups_amount', + 'date_start', 'date_expired', 'is_valid', 'k8s_apps_amount', 'system_users_amount', + 'created_by', 'date_created', 'is_expired' + ] diff --git a/apps/perms/serializers/k8s_app_permission_relation.py b/apps/perms/serializers/k8s_app_permission_relation.py new file mode 100644 index 000000000..e5786ca50 --- /dev/null +++ b/apps/perms/serializers/k8s_app_permission_relation.py @@ -0,0 +1,73 @@ +# coding: utf-8 +# +from perms.serializers.base import PermissionAllUserSerializer +from rest_framework import serializers + +from common.drf.serializers import BulkModelSerializer + +from .. import models + + +class K8sAppPermissionUserRelationSerializer(BulkModelSerializer): + user_display = serializers.ReadOnlyField() + k8sapppermission_display = serializers.ReadOnlyField() + + class Meta: + model = models.K8sAppPermission.users.through + fields = [ + 'id', 'user', 'user_display', 'k8sapppermission', + 'k8sapppermission_display' + ] + + +class K8sAppPermissionUserGroupRelationSerializer(BulkModelSerializer): + usergroup_display = serializers.ReadOnlyField() + k8sapppermission_display = serializers.ReadOnlyField() + + class Meta: + model = models.K8sAppPermission.user_groups.through + fields = [ + 'id', 'usergroup', 'usergroup_display', 'k8sapppermission', + 'k8sapppermission_display' + ] + + +class K8sAppPermissionAllUserSerializer(PermissionAllUserSerializer): + class Meta(PermissionAllUserSerializer.Meta): + pass + + +class K8sAppPermissionK8sAppRelationSerializer(BulkModelSerializer): + k8sapp_display = serializers.ReadOnlyField() + k8sapppermission_display = serializers.ReadOnlyField() + + class Meta: + model = models.K8sAppPermission.k8s_apps.through + fields = [ + 'id', "k8sapp", "k8sapp_display", 'k8sapppermission', + 'k8sapppermission_display' + ] + + +class K8sAppPermissionAllK8sAppSerializer(serializers.Serializer): + k8sapp = serializers.UUIDField(read_only=True, source='id') + k8sapp_display = serializers.SerializerMethodField() + + class Meta: + only_fields = ['id', 'name'] + + @staticmethod + def get_k8sapp_display(obj): + return str(obj) + + +class K8sAppPermissionSystemUserRelationSerializer(BulkModelSerializer): + systemuser_display = serializers.ReadOnlyField() + k8sapppermission_display = serializers.ReadOnlyField() + + class Meta: + model = models.K8sAppPermission.system_users.through + fields = [ + 'id', 'systemuser', 'systemuser_display', 'k8sapppermission', + 'k8sapppermission_display' + ] diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index ac248947f..11d1bcfc7 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -14,6 +14,7 @@ __all__ = [ 'ActionsSerializer', 'AssetSystemUserSerializer', 'RemoteAppSystemUserSerializer', 'DatabaseAppSystemUserSerializer', + 'K8sAppSystemUserSerializer', ] @@ -53,6 +54,16 @@ class DatabaseAppSystemUserSerializer(serializers.ModelSerializer): read_only_fields = fields +class K8sAppSystemUserSerializer(serializers.ModelSerializer): + class Meta: + model = SystemUser + only_fields = ( + 'id', 'name', 'username', 'priority', 'protocol', 'login_mode', + ) + fields = list(only_fields) + read_only_fields = fields + + class AssetGrantedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index d70a824cc..f2b87ca72 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -6,6 +6,7 @@ from .asset_permission import asset_permission_urlpatterns from .remote_app_permission import remote_app_permission_urlpatterns from .database_app_permission import database_app_permission_urlpatterns from .system_user_permission import system_users_permission_urlpatterns +from .k8s_app_permission import k8s_app_permission_urlpatterns app_name = 'perms' @@ -16,5 +17,6 @@ old_version_urlpatterns = [ urlpatterns = asset_permission_urlpatterns + \ remote_app_permission_urlpatterns + \ database_app_permission_urlpatterns + \ + k8s_app_permission_urlpatterns + \ old_version_urlpatterns + \ system_users_permission_urlpatterns diff --git a/apps/perms/urls/k8s_app_permission.py b/apps/perms/urls/k8s_app_permission.py new file mode 100644 index 000000000..2c145948b --- /dev/null +++ b/apps/perms/urls/k8s_app_permission.py @@ -0,0 +1,45 @@ +# coding: utf-8 +# + +from django.urls import path, include +from rest_framework_bulk.routes import BulkRouter +from .. import api + + +router = BulkRouter() +router.register('k8s-app-permissions', api.K8sAppPermissionViewSet, 'k8s-app-permission') +router.register('k8s-app-permissions-users-relations', api.K8sAppPermissionUserRelationViewSet, 'k8s-app-permissions-users-relation') +router.register('k8s-app-permissions-user-groups-relations', api.K8sAppPermissionUserGroupRelationViewSet, 'k8s-app-permissions-user-groups-relation') +router.register('k8s-app-permissions-k8s-apps-relations', api.K8sAppPermissionK8sAppRelationViewSet, 'k8s-app-permissions-k8s-apps-relation') +router.register('k8s-app-permissions-system-users-relations', api.K8sAppPermissionSystemUserRelationViewSet, 'k8s-app-permissions-system-users-relation') + +user_permission_urlpatterns = [ + path('/k8s-apps/', api.UserGrantedK8sAppsApi.as_view(), name='user-k8s-apps'), + path('k8s-apps/', api.UserGrantedK8sAppsApi.as_view(), name='my-k8s-apps'), + + # k8sApps as tree + path('/k8s-apps/tree/', api.UserGrantedK8sAppsAsTreeApi.as_view(), name='user-k8ss-apps-tree'), + path('k8s-apps/tree/', api.UserGrantedK8sAppsAsTreeApi.as_view(), name='my-k8ss-apps-tree'), + + path('/k8s-apps//system-users/', api.UserGrantedK8sAppSystemUsersApi.as_view(), name='user-k8s-app-system-users'), + path('k8s-apps//system-users/', api.UserGrantedK8sAppSystemUsersApi.as_view(), name='user-k8s-app-system-users'), +] + +user_group_permission_urlpatterns = [ + path('/k8s-apps/', api.UserGroupGrantedK8sAppsApi.as_view(), name='user-group-k8s-apps'), +] + +permission_urlpatterns = [ + path('/users/all/', api.K8sAppPermissionAllUserListApi.as_view(), name='k8s-app-permission-all-users'), + path('/k8s-apps/all/', api.K8sAppPermissionAllK8sAppListApi.as_view(), name='k8s-app-permission-all-k8s-apps'), + + path('user/validate/', api.ValidateUserK8sAppPermissionApi.as_view(), name='validate-user-k8s-app-permission'), +] + +k8s_app_permission_urlpatterns = [ + path('users/', include(user_permission_urlpatterns)), + path('user-groups/', include(user_group_permission_urlpatterns)), + path('k8s-app-permissions/', include(permission_urlpatterns)) +] + +k8s_app_permission_urlpatterns += router.urls diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py index c6581b858..35e29adb6 100644 --- a/apps/perms/utils/__init__.py +++ b/apps/perms/utils/__init__.py @@ -4,3 +4,4 @@ from .asset_permission import * from .remote_app_permission import * from .database_app_permission import * +from .k8s_app_permission import * \ No newline at end of file diff --git a/apps/perms/utils/k8s_app_permission.py b/apps/perms/utils/k8s_app_permission.py new file mode 100644 index 000000000..578fa6380 --- /dev/null +++ b/apps/perms/utils/k8s_app_permission.py @@ -0,0 +1,93 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext as _ +from django.db.models import Q + +from orgs.utils import set_to_root_org +from ..models import K8sAppPermission +from common.tree import TreeNode +from applications.models import K8sApp +from assets.models import SystemUser + + +def get_user_k8s_app_permissions(user, include_group=True): + if include_group: + groups = user.groups.all() + arg = Q(users=user) | Q(user_groups__in=groups) + else: + arg = Q(users=user) + return K8sAppPermission.objects.all().valid().filter(arg) + + +def get_user_group_k8s_app_permission(user_group): + return K8sAppPermission.objects.all().valid().filter( + user_groups=user_group + ) + + +class K8sAppPermissionUtil: + get_permissions_map = { + 'User': get_user_k8s_app_permissions, + 'UserGroup': get_user_group_k8s_app_permission + } + + def __init__(self, obj): + self.object = obj + self.change_org_if_need() + + @staticmethod + def change_org_if_need(): + set_to_root_org() + + @property + def permissions(self): + obj_class = self.object.__class__.__name__ + func = self.get_permissions_map[obj_class] + _permissions = func(self.object) + return _permissions + + def get_k8s_apps(self): + k8s_apps = K8sApp.objects.filter( + granted_by_permissions__in=self.permissions + ).distinct() + return k8s_apps + + def get_k8s_app_system_users(self, k8s_app): + queryset = self.permissions + kwargs = {'k8s_apps': k8s_app} + queryset = queryset.filter(**kwargs) + system_users_ids = queryset.values_list('system_users', flat=True) + system_users_ids = system_users_ids.distinct() + system_users = SystemUser.objects.filter(id__in=system_users_ids) + system_users = system_users.order_by('-priority') + return system_users + + +def construct_k8s_apps_tree_root(): + tree_root = { + 'id': 'ID_K8S_APP_ROOT', + 'name': _('KubernetesApp'), + 'title': 'K8sApp', + 'pId': '', + 'open': False, + 'isParent': True, + 'iconSkin': '', + 'meta': {'type': 'k8s_app'} + } + return TreeNode(**tree_root) + + +def parse_k8s_app_to_tree_node(parent, k8s_app): + pid = parent.id if parent else '' + tree_node = { + 'id': k8s_app.id, + 'name': k8s_app.name, + 'title': k8s_app.name, + 'pId': pid, + 'open': False, + 'isParent': False, + 'iconSkin': 'file', + 'meta': {'type': 'k8s_app'} + } + return TreeNode(**tree_node) diff --git a/apps/terminal/migrations/0025_auto_20200810_1735.py b/apps/terminal/migrations/0025_auto_20200810_1735.py new file mode 100644 index 000000000..96a06915c --- /dev/null +++ b/apps/terminal/migrations/0025_auto_20200810_1735.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.13 on 2020-08-10 09:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0024_auto_20200715_1713'), + ] + + operations = [ + migrations.AlterField( + model_name='session', + name='protocol', + field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('vnc', 'vnc'), ('telnet', 'telnet'), ('mysql', 'mysql'), ('k8s', 'kubernetes')], db_index=True, default='ssh', max_length=8), + ), + ] diff --git a/apps/terminal/models.py b/apps/terminal/models.py index f3914cc1c..ba43d131a 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -179,6 +179,7 @@ class Session(OrgModelMixin): ('vnc', 'vnc'), ('telnet', 'telnet'), ('mysql', 'mysql'), + ('k8s', 'kubernetes') ) id = models.UUIDField(default=uuid.uuid4, primary_key=True)