From 4847b7a6809034999d8eaf127d61af4b7c37bb8a Mon Sep 17 00:00:00 2001
From: Bai <bugatti_it@163.com>
Date: Wed, 21 Oct 2020 19:32:27 +0800
Subject: [PATCH] =?UTF-8?q?feat(perms):=20=E6=B7=BB=E5=8A=A0ApplicationPer?=
 =?UTF-8?q?mission=20Model=20=E5=92=8C=20API=EF=BC=88=E5=8C=85=E5=90=ABVie?=
 =?UTF-8?q?wSet=E5=92=8CRelationViewSet)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/perms/api/__init__.py                    |  2 +
 apps/perms/api/application_permission.py      | 29 ++++++
 .../api/application_permission_relation.py    | 98 +++++++++++++++++++
 .../migrations/0016_applicationpermission.py  | 44 +++++++++
 apps/perms/models/__init__.py                 |  1 +
 apps/perms/models/application_permission.py   | 38 +++++++
 apps/perms/serializers/__init__.py            |  2 +
 .../serializers/application_permission.py     | 38 +++++++
 .../application_permission_relation.py        | 69 +++++++++++++
 apps/perms/urls/api_urls.py                   |  2 +
 apps/perms/urls/application_permission.py     | 50 ++++++++++
 11 files changed, 373 insertions(+)
 create mode 100644 apps/perms/api/application_permission.py
 create mode 100644 apps/perms/api/application_permission_relation.py
 create mode 100644 apps/perms/migrations/0016_applicationpermission.py
 create mode 100644 apps/perms/models/application_permission.py
 create mode 100644 apps/perms/serializers/application_permission.py
 create mode 100644 apps/perms/serializers/application_permission_relation.py
 create mode 100644 apps/perms/urls/application_permission.py

diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py
index dc0282725..d5155aeef 100644
--- a/apps/perms/api/__init__.py
+++ b/apps/perms/api/__init__.py
@@ -2,8 +2,10 @@
 #
 
 from .asset_permission import *
+from .application_permission import *
 from .user_permission import *
 from .asset_permission_relation import *
+from .application_permission_relation import *
 from .user_group_permission import *
 from .remote_app_permission import *
 from .remote_app_permission_relation import *
diff --git a/apps/perms/api/application_permission.py b/apps/perms/api/application_permission.py
new file mode 100644
index 000000000..4421c48cd
--- /dev/null
+++ b/apps/perms/api/application_permission.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+#
+from django.db.models import Q
+
+from common.permissions import IsOrgAdmin
+from orgs.mixins.api import OrgModelViewSet
+from common.utils import get_object_or_none
+from ..models import ApplicationPermission
+from ..hands import (
+    User, UserGroup, Asset, Node, SystemUser,
+)
+from .. import serializers
+
+
+class ApplicationPermissionViewSet(OrgModelViewSet):
+    """
+    应用授权列表的增删改查API
+    """
+    model = ApplicationPermission
+    serializer_class = serializers.ApplicationPermissionSerializer
+    filter_fields = ['name']
+    permission_classes = (IsOrgAdmin,)
+
+    def get_queryset(self):
+        queryset = super().get_queryset().prefetch_related(
+            "applications", "users", "user_groups", "system_users"
+        )
+        return queryset
+
diff --git a/apps/perms/api/application_permission_relation.py b/apps/perms/api/application_permission_relation.py
new file mode 100644
index 000000000..ef1f9fd08
--- /dev/null
+++ b/apps/perms/api/application_permission_relation.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+#
+from rest_framework import generics
+from django.db.models import F, Value
+from django.db.models import Q
+from django.db.models.functions import Concat
+from django.shortcuts import get_object_or_404
+
+from assets.models import Node, Asset
+from orgs.mixins.api import OrgRelationMixin
+from orgs.mixins.api import OrgBulkModelViewSet
+from orgs.utils import current_org
+from common.permissions import IsOrgAdmin
+from .. import serializers
+from .. import models
+
+__all__ = [
+    'ApplicationPermissionUserRelationViewSet',
+    'ApplicationPermissionUserGroupRelationViewSet',
+    'ApplicationPermissionApplicationRelationViewSet',
+    'ApplicationPermissionSystemUserRelationViewSet'
+]
+
+
+class RelationMixin(OrgRelationMixin, OrgBulkModelViewSet):
+    def get_queryset(self):
+        queryset = super().get_queryset()
+        org_id = current_org.org_id()
+        if org_id is not None:
+            queryset = queryset.filter(applicationpermission__org_id=org_id)
+        queryset = queryset.annotate(applicationpermission_display=F('applicationpermission__name'))
+        return queryset
+
+
+class ApplicationPermissionUserRelationViewSet(RelationMixin):
+    serializer_class = serializers.ApplicationPermissionUserRelationSerializer
+    m2m_field = models.ApplicationPermission.users.field
+    permission_classes = (IsOrgAdmin,)
+    filter_fields = [
+        'id', "user", "applicationpermission",
+    ]
+    search_fields = ("user__name", "user__username", "applicationpermission__name")
+
+    def get_queryset(self):
+        queryset = super().get_queryset()
+        queryset = queryset.annotate(user_display=F('user__name'))
+        return queryset
+
+
+class ApplicationPermissionUserGroupRelationViewSet(RelationMixin):
+    serializer_class = serializers.ApplicationPermissionUserGroupRelationSerializer
+    m2m_field = models.ApplicationPermission.user_groups.field
+    permission_classes = (IsOrgAdmin,)
+    filter_fields = [
+        'id', "usergroup", "applicationpermission"
+    ]
+    search_fields = ["usergroup__name", "applicationpermission__name"]
+
+    def get_queryset(self):
+        queryset = super().get_queryset()
+        queryset = queryset.annotate(usergroup_display=F('usergroup__name'))
+        return queryset
+
+
+class ApplicationPermissionApplicationRelationViewSet(RelationMixin):
+    serializer_class = serializers.ApplicationPermissionApplicationRelationSerializer
+    m2m_field = models.ApplicationPermission.applications.field
+    permission_classes = (IsOrgAdmin,)
+    filter_fields = [
+        'id', 'application', 'applicationpermission',
+    ]
+    search_fields = ["id", "application__name", "applicationpermission__name"]
+
+    def get_queryset(self):
+        queryset = super().get_queryset()
+        queryset = queryset.annotate(application_display=F('application__name'))
+        return queryset
+
+
+class ApplicationPermissionSystemUserRelationViewSet(RelationMixin):
+    serializer_class = serializers.ApplicationPermissionSystemUserRelationSerializer
+    m2m_field = models.ApplicationPermission.system_users.field
+    permission_classes = (IsOrgAdmin,)
+    filter_fields = [
+        'id', 'systemuser', 'applicationpermission',
+    ]
+    search_fields = [
+        "applicactionpermission__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/migrations/0016_applicationpermission.py b/apps/perms/migrations/0016_applicationpermission.py
new file mode 100644
index 000000000..c7d922696
--- /dev/null
+++ b/apps/perms/migrations/0016_applicationpermission.py
@@ -0,0 +1,44 @@
+# Generated by Django 2.2.13 on 2020-10-21 07:14
+
+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 = [
+        ('users', '0030_auto_20200819_2041'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('applications', '0006_application'),
+        ('assets', '0057_fill_node_value_assets_amount_and_parent_key'),
+        ('perms', '0015_auto_20200929_1728'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ApplicationPermission',
+            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')),
+                ('applications', models.ManyToManyField(blank=True, related_name='granted_by_permissions', to='applications.Application', verbose_name='Application')),
+                ('system_users', models.ManyToManyField(related_name='granted_by_application_permissions', to='assets.SystemUser', verbose_name='System user')),
+                ('user_groups', models.ManyToManyField(blank=True, related_name='applicationpermissions', to='users.UserGroup', verbose_name='User group')),
+                ('users', models.ManyToManyField(blank=True, related_name='applicationpermissions', to=settings.AUTH_USER_MODEL, verbose_name='User')),
+            ],
+            options={
+                'verbose_name': 'Application permission',
+                'ordering': ('name',),
+                'unique_together': {('org_id', 'name')},
+            },
+        ),
+    ]
diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py
index 264c14787..e7b0b0ffb 100644
--- a/apps/perms/models/__init__.py
+++ b/apps/perms/models/__init__.py
@@ -2,6 +2,7 @@
 #
 
 from .asset_permission import *
+from .application_permission import *
 from .remote_app_permission import *
 from .database_app_permission import *
 from .k8s_app_permission import *
diff --git a/apps/perms/models/application_permission.py b/apps/perms/models/application_permission.py
new file mode 100644
index 000000000..db586c35f
--- /dev/null
+++ b/apps/perms/models/application_permission.py
@@ -0,0 +1,38 @@
+# 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__ = [
+    'ApplicationPermission',
+]
+
+
+class ApplicationPermission(BasePermission):
+    applications = models.ManyToManyField('applications.Application', related_name='granted_by_permissions', blank=True, verbose_name=_("Application"))
+    system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_application_permissions', verbose_name=_("System user"))
+
+    class Meta:
+        unique_together = [('org_id', 'name')]
+        verbose_name = _('Application permission')
+        ordering = ('name',)
+
+    @lazyproperty
+    def users_amount(self):
+        return self.users.count()
+
+    @lazyproperty
+    def user_groups_amount(self):
+        return self.user_groups.count()
+
+    @lazyproperty
+    def applications_amount(self):
+        return self.applications.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 e233a4d5b..0c3cf741d 100644
--- a/apps/perms/serializers/__init__.py
+++ b/apps/perms/serializers/__init__.py
@@ -2,10 +2,12 @@
 #
 from .system_user_permission import *
 from .asset_permission import *
+from .application_permission import *
 from .user_permission import *
 from .remote_app_permission import *
 from .remote_app_permission_relation import *
 from .asset_permission_relation import *
+from .application_permission_relation import *
 from .database_app_permission import *
 from .database_app_permission_relation import *
 from .base import *
diff --git a/apps/perms/serializers/application_permission.py b/apps/perms/serializers/application_permission.py
new file mode 100644
index 000000000..916937f05
--- /dev/null
+++ b/apps/perms/serializers/application_permission.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+#
+
+from rest_framework import serializers
+
+from django.db.models import Count
+from orgs.mixins.serializers import BulkOrgResourceModelSerializer
+from perms.models import ApplicationPermission
+
+__all__ = [
+    'ApplicationPermissionSerializer'
+]
+
+
+class ApplicationPermissionSerializer(BulkOrgResourceModelSerializer):
+    is_valid = serializers.BooleanField(read_only=True)
+    is_expired = serializers.BooleanField(read_only=True)
+
+    class Meta:
+        model = ApplicationPermission
+        mini_fields = ['id', 'name']
+        small_fields = mini_fields + [
+            'is_active', 'is_expired', 'is_valid', 'created_by', 'date_created',
+            'date_expired', 'date_start', 'comment'
+        ]
+        m2m_fields = [
+            'users', 'user_groups', 'applications', 'system_users',
+            'users_amount', 'user_groups_amount', 'applications_amount', 'system_users_amount',
+        ]
+        fields = small_fields + m2m_fields
+        read_only_fields = ['created_by', 'date_created']
+
+    @classmethod
+    def setup_eager_loading(cls, queryset):
+        """ Perform necessary eager loading of data. """
+        queryset = queryset.prefetch_related('users', 'user_groups', 'applications', 'system_users')
+        return queryset
+
diff --git a/apps/perms/serializers/application_permission_relation.py b/apps/perms/serializers/application_permission_relation.py
new file mode 100644
index 000000000..4d9abd982
--- /dev/null
+++ b/apps/perms/serializers/application_permission_relation.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+#
+from rest_framework import serializers
+
+from common.mixins import BulkSerializerMixin
+from common.serializers import AdaptedBulkListSerializer
+from assets.models import Asset, Node
+from ..models import ApplicationPermission
+from users.models import User
+
+__all__ = [
+    'ApplicationPermissionUserRelationSerializer',
+    'ApplicationPermissionUserGroupRelationSerializer',
+    'ApplicationPermissionApplicationRelationSerializer',
+    'ApplicationPermissionSystemUserRelationSerializer'
+]
+
+
+class RelationMixin(BulkSerializerMixin, serializers.Serializer):
+    applicationpermission_display = serializers.ReadOnlyField()
+
+    def get_field_names(self, declared_fields, info):
+        fields = super().get_field_names(declared_fields, info)
+        fields.extend(['applicationpermission', "applicationpermission_display"])
+        return fields
+
+    class Meta:
+        list_serializer_class = AdaptedBulkListSerializer
+
+
+class ApplicationPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
+    user_display = serializers.ReadOnlyField()
+
+    class Meta(RelationMixin.Meta):
+        model = ApplicationPermission.users.through
+        fields = [
+            'id', 'user', 'user_display',
+        ]
+
+
+class ApplicationPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer):
+    usergroup_display = serializers.ReadOnlyField()
+
+    class Meta(RelationMixin.Meta):
+        model = ApplicationPermission.user_groups.through
+        fields = [
+            'id', 'usergroup', "usergroup_display",
+        ]
+
+
+class ApplicationPermissionApplicationRelationSerializer(RelationMixin, serializers.ModelSerializer):
+    application_display = serializers.ReadOnlyField()
+
+    class Meta(RelationMixin.Meta):
+        model = ApplicationPermission.applications.through
+        fields = [
+            'id', "application", "application_display",
+        ]
+
+
+class ApplicationPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
+    systemuser_display = serializers.ReadOnlyField()
+
+    class Meta(RelationMixin.Meta):
+        model = ApplicationPermission.system_users.through
+        fields = [
+            'id', 'systemuser', 'systemuser_display'
+        ]
+
diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py
index f2b87ca72..3559703a9 100644
--- a/apps/perms/urls/api_urls.py
+++ b/apps/perms/urls/api_urls.py
@@ -3,6 +3,7 @@
 from django.urls import re_path
 from common import api as capi
 from .asset_permission import asset_permission_urlpatterns
+from .application_permission import application_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
@@ -15,6 +16,7 @@ old_version_urlpatterns = [
 ]
 
 urlpatterns = asset_permission_urlpatterns + \
+              application_permission_urlpatterns + \
               remote_app_permission_urlpatterns + \
               database_app_permission_urlpatterns + \
               k8s_app_permission_urlpatterns + \
diff --git a/apps/perms/urls/application_permission.py b/apps/perms/urls/application_permission.py
new file mode 100644
index 000000000..bd37501c9
--- /dev/null
+++ b/apps/perms/urls/application_permission.py
@@ -0,0 +1,50 @@
+# coding: utf-8
+#
+
+from django.urls import path, include
+from rest_framework_bulk.routes import BulkRouter
+from .. import api
+
+
+router = BulkRouter()
+router.register('application-permissions', api.ApplicationPermissionViewSet, 'application-permission')
+router.register('application-permissions-users-relations', api.ApplicationPermissionUserRelationViewSet, 'application-permissions-users-relation')
+router.register('application-permissions-user-groups-relations', api.ApplicationPermissionUserGroupRelationViewSet, 'application-permissions-user-groups-relation')
+router.register('application-permissions-applications-relations', api.ApplicationPermissionApplicationRelationViewSet, 'application-permissions-application-relation')
+router.register('application-permissions-system-users-relations', api.ApplicationPermissionSystemUserRelationViewSet, 'application-permissions-system-users-relation')
+
+"""
+user_permission_urlpatterns = [
+    path('<uuid:pk>/applications/', api.UserGrantedApplicationsApi.as_view(), name='user-applications'),
+    path('applications/', api.UserGrantedApplicationsApi.as_view(), name='my-applications'),
+
+    # Application as tree
+    path('<uuid:pk>/applications/tree/', api.UserGrantedApplicationsAsTreeApi.as_view(), name='user-applications-as-tree'),
+    path('applications/tree/', api.UserGrantedApplicationsAsTreeApi.as_view(), name='my-applications-as-tree'),
+
+    path('<uuid:pk>/applications/<uuid:application_id>/system-users/', api.UserGrantedApplicationSystemUsersApi.as_view(), name='user-application-system-users'),
+    path('applications/<uuid:application_id>/system-users/', api.UserGrantedApplicationSystemUsersApi.as_view(), name='user-application-system-users'),
+]
+
+user_group_permission_urlpatterns = [
+    path('<uuid:pk>/applications/', api.UserGroupGrantedApplicationsApi.as_view(), name='user-group-applications'),
+]
+
+permission_urlpatterns = [
+    # 授权规则中授权的用户和数据库应用
+    path('<uuid:pk>/applications/all/', api.ApplicationPermissionAllApplicationListApi.as_view(), name='application-permission-all-applications'),
+    path('<uuid:pk>/users/all/', api.ApplicationPermissionAllUserListApi.as_view(), name='application-permission-all-users'),
+
+    # 验证用户是否有某个数据库应用的权限
+    path('user/validate/', api.ValidateUserApplicationPermissionApi.as_view(), name='validate-user-application-permission'),
+]
+
+application_permission_urlpatterns = [
+    path('users/', include(user_permission_urlpatterns)),
+    path('user-groups/', include(user_group_permission_urlpatterns)),
+    path('application-permissions/', include(permission_urlpatterns))
+]
+
+application_permission_urlpatterns += router.urls
+"""
+application_permission_urlpatterns = router.urls