fix: fix rbac to dev (#7636)

* feat: 添加 RBAC 应用模块

* feat: 添加 RBAC Model、API

* feat: 添加 RBAC Model、API 2

* feat: 添加 RBAC Model、API 3

* feat: 添加 RBAC Model、API 4

* feat: RBAC

* feat: RBAC

* feat: RBAC

* feat: RBAC

* feat: RBAC

* feat: RBAC 整理权限位

* feat: RBAC 整理权限位2

* feat: RBAC 整理权限位2

* feat: RBAC 整理权限位

* feat: RBAC 添加默认角色

* feat: RBAC 添加迁移文件;迁移用户角色->用户角色绑定

* feat: RBAC 添加迁移文件;迁移用户角色->用户角色绑定

* feat: RBAC 修改用户模块API

* feat: RBAC 添加组织模块迁移文件 & 修改组织模块API

* feat: RBAC 添加组织模块迁移文件 & 修改组织模块API

* feat: RBAC 修改用户角色属性的使用

* feat: RBAC No.1

* xxx

* perf: 暂存

* perf: ...

* perf(rbac): 添加 perms 到 profile serializer 中

* stash

* perf: 使用init

* perf: 修改migrations

* perf: rbac

* stash

* stash

* pref: 修改rbac

* stash it

* stash: 先去修复其他bug

* perf: 修改 role 添加 users

* pref: 修改 RBAC Model

* feat: 添加权限的 tree api

* stash: 暂存一下

* stash: 暂存一下

* perf: 修改 model verbose name

* feat: 添加model各种 verbose name

* perf: 生成 migrations

* perf: 优化权限位

* perf: 添加迁移脚本

* feat: 添加组织角色迁移

* perf: 添加迁移脚本

* stash

* perf: 添加migrateion

* perf: 暂存一下

* perf: 修改rbac

* perf: stash it

* fix: 迁移冲突

* fix: 迁移冲突

* perf: 暂存一下

* perf: 修改 rbac 逻辑

* stash: 暂存一下

* perf: 修改内置角色

* perf: 解决 root 组织的问题

* perf: stash it

* perf: 优化 rbac

* perf: 优化 rolebinding 处理

* perf: 完成用户离开组织的问题

* perf: 暂存一下

* perf: 修改翻译

* perf: 去掉了 IsSuperUser

* perf: IsAppUser 去掉完成

* perf: 修改 connection token 的权限

* perf: 去掉导入的问题

* perf: perms define 格式,修改 app 用户 的全新啊

* perf: 修改 permission

* perf: 去掉一些 org admin

* perf: 去掉部分 org admin

* perf: 再去掉点 org admin role

* perf: 再去掉部分 org admin

* perf: user 角色搜索

* perf: 去掉很多 js

* perf: 添加权限位

* perf: 修改权限

* perf: 去掉一个 todo

* merge: with dev

* fix: 修复冲突

Co-authored-by: Bai <bugatti_it@163.com>
Co-authored-by: Michael Bai <baijiangjie@gmail.com>
Co-authored-by: ibuler <ibuler@qq.com>
This commit is contained in:
fit2bot 2022-02-17 20:13:31 +08:00 committed by GitHub
parent b088362ae3
commit e259d2a9e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
263 changed files with 3049 additions and 62465 deletions

View File

@ -1,20 +1,14 @@
from common.permissions import IsOrgAdmin, HasQueryParamsUserAndIsCurrentOrgMember
from common.drf.api import JMSBulkModelViewSet from common.drf.api import JMSBulkModelViewSet
from ..models import LoginACL from ..models import LoginACL
from .. import serializers from .. import serializers
from ..filters import LoginAclFilter from ..filters import LoginAclFilter
__all__ = ['LoginACLViewSet', ] __all__ = ['LoginACLViewSet']
class LoginACLViewSet(JMSBulkModelViewSet): class LoginACLViewSet(JMSBulkModelViewSet):
queryset = LoginACL.objects.all() queryset = LoginACL.objects.all()
filterset_class = LoginAclFilter filterset_class = LoginAclFilter
search_fields = ('name',) search_fields = ('name',)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LoginACLSerializer serializer_class = serializers.LoginACLSerializer
def get_permissions(self):
if self.action in ["retrieve", "list"]:
self.permission_classes = (IsOrgAdmin, HasQueryParamsUserAndIsCurrentOrgMember)
return super().get_permissions()

View File

@ -1,5 +1,4 @@
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from common.permissions import IsOrgAdmin
from .. import models, serializers from .. import models, serializers
@ -10,5 +9,4 @@ class LoginAssetACLViewSet(OrgBulkModelViewSet):
model = models.LoginAssetACL model = models.LoginAssetACL
filterset_fields = ('name', ) filterset_fields = ('name', )
search_fields = filterset_fields search_fields = filterset_fields
permission_classes = (IsOrgAdmin, )
serializer_class = serializers.LoginAssetACLSerializer serializer_class = serializers.LoginAssetACLSerializer

View File

@ -1,7 +1,6 @@
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.generics import CreateAPIView from rest_framework.generics import CreateAPIView
from common.permissions import IsAppUser
from common.utils import reverse, lazyproperty from common.utils import reverse, lazyproperty
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from tickets.api import GenericTicketStatusRetrieveCloseAPI from tickets.api import GenericTicketStatusRetrieveCloseAPI
@ -12,8 +11,8 @@ __all__ = ['LoginAssetCheckAPI', 'LoginAssetConfirmStatusAPI']
class LoginAssetCheckAPI(CreateAPIView): class LoginAssetCheckAPI(CreateAPIView):
permission_classes = (IsAppUser,)
serializer_class = serializers.LoginAssetCheckSerializer serializer_class = serializers.LoginAssetCheckSerializer
model = LoginAssetACL
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
is_need_confirm, response_data = self.check_if_need_confirm() is_need_confirm, response_data = self.check_if_need_confirm()

View File

@ -1,5 +1,7 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class AclsConfig(AppConfig): class AclsConfig(AppConfig):
name = 'acls' name = 'acls'
verbose_name = _('Acls')

View File

@ -0,0 +1,21 @@
# Generated by Django 3.1.13 on 2021-11-30 02:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('acls', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='loginacl',
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login acl'},
),
migrations.AlterModelOptions(
name='loginassetacl',
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login asset acl'},
),
]

View File

@ -6,8 +6,9 @@ from django.db.models import F, Q
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from common.drf.api import JMSBulkModelViewSet from common.drf.api import JMSBulkModelViewSet
from rbac.permissions import RBACPermission
from ..models import Account from ..models import Account
from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify from ..hands import NeedMFAVerify
from .. import serializers from .. import serializers
@ -31,7 +32,8 @@ class AccountFilterSet(BaseFilterSet):
username = self.get_query_param('username') username = self.get_query_param('username')
if not username: if not username:
return qs return qs
qs = qs.filter(Q(username=username) | Q(systemuser__username=username)).distinct() q = Q(username=username) | Q(systemuser__username=username)
qs = qs.filter(q).distinct()
return qs return qs
@ -41,7 +43,6 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet):
filterset_class = AccountFilterSet filterset_class = AccountFilterSet
filterset_fields = ['username', 'app_display', 'type', 'category', 'app'] filterset_fields = ['username', 'app_display', 'type', 'category', 'app']
serializer_class = serializers.AppAccountSerializer serializer_class = serializers.AppAccountSerializer
permission_classes = (IsOrgAdmin,)
def get_queryset(self): def get_queryset(self):
queryset = Account.get_queryset() queryset = Account.get_queryset()
@ -50,5 +51,9 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet):
class ApplicationAccountSecretViewSet(ApplicationAccountViewSet): class ApplicationAccountSecretViewSet(ApplicationAccountViewSet):
serializer_class = serializers.AppAccountSecretSerializer serializer_class = serializers.AppAccountSecretSerializer
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] permission_classes = [RBACPermission, NeedMFAVerify]
http_method_names = ['get', 'options'] http_method_names = ['get', 'options']
rbac_perms = {
'retrieve': 'view_applicationaccountsecret',
'list': 'view_applicationaccountsecret',
}

View File

@ -6,8 +6,7 @@ from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from common.tree import TreeNodeSerializer from common.tree import TreeNodeSerializer
from common.mixins.api import SuggestionMixin from common.mixins.views import SuggestionMixin
from ..hands import IsOrgAdminOrAppUser
from .. import serializers from .. import serializers
from ..models import Application from ..models import Application
@ -22,12 +21,14 @@ class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet):
'type': ['exact', 'in'], 'type': ['exact', 'in'],
} }
search_fields = ('name', 'type', 'category') search_fields = ('name', 'type', 'category')
permission_classes = (IsOrgAdminOrAppUser,)
serializer_classes = { serializer_classes = {
'default': serializers.AppSerializer, 'default': serializers.AppSerializer,
'get_tree': TreeNodeSerializer, 'get_tree': TreeNodeSerializer,
'suggestion': serializers.MiniAppSerializer 'suggestion': serializers.MiniAppSerializer
} }
rbac_perms = {
'get_tree': 'applications.view_application'
}
@action(methods=['GET'], detail=False, url_path='tree') @action(methods=['GET'], detail=False, url_path='tree')
def get_tree(self, request, *args, **kwargs): def get_tree(self, request, *args, **kwargs):

View File

@ -2,10 +2,8 @@
# #
from orgs.mixins import generics from orgs.mixins import generics
from ..hands import IsAppUser
from .. import models from .. import models
from ..serializers import RemoteAppConnectionInfoSerializer from ..serializers import RemoteAppConnectionInfoSerializer
from ..permissions import IsRemoteApp
__all__ = [ __all__ = [
@ -15,5 +13,4 @@ __all__ = [
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
model = models.Application model = models.Application
permission_classes = (IsAppUser, IsRemoteApp)
serializer_class = RemoteAppConnectionInfoSerializer serializer_class = RemoteAppConnectionInfoSerializer

View File

@ -1,7 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.apps import AppConfig from django.apps import AppConfig
class ApplicationsConfig(AppConfig): class ApplicationsConfig(AppConfig):
name = 'applications' name = 'applications'
verbose_name = _('Applications')

View File

@ -11,5 +11,5 @@
""" """
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser, NeedMFAVerify from common.permissions import NeedMFAVerify
from users.models import User, UserGroup from users.models import User, UserGroup

View File

@ -0,0 +1,29 @@
# Generated by Django 3.1.13 on 2021-11-30 02:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('applications', '0011_auto_20210826_1759'),
]
operations = [
migrations.AlterModelOptions(
name='account',
options={'verbose_name': 'Application account'},
),
migrations.AlterModelOptions(
name='application',
options={'ordering': ('name',), 'verbose_name': 'Application'},
),
migrations.AlterModelOptions(
name='applicationuser',
options={'verbose_name': 'Application user'},
),
migrations.AlterModelOptions(
name='historicalaccount',
options={'get_latest_by': 'history_date', 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical Application account'},
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.12 on 2022-02-11 06:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('applications', '0012_auto_20211130_1037'),
]
operations = [
migrations.AlterModelOptions(
name='account',
options={'permissions': [('view_applicationaccountsecret', 'Can view application account secret'), ('change_appplicationaccountsecret', 'Can view application account secret')], 'verbose_name': 'Application account'},
),
]

View File

@ -20,8 +20,12 @@ class Account(BaseUser):
auth_attrs = ['username', 'password', 'private_key', 'public_key'] auth_attrs = ['username', 'password', 'private_key', 'public_key']
class Meta: class Meta:
verbose_name = _('Account') verbose_name = _('Application account')
unique_together = [('username', 'app', 'systemuser')] unique_together = [('username', 'app', 'systemuser')]
permissions = [
('view_applicationaccountsecret', _('Can view application account secret')),
('change_appplicationaccountsecret', _('Can view application account secret')),
]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -219,6 +219,7 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
verbose_name = _('Application') verbose_name = _('Application')
unique_together = [('org_id', 'name')] unique_together = [('org_id', 'name')]
ordering = ('name',) ordering = ('name',)
verbose_name = _("Application")
def __str__(self): def __str__(self):
category_display = self.get_category_display() category_display = self.get_category_display()
@ -265,3 +266,4 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
class ApplicationUser(SystemUser): class ApplicationUser(SystemUser):
class Meta: class Meta:
proxy = True proxy = True
verbose_name = _('Application user')

View File

@ -6,7 +6,6 @@ from django.shortcuts import get_object_or_404
from rest_framework.generics import CreateAPIView from rest_framework.generics import CreateAPIView
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, NeedMFAVerify
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from ..tasks.account_connectivity import test_accounts_connectivity_manual from ..tasks.account_connectivity import test_accounts_connectivity_manual
from ..models import AuthBook, Node from ..models import AuthBook, Node
@ -62,7 +61,6 @@ class AccountViewSet(OrgBulkModelViewSet):
'default': serializers.AccountSerializer, 'default': serializers.AccountSerializer,
'verify_account': serializers.AssetTaskSerializer 'verify_account': serializers.AssetTaskSerializer
} }
permission_classes = (IsOrgAdmin,)
def get_queryset(self): def get_queryset(self):
queryset = AuthBook.get_queryset() queryset = AuthBook.get_queryset()
@ -82,17 +80,22 @@ class AccountSecretsViewSet(AccountViewSet):
serializer_classes = { serializer_classes = {
'default': serializers.AccountSecretSerializer 'default': serializers.AccountSecretSerializer
} }
permission_classes = (IsOrgAdmin, NeedMFAVerify)
http_method_names = ['get'] http_method_names = ['get']
rbac_perms = {
'list': 'assets.view_assetsecret',
'retrieve': 'assets.view_assetsecret',
}
class AccountTaskCreateAPI(CreateAPIView): class AccountTaskCreateAPI(CreateAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AccountTaskSerializer serializer_class = serializers.AccountTaskSerializer
filterset_fields = AccountViewSet.filterset_fields filterset_fields = AccountViewSet.filterset_fields
search_fields = AccountViewSet.search_fields search_fields = AccountViewSet.search_fields
filterset_class = AccountViewSet.filterset_class filterset_class = AccountViewSet.filterset_class
def check_permissions(self, request):
return request.user.has_perm('assets.test_assetconnectivity')
def get_accounts(self): def get_accounts(self):
queryset = AuthBook.objects.all() queryset = AuthBook.objects.all()
queryset = self.filter_queryset(queryset) queryset = self.filter_queryset(queryset)
@ -109,5 +112,4 @@ class AccountTaskCreateAPI(CreateAPIView):
def get_exception_handler(self): def get_exception_handler(self):
def handler(e, context): def handler(e, context):
return Response({"error": str(e)}, status=400) return Response({"error": str(e)}, status=400)
return handler return handler

View File

@ -2,9 +2,9 @@ from django.db.models import Count
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from common.utils import get_logger from common.utils import get_logger
from ..hands import IsOrgAdmin
from ..models import SystemUser from ..models import SystemUser
from .. import serializers from .. import serializers
from rbac.permissions import RBACPermission
logger = get_logger(__file__) logger = get_logger(__file__)
@ -20,7 +20,7 @@ class AdminUserViewSet(OrgBulkModelViewSet):
filterset_fields = ("name", "username") filterset_fields = ("name", "username")
search_fields = filterset_fields search_fields = filterset_fields
serializer_class = serializers.AdminUserSerializer serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (RBACPermission,)
ordering_fields = ('name',) ordering_fields = ('name',)
ordering = ('name', ) ordering = ('name', )

View File

@ -1,13 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from assets.api import FilterAssetByNodeMixin
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import RetrieveAPIView, ListAPIView from rest_framework.generics import RetrieveAPIView, ListAPIView
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db.models import Q from django.db.models import Q
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser
from common.mixins.api import SuggestionMixin from common.mixins.api import SuggestionMixin
from users.models import User, UserGroup from users.models import User, UserGroup
from users.serializers import UserSerializer, UserGroupSerializer from users.serializers import UserSerializer, UserGroupSerializer
@ -17,6 +15,7 @@ from perms.serializers import AssetPermissionSerializer
from perms.filters import AssetPermissionFilter from perms.filters import AssetPermissionFilter
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics from orgs.mixins import generics
from assets.api import FilterAssetByNodeMixin
from ..models import Asset, Node, Platform from ..models import Asset, Node, Platform
from .. import serializers from .. import serializers
from ..tasks import ( from ..tasks import (
@ -55,7 +54,6 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
'default': serializers.AssetSerializer, 'default': serializers.AssetSerializer,
'suggestion': serializers.MiniAssetSerializer 'suggestion': serializers.MiniAssetSerializer
} }
permission_classes = (IsOrgAdminOrAppUser,)
extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend] extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend]
def set_assets_node(self, assets): def set_assets_node(self, assets):
@ -76,8 +74,10 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
class AssetPlatformRetrieveApi(RetrieveAPIView): class AssetPlatformRetrieveApi(RetrieveAPIView):
queryset = Platform.objects.all() queryset = Platform.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.PlatformSerializer serializer_class = serializers.PlatformSerializer
rbac_perms = {
'retrieve': 'assets.view_gateway'
}
def get_object(self): def get_object(self):
asset_pk = self.kwargs.get('pk') asset_pk = self.kwargs.get('pk')
@ -87,16 +87,10 @@ class AssetPlatformRetrieveApi(RetrieveAPIView):
class AssetPlatformViewSet(ModelViewSet): class AssetPlatformViewSet(ModelViewSet):
queryset = Platform.objects.all() queryset = Platform.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.PlatformSerializer serializer_class = serializers.PlatformSerializer
filterset_fields = ['name', 'base'] filterset_fields = ['name', 'base']
search_fields = ['name'] search_fields = ['name']
def get_permissions(self):
if self.request.method.lower() in ['get', 'options']:
self.permission_classes = (IsOrgAdmin,)
return super().get_permissions()
def check_object_permissions(self, request, obj): def check_object_permissions(self, request, obj):
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
self.permission_denied( self.permission_denied(
@ -131,7 +125,6 @@ class AssetsTaskMixin:
class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
model = Asset model = Asset
serializer_class = serializers.AssetTaskSerializer serializer_class = serializers.AssetTaskSerializer
permission_classes = (IsOrgAdmin,)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
@ -139,11 +132,24 @@ class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
request.data['assets'] = [pk] request.data['assets'] = [pk]
return super().create(request, *args, **kwargs) return super().create(request, *args, **kwargs)
def check_permissions(self, request):
action = request.data.get('action')
action_perm_require = {
'push_system_user': 'assets.push_assetsystemuser',
'test_system_user': 'assets.test_assetconnectivity'
}
perm_required = action_perm_require.get(action)
has = self.request.user.has_perm(perm_required)
if not has:
self.permission_denied(request)
def perform_asset_task(self, serializer): def perform_asset_task(self, serializer):
data = serializer.validated_data data = serializer.validated_data
action = data['action'] action = data['action']
if action not in ['push_system_user', 'test_system_user']: if action not in ['push_system_user', 'test_system_user']:
return return
asset = data['asset'] asset = data['asset']
system_users = data.get('system_users') system_users = data.get('system_users')
if not system_users: if not system_users:
@ -166,12 +172,13 @@ class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
model = Asset model = Asset
serializer_class = serializers.AssetsTaskSerializer serializer_class = serializers.AssetsTaskSerializer
permission_classes = (IsOrgAdmin,)
class AssetGatewayListApi(generics.ListAPIView): class AssetGatewayListApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GatewayWithAuthSerializer serializer_class = serializers.GatewayWithAuthSerializer
rbac_perms = {
'list': 'assets.view_gateway'
}
def get_queryset(self): def get_queryset(self):
asset_id = self.kwargs.get('pk') asset_id = self.kwargs.get('pk')
@ -183,7 +190,6 @@ class AssetGatewayListApi(generics.ListAPIView):
class BaseAssetPermUserOrUserGroupListApi(ListAPIView): class BaseAssetPermUserOrUserGroupListApi(ListAPIView):
permission_classes = (IsOrgAdmin,)
def get_object(self): def get_object(self):
asset_id = self.kwargs.get('pk') asset_id = self.kwargs.get('pk')
@ -220,11 +226,13 @@ class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi):
class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView): class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
model = AssetPermission model = AssetPermission
serializer_class = AssetPermissionSerializer serializer_class = AssetPermissionSerializer
filterset_class = AssetPermissionFilter filterset_class = AssetPermissionFilter
search_fields = ('name',) search_fields = ('name',)
rbac_perms = {
'list': 'perms.view_assetpermission'
}
def get_object(self): def get_object(self):
asset_id = self.kwargs.get('pk') asset_id = self.kwargs.get('pk')

View File

@ -1,11 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import status, mixins, viewsets from rest_framework import status, viewsets
from rest_framework.response import Response from rest_framework.response import Response
from common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from .. import serializers from .. import serializers
from ..tasks import execute_account_backup_plan from ..tasks import execute_account_backup_plan
from ..models import ( from ..models import (
@ -24,17 +22,13 @@ class AccountBackupPlanViewSet(OrgBulkModelViewSet):
ordering_fields = ('name',) ordering_fields = ('name',)
ordering = ('name',) ordering = ('name',)
serializer_class = serializers.AccountBackupPlanSerializer serializer_class = serializers.AccountBackupPlanSerializer
permission_classes = (IsOrgAdmin,)
class AccountBackupPlanExecutionViewSet( class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
mixins.CreateModelMixin, mixins.ListModelMixin,
mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
serializer_class = serializers.AccountBackupPlanExecutionSerializer serializer_class = serializers.AccountBackupPlanExecutionSerializer
search_fields = ('trigger',) search_fields = ('trigger',)
filterset_fields = ('trigger', 'plan_id') filterset_fields = ('trigger', 'plan_id')
permission_classes = (IsOrgAdmin,) http_method_names = ['get', 'post']
def get_queryset(self): def get_queryset(self):
queryset = AccountBackupPlanExecution.objects.all() queryset = AccountBackupPlanExecution.objects.all()

View File

@ -9,7 +9,6 @@ from common.utils import reverse
from common.utils import lazyproperty from common.utils import lazyproperty
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from tickets.api import GenericTicketStatusRetrieveCloseAPI from tickets.api import GenericTicketStatusRetrieveCloseAPI
from ..hands import IsOrgAdmin, IsAppUser
from ..models import CommandFilter, CommandFilterRule from ..models import CommandFilter, CommandFilterRule
from .. import serializers from .. import serializers
@ -23,7 +22,6 @@ class CommandFilterViewSet(OrgBulkModelViewSet):
model = CommandFilter model = CommandFilter
filterset_fields = ("name",) filterset_fields = ("name",)
search_fields = filterset_fields search_fields = filterset_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterSerializer serializer_class = serializers.CommandFilterSerializer
@ -31,7 +29,6 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet):
model = CommandFilterRule model = CommandFilterRule
filterset_fields = ('content',) filterset_fields = ('content',)
search_fields = filterset_fields search_fields = filterset_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterRuleSerializer serializer_class = serializers.CommandFilterRuleSerializer
def get_queryset(self): def get_queryset(self):
@ -43,8 +40,10 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet):
class CommandConfirmAPI(CreateAPIView): class CommandConfirmAPI(CreateAPIView):
permission_classes = (IsAppUser,)
serializer_class = serializers.CommandConfirmSerializer serializer_class = serializers.CommandConfirmSerializer
rbac_perms = {
'create': 'tickets.add_ticket'
}
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
ticket = self.create_command_confirm_ticket() ticket = self.create_command_confirm_ticket()

View File

@ -6,7 +6,6 @@ from rest_framework.views import APIView, Response
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..models import Domain, Gateway from ..models import Domain, Gateway
from .. import serializers from .. import serializers
@ -20,7 +19,6 @@ class DomainViewSet(OrgBulkModelViewSet):
model = Domain model = Domain
filterset_fields = ("name", ) filterset_fields = ("name", )
search_fields = filterset_fields search_fields = filterset_fields
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.DomainSerializer serializer_class = serializers.DomainSerializer
ordering_fields = ('name',) ordering_fields = ('name',)
ordering = ('name', ) ordering = ('name', )
@ -35,13 +33,14 @@ class GatewayViewSet(OrgBulkModelViewSet):
model = Gateway model = Gateway
filterset_fields = ("domain__name", "name", "username", "ip", "domain") filterset_fields = ("domain__name", "name", "username", "ip", "domain")
search_fields = ("domain__name", "name", "username", "ip") search_fields = ("domain__name", "name", "username", "ip")
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GatewaySerializer serializer_class = serializers.GatewaySerializer
class GatewayTestConnectionApi(SingleObjectMixin, APIView): class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsOrgAdmin,)
object = None object = None
rbac_perms = {
'POST': 'assets.change_gateway'
}
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object(Gateway.objects.all()) self.object = self.get_object(Gateway.objects.all())

View File

@ -3,7 +3,6 @@
from orgs.mixins.api import OrgModelViewSet from orgs.mixins.api import OrgModelViewSet
from assets.models import GatheredUser from assets.models import GatheredUser
from common.permissions import IsOrgAdmin
from ..serializers import GatheredUserSerializer from ..serializers import GatheredUserSerializer
from ..filters import AssetRelatedByNodeFilterBackend from ..filters import AssetRelatedByNodeFilterBackend
@ -15,7 +14,6 @@ __all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet): class GatheredUserViewSet(OrgModelViewSet):
model = GatheredUser model = GatheredUser
serializer_class = GatheredUserSerializer serializer_class = GatheredUserSerializer
permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend] extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id'] filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id']

View File

@ -17,7 +17,6 @@ from django.db.models import Count
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import IsOrgAdmin
from ..models import Label from ..models import Label
from .. import serializers from .. import serializers
@ -30,7 +29,6 @@ class LabelViewSet(OrgBulkModelViewSet):
model = Label model = Label
filterset_fields = ("name", "value") filterset_fields = ("name", "value")
search_fields = filterset_fields search_fields = filterset_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LabelSerializer serializer_class = serializers.LabelSerializer
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):

View File

@ -20,7 +20,6 @@ from common.tree import TreeNodeSerializer
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics from orgs.mixins import generics
from orgs.utils import current_org from orgs.utils import current_org
from ..hands import IsOrgAdmin
from ..models import Node from ..models import Node
from ..tasks import ( from ..tasks import (
update_node_assets_hardware_info_manual, update_node_assets_hardware_info_manual,
@ -46,7 +45,6 @@ class NodeViewSet(SuggestionMixin, OrgBulkModelViewSet):
model = Node model = Node
filterset_fields = ('value', 'key', 'id') filterset_fields = ('value', 'key', 'id')
search_fields = ('value', ) search_fields = ('value', )
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
@action(methods=[POST], detail=False, url_path='check_assets_amount_task') @action(methods=[POST], detail=False, url_path='check_assets_amount_task')
@ -85,7 +83,6 @@ class NodeListAsTreeApi(generics.ListAPIView):
] ]
""" """
model = Node model = Node
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer serializer_class = TreeNodeSerializer
@staticmethod @staticmethod
@ -100,7 +97,6 @@ class NodeListAsTreeApi(generics.ListAPIView):
class NodeChildrenApi(generics.ListCreateAPIView): class NodeChildrenApi(generics.ListCreateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
instance = None instance = None
is_initial = False is_initial = False
@ -199,7 +195,6 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
class NodeAssetsApi(generics.ListAPIView): class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
def get_queryset(self): def get_queryset(self):
@ -214,7 +209,6 @@ class NodeAssetsApi(generics.ListAPIView):
class NodeAddChildrenApi(generics.UpdateAPIView): class NodeAddChildrenApi(generics.UpdateAPIView):
model = Node model = Node
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer serializer_class = serializers.NodeAddChildrenSerializer
instance = None instance = None
@ -231,7 +225,6 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
class NodeAddAssetsApi(generics.UpdateAPIView): class NodeAddAssetsApi(generics.UpdateAPIView):
model = Node model = Node
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None instance = None
def perform_update(self, serializer): def perform_update(self, serializer):
@ -243,7 +236,6 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
class NodeRemoveAssetsApi(generics.UpdateAPIView): class NodeRemoveAssetsApi(generics.UpdateAPIView):
model = Node model = Node
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None instance = None
def perform_update(self, serializer): def perform_update(self, serializer):
@ -262,7 +254,6 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
class MoveAssetsToNodeApi(generics.UpdateAPIView): class MoveAssetsToNodeApi(generics.UpdateAPIView):
model = Node model = Node
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None instance = None
def perform_update(self, serializer): def perform_update(self, serializer):
@ -305,8 +296,6 @@ class MoveAssetsToNodeApi(generics.UpdateAPIView):
class NodeTaskCreateApi(generics.CreateAPIView): class NodeTaskCreateApi(generics.CreateAPIView):
model = Node model = Node
serializer_class = serializers.NodeTaskSerializer serializer_class = serializers.NodeTaskSerializer
permission_classes = (IsOrgAdmin,)
def get_object(self): def get_object(self):
node_id = self.kwargs.get('pk') node_id = self.kwargs.get('pk')
node = get_object_or_none(self.model, id=node_id) node = get_object_or_none(self.model, id=node_id)

View File

@ -1,16 +1,15 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.middleware import csrf
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import action
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.utils.crypto import get_aes_crypto from common.utils.crypto import get_aes_crypto
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsValidUser from common.permissions import IsValidUser
from common.mixins.api import SuggestionMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics from orgs.mixins import generics
from common.mixins.api import SuggestionMixin
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
from rest_framework.decorators import action
from ..models import SystemUser, CommandFilterRule from ..models import SystemUser, CommandFilterRule
from .. import serializers from .. import serializers
from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer
@ -46,7 +45,6 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet):
} }
ordering_fields = ('name', 'protocol', 'login_mode') ordering_fields = ('name', 'protocol', 'login_mode')
ordering = ('name', ) ordering = ('name', )
permission_classes = (IsOrgAdminOrAppUser,)
@action(methods=['get'], detail=False, url_path='su-from') @action(methods=['get'], detail=False, url_path='su-from')
def su_from(self, request, *args, **kwargs): def su_from(self, request, *args, **kwargs):
@ -80,8 +78,13 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
Get system user auth info Get system user auth info
""" """
model = SystemUser model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer serializer_class = SystemUserWithAuthInfoSerializer
rbac_perms = {
'retrieve': 'assets.view_systemusersecret',
'list': 'assets.view_systemusersecret',
'change': 'assets.change_systemuser',
'destroy': 'assets.change_systemuser',
}
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
instance = self.get_object() instance = self.get_object()
@ -123,7 +126,6 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
Get system user with asset auth info Get system user with asset auth info
""" """
model = SystemUser model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer serializer_class = SystemUserWithAuthInfoSerializer
def get_object(self): def get_object(self):
@ -140,8 +142,10 @@ class SystemUserAppAuthInfoApi(generics.RetrieveAPIView):
Get system user with asset auth info Get system user with asset auth info
""" """
model = SystemUser model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer serializer_class = SystemUserWithAuthInfoSerializer
rbac_perms = {
'retrieve': 'assets.view_systemusersecret',
}
def get_object(self): def get_object(self):
instance = super().get_object() instance = super().get_object()
@ -153,7 +157,6 @@ class SystemUserAppAuthInfoApi(generics.RetrieveAPIView):
class SystemUserTaskApi(generics.CreateAPIView): class SystemUserTaskApi(generics.CreateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.SystemUserTaskSerializer serializer_class = serializers.SystemUserTaskSerializer
def do_push(self, system_user, asset_ids=None): def do_push(self, system_user, asset_ids=None):
@ -175,6 +178,18 @@ class SystemUserTaskApi(generics.CreateAPIView):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
return get_object_or_404(SystemUser, pk=pk) return get_object_or_404(SystemUser, pk=pk)
def check_permissions(self, request):
action = request.data.get('action')
action_perm_require = {
'push': 'assets.push_systemuser',
'test': 'assets.test_connectivity'
}
perm_required = action_perm_require.get(action)
has = self.request.user.has_perm(perm_required)
if not has:
self.permission_denied(request)
def perform_create(self, serializer): def perform_create(self, serializer):
action = serializer.validated_data["action"] action = serializer.validated_data["action"]
asset = serializer.validated_data.get('asset') asset = serializer.validated_data.get('asset')
@ -198,7 +213,9 @@ class SystemUserTaskApi(generics.CreateAPIView):
class SystemUserCommandFilterRuleListApi(generics.ListAPIView): class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,) rbac_perms = {
'list': 'assets.view_commandfilterule'
}
def get_serializer_class(self): def get_serializer_class(self):
from ..serializers import CommandFilterRuleSerializer from ..serializers import CommandFilterRuleSerializer
@ -224,10 +241,12 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
class SystemUserAssetsListView(generics.ListAPIView): class SystemUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer serializer_class = serializers.AssetSimpleSerializer
filterset_fields = ("hostname", "ip") filterset_fields = ("hostname", "ip")
search_fields = filterset_fields search_fields = filterset_fields
rbac_perms = {
'list': 'assets.view_asset'
}
def get_object(self): def get_object(self):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')

View File

@ -5,7 +5,6 @@ from django.db.models import F, Value, Model
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
from django.db.models.functions import Concat from django.db.models.functions import Concat
from common.permissions import IsOrgAdmin
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org from orgs.utils import current_org
@ -71,7 +70,6 @@ class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet):
class SystemUserAssetRelationViewSet(BaseRelationViewSet): class SystemUserAssetRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserAssetRelationSerializer serializer_class = serializers.SystemUserAssetRelationSerializer
model = models.SystemUser.assets.through model = models.SystemUser.assets.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', 'asset', 'systemuser', 'id', 'asset', 'systemuser',
] ]
@ -97,7 +95,6 @@ class SystemUserAssetRelationViewSet(BaseRelationViewSet):
class SystemUserNodeRelationViewSet(BaseRelationViewSet): class SystemUserNodeRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserNodeRelationSerializer serializer_class = serializers.SystemUserNodeRelationSerializer
model = models.SystemUser.nodes.through model = models.SystemUser.nodes.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', 'node', 'systemuser', 'id', 'node', 'systemuser',
] ]
@ -118,7 +115,6 @@ class SystemUserNodeRelationViewSet(BaseRelationViewSet):
class SystemUserUserRelationViewSet(BaseRelationViewSet): class SystemUserUserRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserUserRelationSerializer serializer_class = serializers.SystemUserUserRelationSerializer
model = models.SystemUser.users.through model = models.SystemUser.users.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', 'user', 'systemuser', 'id', 'user', 'systemuser',
] ]

View File

@ -1,10 +1,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.apps import AppConfig from django.apps import AppConfig
class AssetsConfig(AppConfig): class AssetsConfig(AppConfig):
name = 'assets' name = 'assets'
verbose_name = _('Assets')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def ready(self): def ready(self):
super().ready() super().ready()

View File

@ -11,5 +11,4 @@
""" """
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup from users.models import User, UserGroup

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.13 on 2021-11-30 02:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0076_delete_assetuser'),
]
operations = [
migrations.AlterModelOptions(
name='label',
options={'verbose_name': 'Label'},
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.1.12 on 2022-02-11 06:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0077_auto_20211130_1037'),
]
operations = [
migrations.AlterModelOptions(
name='asset',
options={'ordering': ['hostname'], 'permissions': [('test_assetconnectivity', 'Can test asset connectivity'), ('push_assetsystemuser', 'Can push system user to asset')], 'verbose_name': 'Asset'},
),
migrations.AlterModelOptions(
name='authbook',
options={'permissions': [('view_assetaccountsecret', 'Can view asset account secret'), ('change_assetaccountsecret', 'Can change asset account secret')], 'verbose_name': 'AuthBook'},
),
]

View File

@ -355,3 +355,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
unique_together = [('org_id', 'hostname')] unique_together = [('org_id', 'hostname')]
verbose_name = _("Asset") verbose_name = _("Asset")
ordering = ["hostname", ] ordering = ["hostname", ]
permissions = [
('test_assetconnectivity', 'Can test asset connectivity'),
('push_assetsystemuser', 'Can push system user to asset'),
]

View File

@ -26,6 +26,10 @@ class AuthBook(BaseUser, AbsConnectivity):
class Meta: class Meta:
verbose_name = _('AuthBook') verbose_name = _('AuthBook')
unique_together = [('username', 'asset', 'systemuser')] unique_together = [('username', 'asset', 'systemuser')]
permissions = [
('view_assetaccountsecret', _('Can view asset account secret')),
('change_assetaccountsecret', _('Can change asset account secret'))
]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -37,3 +37,4 @@ class Label(OrgModelMixin):
class Meta: class Meta:
db_table = "assets_label" db_table = "assets_label"
unique_together = [('name', 'value', 'org_id')] unique_together = [('name', 'value', 'org_id')]
verbose_name = _('Label')

View File

@ -325,7 +325,7 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
verbose_name = _("System user") verbose_name = _("System user")
# Todo: 准备废弃 # Deprecated: 准备废弃
class AdminUser(BaseUser): class AdminUser(BaseUser):
""" """
A privileged user that ansible can use it to push system user and so on A privileged user that ansible can use it to push system user and so on

View File

@ -4,7 +4,6 @@ from rest_framework.mixins import ListModelMixin, CreateModelMixin
from django.db.models import F, Value from django.db.models import F, Value
from django.db.models.functions import Concat from django.db.models.functions import Concat
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin
from common.drf.filters import DatetimeRangeFilter from common.drf.filters import DatetimeRangeFilter
from common.api import CommonGenericViewSet from common.api import CommonGenericViewSet
from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin
@ -20,7 +19,6 @@ class FTPLogViewSet(CreateModelMixin,
OrgGenericViewSet): OrgGenericViewSet):
model = FTPLog model = FTPLog
serializer_class = FTPLogSerializer serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,)
extra_filter_backends = [DatetimeRangeFilter] extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [ date_range_filter_fields = [
('date_start', ('date_from', 'date_to')) ('date_start', ('date_from', 'date_to'))
@ -32,7 +30,6 @@ class FTPLogViewSet(CreateModelMixin,
class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet): class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet):
queryset = UserLoginLog.objects.all() queryset = UserLoginLog.objects.all()
permission_classes = [IsOrgAdmin | IsOrgAuditor]
serializer_class = UserLoginLogSerializer serializer_class = UserLoginLogSerializer
extra_filter_backends = [DatetimeRangeFilter] extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [ date_range_filter_fields = [
@ -58,7 +55,6 @@ class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet):
class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): class OperateLogViewSet(ListModelMixin, OrgGenericViewSet):
model = OperateLog model = OperateLog
serializer_class = OperateLogSerializer serializer_class = OperateLogSerializer
permission_classes = [IsOrgAdmin | IsOrgAuditor]
extra_filter_backends = [DatetimeRangeFilter] extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [ date_range_filter_fields = [
('datetime', ('date_from', 'date_to')) ('datetime', ('date_from', 'date_to'))
@ -70,7 +66,6 @@ class OperateLogViewSet(ListModelMixin, OrgGenericViewSet):
class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet):
queryset = PasswordChangeLog.objects.all() queryset = PasswordChangeLog.objects.all()
permission_classes = [IsOrgAdmin | IsOrgAuditor]
serializer_class = PasswordChangeLogSerializer serializer_class = PasswordChangeLogSerializer
extra_filter_backends = [DatetimeRangeFilter] extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [ date_range_filter_fields = [
@ -91,7 +86,6 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet):
class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
model = CommandExecution model = CommandExecution
serializer_class = CommandExecutionSerializer serializer_class = CommandExecutionSerializer
permission_classes = [IsOrgAdmin | IsOrgAuditor]
extra_filter_backends = [DatetimeRangeFilter] extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [ date_range_filter_fields = [
('date_start', ('date_from', 'date_to')) ('date_start', ('date_from', 'date_to'))
@ -117,7 +111,6 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
serializer_class = CommandExecutionHostsRelationSerializer serializer_class = CommandExecutionHostsRelationSerializer
m2m_field = CommandExecution.hosts.field m2m_field = CommandExecution.hosts.field
permission_classes = [IsOrgAdmin | IsOrgAuditor]
filterset_fields = [ filterset_fields = [
'id', 'asset', 'commandexecution' 'id', 'asset', 'commandexecution'
] ]

View File

@ -1,10 +1,12 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import post_save from django.db.models.signals import post_save
class AuditsConfig(AppConfig): class AuditsConfig(AppConfig):
name = 'audits' name = 'audits'
verbose_name = _('Audits')
def ready(self): def ready(self):
from . import signals_handler from . import signals_handler

View File

@ -0,0 +1,29 @@
# Generated by Django 3.1.13 on 2021-11-30 02:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('audits', '0012_auto_20210414_1443'),
]
operations = [
migrations.AlterModelOptions(
name='ftplog',
options={'verbose_name': 'File transfer log'},
),
migrations.AlterModelOptions(
name='operatelog',
options={'verbose_name': 'Operate log'},
),
migrations.AlterModelOptions(
name='passwordchangelog',
options={'verbose_name': 'Password change log'},
),
migrations.AlterModelOptions(
name='userloginlog',
options={'ordering': ['-datetime', 'username'], 'verbose_name': 'User login log'},
),
]

View File

@ -43,6 +43,9 @@ class FTPLog(OrgModelMixin):
is_success = models.BooleanField(default=True, verbose_name=_("Success")) is_success = models.BooleanField(default=True, verbose_name=_("Success"))
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
class Meta:
verbose_name = _("File transfer log")
class OperateLog(OrgModelMixin): class OperateLog(OrgModelMixin):
ACTION_CREATE = 'create' ACTION_CREATE = 'create'
@ -73,6 +76,9 @@ class OperateLog(OrgModelMixin):
self.org_id = Organization.ROOT_ID self.org_id = Organization.ROOT_ID
return super(OperateLog, self).save(*args, **kwargs) return super(OperateLog, self).save(*args, **kwargs)
class Meta:
verbose_name = _("Operate log")
class PasswordChangeLog(models.Model): class PasswordChangeLog(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
@ -84,6 +90,9 @@ class PasswordChangeLog(models.Model):
def __str__(self): def __str__(self):
return "{} change {}'s password".format(self.change_by, self.user) return "{} change {}'s password".format(self.change_by, self.user)
class Meta:
verbose_name = _('Password change log')
class UserLoginLog(models.Model): class UserLoginLog(models.Model):
LOGIN_TYPE_CHOICE = ( LOGIN_TYPE_CHOICE = (
@ -155,3 +164,4 @@ class UserLoginLog(models.Model):
class Meta: class Meta:
ordering = ['-datetime', 'username'] ordering = ['-datetime', 'username']
verbose_name = _('User login log')

View File

@ -102,11 +102,6 @@ def create_operate_log(action, sender, resource):
M2M_NEED_RECORD = { M2M_NEED_RECORD = {
'OrganizationMember': (
_('User and Organization'),
_('{User} JOINED {Organization}'),
_('{User} LEFT {Organization}')
),
User.groups.through._meta.object_name: ( User.groups.through._meta.object_name: (
_('User and Group'), _('User and Group'),
_('{User} JOINED {UserGroup}'), _('{User} JOINED {UserGroup}'),

View File

@ -24,12 +24,10 @@ from applications.models import Application
from authentication.signals import post_auth_failed from authentication.signals import post_auth_failed
from common.utils import get_logger, random_string from common.utils import get_logger, random_string
from common.mixins.api import SerializerMixin from common.mixins.api import SerializerMixin
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
from common.utils.common import get_file_by_arch from common.utils.common import get_file_by_arch
from orgs.mixins.api import RootOrgViewMixin from orgs.mixins.api import RootOrgViewMixin
from common.http import is_true from common.http import is_true
from perms.models.base import Action from perms.models.base import Action
from perms.utils.application.permission import validate_permission as app_validate_permission
from perms.utils.application.permission import get_application_actions from perms.utils.application.permission import get_application_actions
from perms.utils.asset.permission import get_asset_actions from perms.utils.asset.permission import get_asset_actions
@ -42,6 +40,14 @@ __all__ = ['UserConnectionTokenViewSet']
class ClientProtocolMixin: class ClientProtocolMixin:
"""
下载客户端支持的连接文件里面包含了 token 其他连接信息
- [x] RDP
- [ ] KoKo
本质上这里还是暴露出 token 进行使用
"""
request: Request request: Request
get_serializer: Callable get_serializer: Callable
create_token: Callable create_token: Callable
@ -167,7 +173,7 @@ class ClientProtocolMixin:
rst = rst.decode('ascii') rst = rst.decode('ascii')
return rst return rst
@action(methods=['POST', 'GET'], detail=False, url_path='rdp/file', permission_classes=[IsValidUser]) @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file')
def get_rdp_file(self, request, *args, **kwargs): def get_rdp_file(self, request, *args, **kwargs):
if self.request.method == 'GET': if self.request.method == 'GET':
data = self.request.query_params data = self.request.query_params
@ -214,7 +220,7 @@ class ClientProtocolMixin:
} }
return data return data
@action(methods=['POST', 'GET'], detail=False, url_path='client-url', permission_classes=[IsValidUser]) @action(methods=['POST', 'GET'], detail=False, url_path='client-url')
def get_client_protocol_url(self, request, *args, **kwargs): def get_client_protocol_url(self, request, *args, **kwargs):
serializer = self.get_valid_serializer() serializer = self.get_valid_serializer()
try: try:
@ -271,8 +277,14 @@ class SecretDetailMixin:
'remote_app': None, 'remote_app': None,
} }
@action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail') @action(methods=['POST'], detail=False, url_path='secret-info/detail')
def get_secret_detail(self, request, *args, **kwargs): def get_secret_detail(self, request, *args, **kwargs):
perm_required = 'authentication.view_connectiontokensecret'
# 非常重要的 api再逻辑层再判断一下双重保险
if not request.user.has_perm(perm_required):
raise PermissionDenied('Not allow to view secret')
token = request.data.get('token', '') token = request.data.get('token', '')
try: try:
value, user, system_user, asset, app, expired_at, actions = self.valid_token(token) value, user, system_user, asset, app, expired_at, actions = self.valid_token(token)
@ -307,12 +319,18 @@ class UserConnectionTokenViewSet(
RootOrgViewMixin, SerializerMixin, ClientProtocolMixin, RootOrgViewMixin, SerializerMixin, ClientProtocolMixin,
SecretDetailMixin, GenericViewSet SecretDetailMixin, GenericViewSet
): ):
permission_classes = (IsSuperUserOrAppUser,)
serializer_classes = { serializer_classes = {
'default': ConnectionTokenSerializer, 'default': ConnectionTokenSerializer,
'get_secret_detail': ConnectionTokenSecretSerializer, 'get_secret_detail': ConnectionTokenSecretSerializer,
} }
CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}' CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}'
rbac_perms = {
'GET': 'view_connectiontoken',
'create': 'add_connectiontoken',
'get_secret_detail': 'view_connectiontokensecret',
'get_rdp_file': 'add_connectiontoken',
'get_client_protocol_url': 'add_connectiontoken',
}
@staticmethod @staticmethod
def check_resource_permission(user, asset, application, system_user): def check_resource_permission(user, asset, application, system_user):
@ -403,14 +421,6 @@ class UserConnectionTokenViewSet(
raise serializers.ValidationError('Permission expired or invalid') raise serializers.ValidationError('Permission expired or invalid')
return value, user, system_user, asset, app, expired_at, actions return value, user, system_user, asset, app, expired_at, actions
def get_permissions(self):
if self.action in ["create", "get_rdp_file"]:
if self.request.data.get('user', None):
self.permission_classes = (IsSuperUser,)
else:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
def get(self, request): def get(self, request):
token = request.query_params.get('token') token = request.query_params.get('token')
key = self.CACHE_KEY_PREFIX.format(token) key = self.CACHE_KEY_PREFIX.format(token)

View File

@ -5,7 +5,6 @@ from rest_framework.response import Response
from users.permissions import IsAuthPasswdTimeValid from users.permissions import IsAuthPasswdTimeValid
from users.models import User from users.models import User
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin
from common.mixins.api import RoleUserMixin, RoleAdminMixin from common.mixins.api import RoleUserMixin, RoleAdminMixin
from authentication import errors from authentication import errors
@ -32,4 +31,4 @@ class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase):
class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase): class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase):
user_id_url_kwarg = 'user_id' user_id_url_kwarg = 'user_id'
permission_classes = (IsOrgAdmin,)

View File

@ -5,7 +5,6 @@ from rest_framework.response import Response
from users.permissions import IsAuthPasswdTimeValid from users.permissions import IsAuthPasswdTimeValid
from users.models import User from users.models import User
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin
from common.mixins.api import RoleUserMixin, RoleAdminMixin from common.mixins.api import RoleUserMixin, RoleAdminMixin
from authentication import errors from authentication import errors
@ -32,7 +31,6 @@ class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase):
class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase): class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase):
user_id_url_kwarg = 'user_id' user_id_url_kwarg = 'user_id'
permission_classes = (IsOrgAdmin,)
class FeiShuEventSubscriptionCallback(APIView): class FeiShuEventSubscriptionCallback(APIView):

View File

@ -1,13 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework.generics import UpdateAPIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from django.shortcuts import get_object_or_404
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin
from .. import errors, mixins from .. import errors, mixins
__all__ = ['TicketStatusApi'] __all__ = ['TicketStatusApi']

View File

@ -39,14 +39,6 @@ class MFASendCodeApi(AuthMixin, CreateAPIView):
username = '' username = ''
ip = '' ip = ''
def get_user_from_db(self, username):
try:
user = get_object_or_404(User, username=username)
return user
except Exception as e:
self.incr_mfa_failed_time(username, self.ip)
raise e
def get_user_from_db(self, username): def get_user_from_db(self, username):
"""避免暴力测试用户名""" """避免暴力测试用户名"""
ip = self.get_request_ip() ip = self.get_request_ip()

View File

@ -13,7 +13,7 @@ from common.utils.timezone import utc_now
from common.const.http import POST, GET 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.drf.serializers import EmptySerializer
from common.permissions import IsSuperUser from common.permissions import OnlySuperUser
from common.utils import reverse from common.utils import reverse
from users.models import User from users.models import User
from ..serializers import SSOTokenSerializer from ..serializers import SSOTokenSerializer
@ -32,9 +32,8 @@ class SSOViewSet(AuthMixin, JMSGenericViewSet):
'login_url': SSOTokenSerializer, 'login_url': SSOTokenSerializer,
'login': EmptySerializer 'login': EmptySerializer
} }
permission_classes = (IsSuperUser,)
@action(methods=[POST], detail=False, permission_classes=[IsSuperUser], url_path='login-url') @action(methods=[POST], detail=False, permission_classes=[OnlySuperUser], url_path='login-url')
def login_url(self, request, *args, **kwargs): def login_url(self, request, *args, **kwargs):
if not settings.AUTH_SSO: if not settings.AUTH_SSO:
raise SSOAuthClosed() raise SSOAuthClosed()

View File

@ -5,7 +5,6 @@ from rest_framework.response import Response
from users.permissions import IsAuthPasswdTimeValid from users.permissions import IsAuthPasswdTimeValid
from users.models import User from users.models import User
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin
from common.mixins.api import RoleUserMixin, RoleAdminMixin from common.mixins.api import RoleUserMixin, RoleAdminMixin
from authentication import errors from authentication import errors
@ -32,4 +31,4 @@ class WeComQRUnBindForUserApi(RoleUserMixin, WeComQRUnBindBase):
class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase): class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase):
user_id_url_kwarg = 'user_id' user_id_url_kwarg = 'user_id'
permission_classes = (IsOrgAdmin,)

View File

@ -1,8 +1,10 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class AuthenticationConfig(AppConfig): class AuthenticationConfig(AppConfig):
name = 'authentication' name = 'authentication'
verbose_name = _('Authentication')
def ready(self): def ready(self):
from . import signals_handlers from . import signals_handlers

View File

@ -29,6 +29,9 @@ def get_request_date_header(request):
class JMSModelBackend(ModelBackend): class JMSModelBackend(ModelBackend):
def has_perm(self, user_obj, perm, obj=None):
return False
def user_can_authenticate(self, user): def user_can_authenticate(self, user):
return True return True

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.12 on 2021-09-29 03:06
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('authentication', '0004_ssotoken'),
]
operations = [
migrations.AlterModelOptions(
name='loginconfirmsetting',
options={'verbose_name': 'Login Confirm'},
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.1.13 on 2021-11-30 02:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('authentication', '0005_auto_20210929_1106'),
]
operations = [
migrations.AlterModelOptions(
name='accesskey',
options={'verbose_name': 'Access key'},
),
migrations.AlterModelOptions(
name='ssotoken',
options={'verbose_name': 'SSO token'},
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.1.12 on 2022-02-11 06:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0006_auto_20211130_1037'),
]
operations = [
migrations.CreateModel(
name='ConnectionToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
],
options={
'permissions': [('add_superconnectiontoken', 'Can add super connection token'), ('view_connectiontokensecret', 'Can view connect token secret')],
},
),
]

View File

@ -29,6 +29,9 @@ class AccessKey(models.Model):
def __str__(self): def __str__(self):
return str(self.id) return str(self.id)
class Meta:
verbose_name = _("Access key")
class PrivateToken(Token): class PrivateToken(Token):
"""Inherit from auth token, otherwise migration is boring""" """Inherit from auth token, otherwise migration is boring"""
@ -45,3 +48,17 @@ class SSOToken(models.JMSBaseModel):
authkey = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('Token')) authkey = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('Token'))
expired = models.BooleanField(default=False, verbose_name=_('Expired')) expired = models.BooleanField(default=False, verbose_name=_('Expired'))
user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User'), db_constraint=False) user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User'), db_constraint=False)
class Meta:
verbose_name = _('SSO token')
class ConnectionToken(models.JMSBaseModel):
# Todo: 未来可能放到这里,不记录到 redis 了,虽然方便,但是不易于审计
# Todo: add connection token 可能要授权给 普通用户, 或者放开就行
class Meta:
permissions = [
('add_superconnectiontoken', _('Can add super connection token')),
('view_connectiontokensecret', _('Can view connect token secret'))
]

View File

@ -2,10 +2,13 @@
# #
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from rest_framework import permissions from rest_framework import permissions
from rest_framework.decorators import action
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response
from common.permissions import IsValidUser
__all__ = ["PermissionsMixin"] __all__ = ["PermissionsMixin", "SuggestionMixin"]
class PermissionsMixin(UserPassesTestMixin): class PermissionsMixin(UserPassesTestMixin):
@ -23,3 +26,17 @@ class PermissionsMixin(UserPassesTestMixin):
return True return True
class SuggestionMixin:
suggestion_mini_count = 10
@action(methods=['get'], detail=False, permission_classes=(IsValidUser,))
def suggestions(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
queryset = queryset[:self.suggestion_mini_count]
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

View File

@ -5,9 +5,6 @@ from rest_framework import permissions
from django.conf import settings from django.conf import settings
from common.exceptions import MFAVerifyRequired from common.exceptions import MFAVerifyRequired
from orgs.utils import current_org
from common.utils import is_uuid
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission): class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
"""Allows access to valid user, is active and not expired""" """Allows access to valid user, is active and not expired"""
@ -17,80 +14,12 @@ class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
and request.user.is_valid and request.user.is_valid
class IsAppUser(IsValidUser): class OnlySuperUser(IsValidUser):
"""Allows access only to app user """
def has_permission(self, request, view): def has_permission(self, request, view):
return super(IsAppUser, self).has_permission(request, view) \ return super().has_permission(request, view) \
and request.user.is_app
class IsSuperUser(IsValidUser):
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser and request.user.is_superuser
class IsSuperUserOrAppUser(IsSuperUser):
def has_permission(self, request, view):
if request.user.is_anonymous:
return False
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
or request.user.is_app
class IsSuperAuditor(IsValidUser):
def has_permission(self, request, view):
return super(IsSuperAuditor, self).has_permission(request, view) \
and request.user.is_super_auditor
class IsOrgAuditor(IsValidUser):
def has_permission(self, request, view):
if not current_org:
return False
return super(IsOrgAuditor, self).has_permission(request, view) \
and current_org.can_audit_by(request.user)
class IsOrgAdmin(IsValidUser):
"""Allows access only to superuser"""
def has_permission(self, request, view):
if not current_org:
return False
return super(IsOrgAdmin, self).has_permission(request, view) \
and current_org.can_admin_by(request.user)
class IsOrgAdminOrAppUser(IsValidUser):
"""Allows access between superuser and app user"""
def has_permission(self, request, view):
if not current_org:
return False
if request.user.is_anonymous:
return False
return super(IsOrgAdminOrAppUser, self).has_permission(request, view) \
and (current_org.can_admin_by(request.user) or request.user.is_app)
class IsOrgAdminOrAppUserOrUserReadonly(IsOrgAdminOrAppUser):
def has_permission(self, request, view):
if IsValidUser.has_permission(self, request, view) \
and request.method in permissions.SAFE_METHODS:
return True
else:
return IsOrgAdminOrAppUser.has_permission(self, request, view)
class IsCurrentUserOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj == request.user
class WithBootstrapToken(permissions.BasePermission): class WithBootstrapToken(permissions.BasePermission):
def has_permission(self, request, view): def has_permission(self, request, view):
authorization = request.META.get('HTTP_AUTHORIZATION', '') authorization = request.META.get('HTTP_AUTHORIZATION', '')
@ -100,21 +29,6 @@ class WithBootstrapToken(permissions.BasePermission):
return settings.BOOTSTRAP_TOKEN == request_bootstrap_token return settings.BOOTSTRAP_TOKEN == request_bootstrap_token
class UserCanAnyPermCurrentOrg(permissions.BasePermission):
def has_permission(self, request, view):
return current_org.can_any_by(request.user)
class UserCanUpdatePassword(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.can_update_password()
class UserCanUpdateSSHKey(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.can_update_ssh_key()
class NeedMFAVerify(permissions.BasePermission): class NeedMFAVerify(permissions.BasePermission):
def has_permission(self, request, view): def has_permission(self, request, view):
if not settings.SECURITY_VIEW_AUTH_NEED_MFA: if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
@ -126,80 +40,7 @@ class NeedMFAVerify(permissions.BasePermission):
raise MFAVerifyRequired() raise MFAVerifyRequired()
class CanUpdateDeleteUser(permissions.BasePermission):
@staticmethod
def has_delete_object_permission(request, view, obj):
if request.user.is_anonymous:
return False
if not request.user.can_admin_current_org:
return False
# 超级管理员 / 组织管理员
if str(request.user.id) == str(obj.id):
return False
# 超级管理员
if request.user.is_superuser:
if obj.is_superuser and obj.username in ['admin']:
return False
return True
# 组织管理员
if obj.is_superuser:
return False
if obj.is_super_auditor:
return False
if obj.can_admin_current_org:
return False
return True
@staticmethod
def has_update_object_permission(request, view, obj):
if request.user.is_anonymous:
return False
if not request.user.can_admin_current_org:
return False
# 超级管理员 / 组织管理员
if str(request.user.id) == str(obj.id):
return True
# 超级管理员
if request.user.is_superuser:
return True
# 组织管理员
if obj.is_superuser:
return False
if obj.is_super_auditor:
return False
return True
def has_object_permission(self, request, view, obj):
if request.user.is_anonymous:
return False
if not request.user.can_admin_current_org:
return False
if request.method in ['DELETE']:
return self.has_delete_object_permission(request, view, obj)
if request.method in ['PUT', 'PATCH']:
return self.has_update_object_permission(request, view, obj)
return True
class IsObjectOwner(IsValidUser): class IsObjectOwner(IsValidUser):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return (super().has_object_permission(request, view, obj) and return (super().has_object_permission(request, view, obj) and
request.user == getattr(obj, 'user', None)) request.user == getattr(obj, 'user', None))
class HasQueryParamsUserAndIsCurrentOrgMember(permissions.BasePermission):
def has_permission(self, request, view):
query_user_id = request.query_params.get('user')
if not query_user_id or not is_uuid(query_user_id):
return False
query_user = current_org.get_members().filter(id=query_user_id).first()
return bool(query_user)
class OnlySuperUserCanList(IsValidUser):
def has_permission(self, request, view):
user = request.user
if view.action == 'list' and not user.is_superuser:
return False
return True

View File

@ -15,6 +15,7 @@ class TreeNode:
iconSkin = "" iconSkin = ""
parentInfo = '' parentInfo = ''
meta = {} meta = {}
checked = False
_tree = None _tree = None
@ -101,4 +102,7 @@ class TreeNodeSerializer(serializers.Serializer):
open = serializers.BooleanField(default=False) open = serializers.BooleanField(default=False)
iconSkin = serializers.CharField(max_length=128, allow_blank=True) iconSkin = serializers.CharField(max_length=128, allow_blank=True)
nocheck = serializers.BooleanField(default=False) nocheck = serializers.BooleanField(default=False)
checked = serializers.BooleanField(default=False)
halfCheck = serializers.BooleanField(default=False)
chkDisabled = serializers.BooleanField(default=False)
meta = serializers.JSONField() meta = serializers.JSONField()

View File

@ -15,8 +15,7 @@ from assets.models import Asset
from terminal.models import Session from terminal.models import Session
from terminal.utils import ComponentsPrometheusMetricsUtil from terminal.utils import ComponentsPrometheusMetricsUtil
from orgs.utils import current_org from orgs.utils import current_org
from common.permissions import IsOrgAdmin, IsOrgAuditor from common.utils import lazyproperty
from common.utils import lazyproperty, get_request_ip
from orgs.caches import OrgResourceStatisticsCache from orgs.caches import OrgResourceStatisticsCache
@ -213,8 +212,10 @@ class DatesLoginMetricMixin:
class IndexApi(DatesLoginMetricMixin, APIView): class IndexApi(DatesLoginMetricMixin, APIView):
permission_classes = (IsOrgAdmin | IsOrgAuditor,)
http_method_names = ['get'] http_method_names = ['get']
rbac_perms = {
'GET': 'view_auditview'
}
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
data = {} data = {}

View File

@ -139,6 +139,7 @@ OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS
AUTH_BACKEND_MODEL = 'authentication.backends.api.JMSModelBackend' AUTH_BACKEND_MODEL = 'authentication.backends.api.JMSModelBackend'
RBAC_BACKEND = 'rbac.backends.RBACBackend'
AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend' AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend'
AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend' AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend'
AUTH_BACKEND_OIDC_PASSWORD = 'jms_oidc_rp.backends.OIDCAuthPasswordBackend' AUTH_BACKEND_OIDC_PASSWORD = 'jms_oidc_rp.backends.OIDCAuthPasswordBackend'
@ -154,7 +155,7 @@ AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend'
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_WECOM, AUTH_BACKEND_MODEL, RBAC_BACKEND, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_WECOM,
AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU, AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU, AUTH_BACKEND_AUTH_TOKEN,
AUTH_BACKEND_SSO, AUTH_BACKEND_SSO,
] ]

View File

@ -49,6 +49,7 @@ INSTALLED_APPS = [
'tickets.apps.TicketsConfig', 'tickets.apps.TicketsConfig',
'acls.apps.AclsConfig', 'acls.apps.AclsConfig',
'notifications.apps.NotificationsConfig', 'notifications.apps.NotificationsConfig',
'rbac.apps.RBACConfig',
'common.apps.CommonConfig', 'common.apps.CommonConfig',
'jms_oidc_rp', 'jms_oidc_rp',
'rest_framework', 'rest_framework',

View File

@ -7,7 +7,7 @@ REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions, # Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users. # or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': ( 'DEFAULT_PERMISSION_CLASSES': (
'common.permissions.IsSuperUser', 'rbac.permissions.RBACPermission',
), ),
'DEFAULT_RENDERER_CLASSES': ( 'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.JSONRenderer',

View File

@ -24,6 +24,7 @@ api_v1 = [
path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')), path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')),
path('acls/', include('acls.urls.api_urls', namespace='api-acls')), path('acls/', include('acls.urls.api_urls', namespace='api-acls')),
path('notifications/', include('notifications.urls.api_urls', namespace='api-notifications')), path('notifications/', include('notifications.urls.api_urls', namespace='api-notifications')),
path('rbac/', include('rbac.urls.api_urls', namespace='api-rbac')),
path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()), path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()),
] ]

View File

@ -3,7 +3,7 @@ from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from common.drf.api import JMSGenericViewSet from common.drf.api import JMSGenericViewSet
from common.permissions import IsObjectOwner, IsSuperUser, OnlySuperUserCanList from common.permissions import IsValidUser
from notifications.notifications import system_msgs from notifications.notifications import system_msgs
from notifications.models import SystemMsgSubscription, UserMsgSubscription from notifications.models import SystemMsgSubscription, UserMsgSubscription
from notifications.backends import BACKEND from notifications.backends import BACKEND
@ -25,7 +25,7 @@ class BackendListView(APIView):
'name': backend, 'name': backend,
'name_display': backend.label 'name_display': backend.label
} }
for backend in BACKEND for backend in BACKEND.choices
if backend.is_enable if backend.is_enable
] ]
return Response(data=data) return Response(data=data)
@ -41,6 +41,9 @@ class SystemMsgSubscriptionViewSet(ListModelMixin,
'update': SystemMsgSubscriptionSerializer, 'update': SystemMsgSubscriptionSerializer,
'partial_update': SystemMsgSubscriptionSerializer 'partial_update': SystemMsgSubscriptionSerializer
} }
rbac_perms = {
}
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
data = [] data = []
@ -80,9 +83,13 @@ class UserMsgSubscriptionViewSet(ListModelMixin,
UpdateModelMixin, UpdateModelMixin,
JMSGenericViewSet): JMSGenericViewSet):
lookup_field = 'user_id' lookup_field = 'user_id'
queryset = UserMsgSubscription.objects.all()
serializer_class = UserMsgSubscriptionSerializer serializer_class = UserMsgSubscriptionSerializer
permission_classes = (IsObjectOwner | IsSuperUser, OnlySuperUserCanList) permission_classes = (IsValidUser,)
def get_queryset(self):
queryset = UserMsgSubscription.objects.filter(user=self.request.user)
return queryset
def get_all_test_messages(request): def get_all_test_messages(request):

View File

@ -1,8 +1,10 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class NotificationsConfig(AppConfig): class NotificationsConfig(AppConfig):
name = 'notifications' name = 'notifications'
verbose_name = _('Notifications')
def ready(self): def ready(self):
from . import signals_handler from . import signals_handler

View File

@ -5,8 +5,6 @@ from django.shortcuts import get_object_or_404
from rest_framework import viewsets, generics from rest_framework import viewsets, generics
from rest_framework.views import Response from rest_framework.views import Response
from common.drf.api import JMSBulkModelViewSet
from common.permissions import IsOrgAdmin
from common.drf.serializers import CeleryTaskSerializer from common.drf.serializers import CeleryTaskSerializer
from ..models import Task, AdHoc, AdHocExecution from ..models import Task, AdHoc, AdHocExecution
from ..serializers import ( from ..serializers import (
@ -18,7 +16,6 @@ from ..serializers import (
) )
from ..tasks import run_ansible_task from ..tasks import run_ansible_task
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
__all__ = [ __all__ = [
'TaskViewSet', 'TaskRun', 'AdHocViewSet', 'AdHocRunHistoryViewSet' 'TaskViewSet', 'TaskRun', 'AdHocViewSet', 'AdHocRunHistoryViewSet'
@ -30,7 +27,6 @@ class TaskViewSet(OrgBulkModelViewSet):
filterset_fields = ("name",) filterset_fields = ("name",)
search_fields = filterset_fields search_fields = filterset_fields
serializer_class = TaskSerializer serializer_class = TaskSerializer
permission_classes = (IsOrgAdmin,)
def get_serializer_class(self): def get_serializer_class(self):
if self.action == 'retrieve': if self.action == 'retrieve':
@ -46,7 +42,6 @@ class TaskViewSet(OrgBulkModelViewSet):
class TaskRun(generics.RetrieveAPIView): class TaskRun(generics.RetrieveAPIView):
queryset = Task.objects.all() queryset = Task.objects.all()
serializer_class = CeleryTaskSerializer serializer_class = CeleryTaskSerializer
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
task = self.get_object() task = self.get_object()
@ -57,7 +52,6 @@ class TaskRun(generics.RetrieveAPIView):
class AdHocViewSet(viewsets.ModelViewSet): class AdHocViewSet(viewsets.ModelViewSet):
queryset = AdHoc.objects.all() queryset = AdHoc.objects.all()
serializer_class = AdHocSerializer serializer_class = AdHocSerializer
permission_classes = (IsOrgAdmin,)
def get_serializer_class(self): def get_serializer_class(self):
if self.action == 'retrieve': if self.action == 'retrieve':
@ -75,7 +69,6 @@ class AdHocViewSet(viewsets.ModelViewSet):
class AdHocRunHistoryViewSet(viewsets.ModelViewSet): class AdHocRunHistoryViewSet(viewsets.ModelViewSet):
queryset = AdHocExecution.objects.all() queryset = AdHocExecution.objects.all()
serializer_class = AdHocExecutionSerializer serializer_class = AdHocExecutionSerializer
permission_classes = (IsOrgAdmin,)
def get_queryset(self): def get_queryset(self):
task_id = self.request.query_params.get('task') task_id = self.request.query_params.get('task')

View File

@ -10,7 +10,7 @@ from celery.result import AsyncResult
from rest_framework import generics from rest_framework import generics
from django_celery_beat.models import PeriodicTask from django_celery_beat.models import PeriodicTask
from common.permissions import IsValidUser, IsSuperUser from common.permissions import IsValidUser
from common.api import LogTailApi from common.api import LogTailApi
from ..models import CeleryTask from ..models import CeleryTask
from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer
@ -88,7 +88,6 @@ class CeleryResultApi(generics.RetrieveAPIView):
class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):
queryset = PeriodicTask.objects.all() queryset = PeriodicTask.objects.all()
serializer_class = CeleryPeriodTaskSerializer serializer_class = CeleryPeriodTaskSerializer
permission_classes = (IsSuperUser,)
http_method_names = ('get', 'head', 'options', 'patch') http_method_names = ('get', 'head', 'options', 'patch')
def get_queryset(self): def get_queryset(self):

View File

@ -0,0 +1,29 @@
# Generated by Django 3.1.13 on 2021-11-30 02:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ops', '0020_adhoc_run_system_user'),
]
operations = [
migrations.AlterModelOptions(
name='adhoc',
options={'get_latest_by': 'date_created', 'verbose_name': 'AdHoc'},
),
migrations.AlterModelOptions(
name='adhocexecution',
options={'get_latest_by': 'date_start', 'verbose_name': 'AdHoc execution'},
),
migrations.AlterModelOptions(
name='commandexecution',
options={'verbose_name': 'Command execution'},
),
migrations.AlterModelOptions(
name='task',
options={'get_latest_by': 'date_created', 'ordering': ('-date_updated',), 'verbose_name': 'Task'},
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.12 on 2022-02-11 06:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ops', '0021_auto_20211130_1037'),
]
operations = [
migrations.AlterModelOptions(
name='task',
options={'get_latest_by': 'date_created', 'ordering': ('-date_updated',), 'permissions': [('view_taskmonitor', 'Can view task monitor')], 'verbose_name': 'Task'},
),
]

View File

@ -38,7 +38,8 @@ class Task(PeriodTaskModelMixin, OrgModelMixin):
comment = models.TextField(blank=True, verbose_name=_("Comment")) comment = models.TextField(blank=True, verbose_name=_("Comment"))
date_created = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Date created")) date_created = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
latest_adhoc = models.ForeignKey('ops.AdHoc', on_delete=models.SET_NULL, null=True, related_name='task_latest') latest_adhoc = models.ForeignKey('ops.AdHoc', on_delete=models.SET_NULL,
null=True, related_name='task_latest')
latest_execution = models.ForeignKey('ops.AdHocExecution', on_delete=models.SET_NULL, null=True, related_name='task_latest') latest_execution = models.ForeignKey('ops.AdHocExecution', on_delete=models.SET_NULL, null=True, related_name='task_latest')
total_run_amount = models.IntegerField(default=0) total_run_amount = models.IntegerField(default=0)
success_run_amount = models.IntegerField(default=0) success_run_amount = models.IntegerField(default=0)
@ -131,7 +132,11 @@ class Task(PeriodTaskModelMixin, OrgModelMixin):
db_table = 'ops_task' db_table = 'ops_task'
unique_together = ('name', 'org_id') unique_together = ('name', 'org_id')
ordering = ('-date_updated',) ordering = ('-date_updated',)
verbose_name = _("Task")
get_latest_by = 'date_created' get_latest_by = 'date_created'
permissions = [
('view_taskmonitor', _('Can view task monitor'))
]
class AdHoc(OrgModelMixin): class AdHoc(OrgModelMixin):
@ -235,6 +240,7 @@ class AdHoc(OrgModelMixin):
class Meta: class Meta:
db_table = "ops_adhoc" db_table = "ops_adhoc"
get_latest_by = 'date_created' get_latest_by = 'date_created'
verbose_name = _('AdHoc')
class AdHocExecution(OrgModelMixin): class AdHocExecution(OrgModelMixin):
@ -330,3 +336,4 @@ class AdHocExecution(OrgModelMixin):
class Meta: class Meta:
db_table = "ops_adhoc_execution" db_table = "ops_adhoc_execution"
get_latest_by = 'date_start' get_latest_by = 'date_start'
verbose_name = _("AdHoc execution")

View File

@ -2,6 +2,8 @@
# #
import uuid import uuid
import os import os
from django.utils.translation import gettext_lazy as _
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models

View File

@ -155,3 +155,6 @@ class CommandExecution(OrgModelMixin):
self.save() self.save()
print('-' * 10 + ' ' + ugettext('Task end') + ' ' + '-' * 10) print('-' * 10 + ' ' + ugettext('Task end') + ' ' + '-' * 10)
return self.result return self.result
class Meta:
verbose_name = _("Command execution")

View File

@ -32,7 +32,11 @@ class ServerPerformanceMessage(SystemMessage):
@classmethod @classmethod
def post_insert_to_db(cls, subscription: SystemMsgSubscription): def post_insert_to_db(cls, subscription: SystemMsgSubscription):
admins = User.objects.filter(role=User.ROLE.ADMIN) from rbac.models import Role, RoleBinding
# Todo: 需要更改这里
admin_role = Role.BuiltinRole.system_admin.get_role()
admins_ids = RoleBinding.objects.filter(role=admin_role).values_list('user_id', flat=True)
admins = User.objects.filter(id__in=admins_ids)
subscription.users.add(*admins) subscription.users.add(*admins)
subscription.receive_backends = [BACKEND.EMAIL] subscription.receive_backends = [BACKEND.EMAIL]
subscription.save() subscription.save()

View File

@ -3,15 +3,18 @@
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.conf import settings from django.conf import settings
from common.permissions import IsOrgAdmin, IsOrgAuditor
from common.mixins.views import PermissionsMixin from common.mixins.views import PermissionsMixin
from rbac.permissions import RBACPermission
__all__ = ['CeleryTaskLogView'] __all__ = ['CeleryTaskLogView']
class CeleryTaskLogView(PermissionsMixin, TemplateView): class CeleryTaskLogView(PermissionsMixin, TemplateView):
template_name = 'ops/celery_task_log.html' template_name = 'ops/celery_task_log.html'
permission_classes = [IsOrgAdmin | IsOrgAuditor] permission_classes = [RBACPermission]
rbac_perms = {
'GET': 'view_tasklog'
}
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)

View File

@ -2,20 +2,14 @@
# #
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import status
from rest_framework.views import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework.generics import RetrieveAPIView from rest_framework.generics import RetrieveAPIView
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from common.permissions import IsSuperUserOrAppUser, IsValidUser, UserCanAnyPermCurrentOrg from common.permissions import IsValidUser
from common.drf.api import JMSBulkRelationModelViewSet from .models import Organization
from .models import Organization, ROLE
from .serializers import ( from .serializers import (
OrgSerializer, OrgReadSerializer, OrgSerializer, CurrentOrgSerializer
OrgRetrieveSerializer, OrgMemberSerializer,
OrgMemberAdminSerializer, OrgMemberUserSerializer,
CurrentOrgSerializer
) )
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import ( from assets.models import (
@ -26,8 +20,6 @@ from applications.models import Application
from perms.models import AssetPermission, ApplicationPermission from perms.models import AssetPermission, ApplicationPermission
from orgs.utils import current_org, tmp_to_root_org from orgs.utils import current_org, tmp_to_root_org
from common.utils import get_logger from common.utils import get_logger
from .filters import OrgMemberRelationFilterSet
from .models import OrganizationMember
logger = get_logger(__file__) logger = get_logger(__file__)
@ -47,23 +39,20 @@ class OrgViewSet(BulkModelViewSet):
search_fields = ('name', 'comment') search_fields = ('name', 'comment')
queryset = Organization.objects.all() queryset = Organization.objects.all()
serializer_class = OrgSerializer serializer_class = OrgSerializer
permission_classes = (IsSuperUserOrAppUser,)
ordering_fields = ('name',) ordering_fields = ('name',)
ordering = ('name', ) ordering = ('name', )
def get_serializer_class(self): def get_serializer_class(self):
mapper = { mapper = {
'list': OrgReadSerializer, 'list': OrgSerializer,
'retrieve': OrgRetrieveSerializer 'retrieve': OrgSerializer
} }
return mapper.get(self.action, super().get_serializer_class()) return mapper.get(self.action, super().get_serializer_class())
@tmp_to_root_org() @tmp_to_root_org()
def get_data_from_model(self, org, model): def get_data_from_model(self, org, model):
if model == User: if model == User:
data = model.objects.filter( data = model.get_org_users(org=org)
orgs__id=org.id, m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR]
)
elif model == Node: elif model == Node:
# 根节点不能手动删除,所以排除检查 # 根节点不能手动删除,所以排除检查
data = model.objects.filter(org_id=org.id).exclude(parent_key='', key__regex=r'^[0-9]+$') data = model.objects.filter(org_id=org.id).exclude(parent_key='', key__regex=r'^[0-9]+$')
@ -91,77 +80,9 @@ class OrgViewSet(BulkModelViewSet):
super().perform_destroy(instance) super().perform_destroy(instance)
class OrgMemberRelationBulkViewSet(JMSBulkRelationModelViewSet):
permission_classes = (IsSuperUserOrAppUser,)
m2m_field = Organization.members.field
serializer_class = OrgMemberSerializer
filterset_class = OrgMemberRelationFilterSet
search_fields = ('user__name', 'user__username', 'org__name')
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.exclude(user__role=User.ROLE.APP)
return queryset
def perform_bulk_destroy(self, queryset):
objs = list(queryset.all().prefetch_related('user', 'org'))
queryset.delete()
self.send_m2m_changed_signal(objs, action='post_remove')
class OrgMemberAdminRelationBulkViewSet(JMSBulkRelationModelViewSet):
permission_classes = (IsSuperUserOrAppUser,)
m2m_field = Organization.members.field
serializer_class = OrgMemberAdminSerializer
filterset_class = OrgMemberRelationFilterSet
search_fields = ('user__name', 'user__username', 'org__name')
lookup_field = 'user_id'
def get_queryset(self):
queryset = super().get_queryset()
org_id = self.kwargs.get('org_id')
queryset = queryset.filter(org_id=org_id, role=ROLE.ADMIN)
return queryset
def perform_bulk_create(self, serializer):
data = serializer.validated_data
relations = [OrganizationMember(**i) for i in data]
OrganizationMember.objects.bulk_create(relations, ignore_conflicts=True)
def perform_bulk_destroy(self, queryset):
objs = list(queryset.all().prefetch_related('user', 'org'))
queryset.delete()
self.send_m2m_changed_signal(objs, action='post_remove')
class OrgMemberUserRelationBulkViewSet(JMSBulkRelationModelViewSet):
permission_classes = (IsSuperUserOrAppUser,)
m2m_field = Organization.members.field
serializer_class = OrgMemberUserSerializer
filterset_class = OrgMemberRelationFilterSet
search_fields = ('user__name', 'user__username', 'org__name')
lookup_field = 'user_id'
def get_queryset(self):
queryset = super().get_queryset()
org_id = self.kwargs.get('org_id')
queryset = queryset.filter(org_id=org_id, role=ROLE.USER)
return queryset
def perform_bulk_create(self, serializer):
data = serializer.validated_data
relations = [OrganizationMember(**i) for i in data]
OrganizationMember.objects.bulk_create(relations, ignore_conflicts=True)
def perform_bulk_destroy(self, queryset):
objs = list(queryset.all().prefetch_related('user', 'org'))
queryset.delete()
self.send_m2m_changed_signal(objs, action='post_remove')
class CurrentOrgDetailApi(RetrieveAPIView): class CurrentOrgDetailApi(RetrieveAPIView):
serializer_class = CurrentOrgSerializer serializer_class = CurrentOrgSerializer
permission_classes = (IsValidUser, UserCanAnyPermCurrentOrg) permission_classes = (IsValidUser,)
def get_object(self): def get_object(self):
return current_org return current_org

View File

@ -1,8 +1,10 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class OrgsConfig(AppConfig): class OrgsConfig(AppConfig):
name = 'orgs' name = 'orgs'
verbose_name = _('Organizations')
def ready(self): def ready(self):
from . import signals_handler from . import signals_handler

View File

@ -6,11 +6,10 @@ from orgs.utils import current_org, tmp_to_org
from common.cache import Cache, IntegerField from common.cache import Cache, IntegerField
from common.utils import get_logger from common.utils import get_logger
from users.models import UserGroup, User from users.models import UserGroup, User
from assets.models import Node, AdminUser, SystemUser, Domain, Gateway, Asset from assets.models import Node, SystemUser, Domain, Gateway, Asset
from terminal.models import Session from terminal.models import Session
from applications.models import Application from applications.models import Application
from perms.models import AssetPermission, ApplicationPermission from perms.models import AssetPermission, ApplicationPermission
from .models import OrganizationMember
logger = get_logger(__file__) logger = get_logger(__file__)
@ -84,13 +83,8 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
return SystemUser.objects.filter(type=SystemUser.Type.common).count() return SystemUser.objects.filter(type=SystemUser.Type.common).count()
def compute_users_amount(self): def compute_users_amount(self):
users = User.objects.exclude(role='App') amount = User.get_org_users(self.org).count()
return amount
if not self.org.is_root():
users = users.filter(m2m_org_members__org_id=self.org.id)
users_amount = users.values('id').distinct().count()
return users_amount
def compute_assets_amount(self): def compute_assets_amount(self):
if self.org.is_root(): if self.org.is_root():

View File

@ -1,17 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .utils import current_org, get_org_from_request from .utils import get_org_from_request
from .models import Organization
def org_processor(request): def org_processor(request):
context = { context = {
# 'ADMIN_ORGS': request.user.admin_orgs,
# 'AUDIT_ORGS': request.user.audit_orgs,
'ADMIN_OR_AUDIT_ORGS': Organization.get_user_admin_or_audit_orgs(request.user),
'CURRENT_ORG': get_org_from_request(request), 'CURRENT_ORG': get_org_from_request(request),
# 'HAS_ORG_PERM': current_org.can_admin_by(request.user),
} }
return context return context

View File

@ -1,16 +1,6 @@
from django_filters.rest_framework import filterset
from django_filters.rest_framework import filters from django_filters.rest_framework import filters
from .models import OrganizationMember
class UUIDInFilter(filters.BaseInFilter, filters.UUIDFilter): class UUIDInFilter(filters.BaseInFilter, filters.UUIDFilter):
pass pass
class OrgMemberRelationFilterSet(filterset.FilterSet):
id = UUIDInFilter(field_name='id', lookup_expr='in')
class Meta:
model = OrganizationMember
fields = ('org_id', 'user_id', 'org', 'user', 'role', 'id')

View File

@ -2,6 +2,7 @@
# #
from .utils import get_org_from_request, set_current_org from .utils import get_org_from_request, set_current_org
from rbac.models import RoleBinding
class OrgMiddleware: class OrgMiddleware:
@ -14,22 +15,19 @@ class OrgMiddleware:
return return
if not request.user.is_authenticated: if not request.user.is_authenticated:
return return
if request.user.is_common_user:
return
org = get_org_from_request(request) org = get_org_from_request(request)
if org.can_admin_by(request.user):
return search_org = None if org.is_root() else org
if org.can_audit_by(request.user): has_roles = RoleBinding.objects.filter(user=request.user, org=search_org).exists()
return if has_roles:
admin_orgs = request.user.admin_orgs
if admin_orgs:
request.session['oid'] = str(admin_orgs[0].id)
return
audit_orgs = request.user.audit_orgs
if audit_orgs:
request.session['oid'] = str(audit_orgs[0].id)
return return
roles_bindings = RoleBinding.objects.filter(user=request.user).exclude(org=None)
if roles_bindings:
org_id = str(list(roles_bindings.values_list('org_id', flat=True))[0])
request.session['oid'] = org_id
def __call__(self, request): def __call__(self, request):
self.set_permed_org_if_need(request) self.set_permed_org_if_need(request)
org = get_org_from_request(request) org = get_org_from_request(request)

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.13 on 2021-12-23 11:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orgs', '0010_auto_20210219_1241'),
]
operations = [
migrations.AlterField(
model_name='organizationmember',
name='role',
field=models.CharField(default='User', max_length=16, verbose_name='Role'),
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 3.1.13 on 2022-01-18 02:54
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('rbac', '0004_auto_20211201_1901'),
('orgs', '0011_auto_20211223_1913'),
]
operations = [
migrations.AlterModelOptions(
name='organization',
options={'permissions': (('view_rootorg', 'Can view root org'),), 'verbose_name': 'Organization'},
),
migrations.AlterField(
model_name='organization',
name='members',
field=models.ManyToManyField(related_name='orgs', through='rbac.RoleBinding', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -1,22 +1,10 @@
import uuid import uuid
from functools import partial
from itertools import chain
from django.db import models from django.db import models
from django.db.models import signals
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty, settings from common.utils import lazyproperty, settings
from common.const import choices
from common.tree import TreeNode from common.tree import TreeNode
from common.db.models import TextChoices
class ROLE(TextChoices):
ADMIN = choices.ADMIN, _('Organization administrator')
AUDITOR = choices.AUDITOR, _("Organization auditor")
USER = choices.USER, _('User')
class Organization(models.Model): class Organization(models.Model):
@ -25,7 +13,7 @@ class Organization(models.Model):
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
members = models.ManyToManyField('users.User', related_name='orgs', through='orgs.OrganizationMember', through_fields=('org', 'user')) members = models.ManyToManyField('users.User', related_name='orgs', through='rbac.RoleBinding', through_fields=('org', 'user'))
ROOT_ID = '00000000-0000-0000-0000-000000000000' ROOT_ID = '00000000-0000-0000-0000-000000000000'
ROOT_NAME = _('GLOBAL') ROOT_NAME = _('GLOBAL')
@ -35,6 +23,9 @@ class Organization(models.Model):
class Meta: class Meta:
verbose_name = _("Organization") verbose_name = _("Organization")
permissions = (
('view_rootorg', _('Can view root org')),
)
def __str__(self): def __str__(self):
return str(self.name) return str(self.name)
@ -79,113 +70,9 @@ class Organization(models.Model):
def expire_orgs_mapping(cls): def expire_orgs_mapping(cls):
cls.orgs_mapping = None cls.orgs_mapping = None
def get_org_members_by_role(self, role):
from users.models import User
if not self.is_root():
return self.members.filter(m2m_org_members__role=role)
users = User.objects.filter(role=role)
return users
@property
def users(self):
return self.get_org_members_by_role(ROLE.USER)
@property
def admins(self):
return self.get_org_members_by_role(ROLE.ADMIN)
@property
def auditors(self):
return self.get_org_members_by_role(ROLE.AUDITOR)
def org_id(self): def org_id(self):
return self.id return self.id
def get_members(self, exclude=()):
from users.models import User
if self.is_root():
members = User.objects.exclude(role__in=exclude)
else:
members = self.members.exclude(m2m_org_members__role__in=exclude)
return members.exclude(role=User.ROLE.APP).distinct()
def can_admin_by(self, user):
if user.is_superuser:
return True
if self.admins.filter(id=user.id).exists():
return True
return False
def can_audit_by(self, user):
if user.is_superuser or user.is_super_auditor:
return True
if self.can_admin_by(user):
return True
if self.auditors.filter(id=user.id).exists():
return True
return False
def can_use_by(self, user):
if user.is_superuser or user.is_super_auditor:
return True
if self.can_audit_by(user):
return True
if self.users.filter(id=user.id).exists():
return True
return False
def can_any_by(self, user):
if user.is_superuser or user.is_super_auditor:
return True
return self.members.filter(id=user.id).exists()
@classmethod
def get_user_orgs_by_role(cls, user, role):
if not isinstance(role, (tuple, list)):
role = (role, )
return cls.objects.filter(
m2m_org_members__role__in=role,
m2m_org_members__user_id=user.id
).distinct()
@classmethod
def get_user_all_orgs(cls, user):
return cls.objects.filter(members=user).distinct()
@classmethod
def get_user_admin_orgs(cls, user):
if user.is_anonymous:
return cls.objects.none()
if user.is_superuser:
return [cls.root(), *cls.objects.all()]
return cls.get_user_orgs_by_role(user, ROLE.ADMIN)
@classmethod
def get_user_user_orgs(cls, user):
if user.is_anonymous:
return cls.objects.none()
return [
*cls.get_user_orgs_by_role(user, ROLE.USER),
cls.default()
]
@classmethod
def get_user_audit_orgs(cls, user):
if user.is_anonymous:
return cls.objects.none()
if user.is_super_auditor:
return [cls.root(), *cls.objects.all()]
return cls.get_user_orgs_by_role(user, ROLE.AUDITOR)
@classmethod
def get_user_admin_or_audit_orgs(cls, user):
if user.is_anonymous:
return cls.objects.none()
if user.is_superuser or user.is_super_auditor:
return [cls.root(), *cls.objects.all()]
return cls.get_user_orgs_by_role(user, (ROLE.AUDITOR, ROLE.ADMIN))
@classmethod @classmethod
def default(cls): def default(cls):
defaults = dict(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME) defaults = dict(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME)
@ -209,13 +96,18 @@ class Organization(models.Model):
@lazyproperty @lazyproperty
def resource_statistics_cache(self): def resource_statistics_cache(self):
# Todo: 由于 redis 问题,没能获取到
return {}
from .caches import OrgResourceStatisticsCache from .caches import OrgResourceStatisticsCache
return OrgResourceStatisticsCache(self) return OrgResourceStatisticsCache(self)
def get_members(self):
return self.members.all().distinct()
def get_total_resources_amount(self): def get_total_resources_amount(self):
from django.apps import apps from django.apps import apps
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
summary = {'users.Members': self.members.all().count()} summary = {'users.Members': self.get_members().count()}
for app_name, app_config in apps.app_configs.items(): for app_name, app_config in apps.app_configs.items():
models_cls = app_config.get_models() models_cls = app_config.get_models()
for model in models_cls: for model in models_cls:
@ -249,178 +141,24 @@ class Organization(models.Model):
return node return node
def _convert_to_uuid_set(users): # class OrgMemberManager(models.Manager):
rst = set() # def remove_users(self, org, users):
for user in users: # from users.models import User
if isinstance(user, models.Model): # pk_set = []
rst.add(user.id) # for user in users:
elif not isinstance(user, uuid.UUID): # if hasattr(user, 'pk'):
rst.add(uuid.UUID(user)) # pk_set.append(user.pk)
return rst # else:
# pk_set.append(user)
#
def _none2list(*args): # send = partial(
return ([] if v is None else v for v in args) # signals.m2m_changed.send, sender=self.model,
# instance=org, reverse=False, model=User,
# pk_set=pk_set, using=self.db
def _users2pks_if_need(users, admins, auditors): # )
pks = [] # send(action="pre_remove")
for user in chain(users, admins, auditors): # self.filter(org_id=org.id, user_id__in=pk_set).delete()
if hasattr(user, 'pk'): # send(action="post_remove")
pks.append(user.pk)
else:
pks.append(user)
return pks
class UserRoleMapper(dict):
def __init__(self, container=set):
super().__init__()
self.users = container()
self.admins = container()
self.auditors = container()
self[ROLE.USER] = self.users
self[ROLE.ADMIN] = self.admins
self[ROLE.AUDITOR] = self.auditors
class OrgMemberManager(models.Manager):
def remove_users(self, org, users):
from users.models import User
pk_set = []
for user in users:
if hasattr(user, 'pk'):
pk_set.append(user.pk)
else:
pk_set.append(user)
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=User, pk_set=pk_set, using=self.db)
send(action="pre_remove")
self.filter(org_id=org.id, user_id__in=pk_set).delete()
send(action="post_remove")
def remove_users_by_role(self, org, users=None, admins=None, auditors=None):
from users.models import User
if not any((users, admins, auditors)):
return
users, admins, auditors = _none2list(users, admins, auditors)
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=User, pk_set=_users2pks_if_need(users, admins, auditors), using=self.db)
send(action="pre_remove")
self.filter(org_id=org.id).filter(
Q(user__in=users, role=ROLE.USER) |
Q(user__in=admins, role=ROLE.ADMIN) |
Q(user__in=auditors, role=ROLE.AUDITOR)
).delete()
send(action="post_remove")
def add_users_by_role(self, org, users=None, admins=None, auditors=None):
from users.models import User
if not any((users, admins, auditors)):
return
users, admins, auditors = _none2list(users, admins, auditors)
add_mapper = (
(users, ROLE.USER),
(admins, ROLE.ADMIN),
(auditors, ROLE.AUDITOR)
)
oms_add = []
for _users, _role in add_mapper:
for _user in _users:
if isinstance(_user, models.Model):
_user = _user.id
oms_add.append(self.model(org_id=org.id, user_id=_user, role=_role))
pk_set = _users2pks_if_need(users, admins, auditors)
send = partial(
signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=User, pk_set=pk_set, using=self.db
)
send(action='pre_add')
self.bulk_create(oms_add, ignore_conflicts=True)
send(action='post_add')
def _get_remove_add_set(self, new_users, old_users):
if new_users is None:
return None, None
new_users = _convert_to_uuid_set(new_users)
return (old_users - new_users), (new_users - old_users)
def set_user_roles(self, org, user, roles):
"""
设置某个用户在某个组织里的角色
"""
old_roles = set(self.filter(org_id=org.id, user=user).values_list('role', flat=True))
new_roles = set(roles)
roles_remove = old_roles - new_roles
roles_add = new_roles - old_roles
to_remove = UserRoleMapper()
to_add = UserRoleMapper()
for role in roles_remove:
if role in to_remove:
to_remove[role].add(user)
for role in roles_add:
if role in to_add:
to_add[role].add(user)
# 先添加再移除 (防止用户角色由组织用户->组织管理员时从组织清除用户)
self.add_users_by_role(
org,
to_add.users,
to_add.admins,
to_add.auditors
)
self.remove_users_by_role(
org,
to_remove.users,
to_remove.admins,
to_remove.auditors
)
def set_users_by_role(self, org, users=None, admins=None, auditors=None):
"""
给组织设置带角色的用户
"""
oms = self.filter(org_id=org.id).values_list('role', 'user_id')
old_mapper = UserRoleMapper()
for role, user_id in oms:
if role in old_mapper:
old_mapper[role].add(user_id)
users_remove, users_add = self._get_remove_add_set(users, old_mapper.users)
admins_remove, admins_add = self._get_remove_add_set(admins, old_mapper.admins)
auditors_remove, auditors_add = self._get_remove_add_set(auditors, old_mapper.auditors)
self.remove_users_by_role(
org,
users_remove,
admins_remove,
auditors_remove
)
self.add_users_by_role(
org,
users_add,
admins_add,
auditors_add
)
class OrganizationMember(models.Model): class OrganizationMember(models.Model):
@ -431,16 +169,15 @@ class OrganizationMember(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
org = models.ForeignKey(Organization, related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('Organization')) org = models.ForeignKey(Organization, related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('Organization'))
user = models.ForeignKey('users.User', related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('User')) user = models.ForeignKey('users.User', related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('User'))
role = models.CharField(max_length=16, choices=ROLE.choices, default=ROLE.USER, verbose_name=_("Role")) role = models.CharField(max_length=16, default='User', verbose_name=_("Role"))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
# objects = OrgMemberManager()
objects = OrgMemberManager()
class Meta: class Meta:
unique_together = [('org', 'user', 'role')] unique_together = [('org', 'user', 'role')]
db_table = 'orgs_organization_members' db_table = 'orgs_organization_members'
def __str__(self): def __str__(self):
return '{} is {}: {}'.format(self.user.name, self.org.name, self.role) return '{} | {}'.format(self.user, self.org)

View File

@ -1,12 +1,8 @@
from django.db.models import F
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from users.models.user import User from .utils import get_current_org
from common.drf.serializers import BulkModelSerializer from .models import Organization
from common.db.models import concated_display as display
from .models import Organization, OrganizationMember, ROLE
class ResourceStatisticsSerializer(serializers.Serializer): class ResourceStatisticsSerializer(serializers.Serializer):
@ -25,11 +21,7 @@ class ResourceStatisticsSerializer(serializers.Serializer):
app_perms_amount = serializers.IntegerField(required=False) app_perms_amount = serializers.IntegerField(required=False)
class OrgSerializer(BulkModelSerializer): class OrgSerializer(ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False)
admins = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False)
auditors = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False)
resource_statistics = ResourceStatisticsSerializer(source='resource_statistics_cache', read_only=True) resource_statistics = ResourceStatisticsSerializer(source='resource_statistics_cache', read_only=True)
class Meta: class Meta:
@ -38,104 +30,26 @@ class OrgSerializer(BulkModelSerializer):
fields_small = fields_mini + [ fields_small = fields_mini + [
'resource_statistics', 'resource_statistics',
'is_default', 'is_root', 'is_default', 'is_root',
'date_created', 'date_created', 'created_by',
'comment', 'created_by', 'comment', 'created_by',
] ]
fields_m2m = ['users', 'admins', 'auditors'] fields_m2m = []
fields = fields_small + fields_m2m fields = fields_small + fields_m2m
read_only_fields = ['created_by', 'date_created'] read_only_fields = ['created_by', 'date_created']
def create(self, validated_data):
members = self._pop_members(validated_data)
instance = Organization.objects.create(**validated_data)
OrganizationMember.objects.add_users_by_role(instance, *members)
return instance
def _pop_members(self, validated_data):
return (
validated_data.pop('users', None),
validated_data.pop('admins', None),
validated_data.pop('auditors', None)
)
def update(self, instance, validated_data):
members = self._pop_members(validated_data)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
OrganizationMember.objects.set_users_by_role(instance, *members)
return instance
class OrgReadSerializer(OrgSerializer):
pass
class OrgMemberSerializer(BulkModelSerializer):
org_display = serializers.CharField(read_only=True)
user_display = serializers.CharField(read_only=True)
role_display = serializers.CharField(source='get_role_display', read_only=True)
class Meta:
model = OrganizationMember
fields_mini = ['id']
fields_small = fields_mini + [
'role', 'role_display'
]
fields_fk = ['org', 'user', 'org_display', 'user_display',]
fields = fields_small + fields_fk
use_model_bulk_create = True
model_bulk_create_kwargs = {'ignore_conflicts': True}
def get_unique_together_validators(self):
if self.parent:
return []
return super().get_unique_together_validators()
@classmethod
def setup_eager_loading(cls, queryset):
return queryset.annotate(
org_display=F('org__name'),
user_display=display('user__name', 'user__username')
).distinct()
class OrgMemberOldBaseSerializer(BulkModelSerializer):
organization = serializers.PrimaryKeyRelatedField(
label=_('Organization'), queryset=Organization.objects.all(), required=True, source='org'
)
def to_internal_value(self, data):
view = self.context['view']
org_id = view.kwargs.get('org_id')
if org_id:
data['organization'] = org_id
return super().to_internal_value(data)
class Meta:
model = OrganizationMember
fields = ('id', 'organization', 'user', 'role')
class OrgMemberAdminSerializer(OrgMemberOldBaseSerializer):
role = serializers.HiddenField(default=ROLE.ADMIN)
class OrgMemberUserSerializer(OrgMemberOldBaseSerializer):
role = serializers.HiddenField(default=ROLE.USER)
class OrgRetrieveSerializer(OrgReadSerializer):
admins = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
auditors = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
users = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta(OrgReadSerializer.Meta):
pass
class CurrentOrgSerializer(ModelSerializer): class CurrentOrgSerializer(ModelSerializer):
class Meta: class Meta:
model = Organization model = Organization
fields = ['id', 'name', 'is_default', 'is_root', 'comment'] fields = ['id', 'name', 'is_default', 'is_root', 'comment']
class CurrentOrgDefault:
requires_context = False
def __call__(self, *args):
return get_current_org()
def __repr__(self):
return '%s()' % self.__class__.__name__

View File

@ -1,15 +1,13 @@
from django.db.models.signals import m2m_changed
from django.db.models.signals import post_save, pre_delete, pre_save, post_delete from django.db.models.signals import post_save, pre_delete, pre_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from orgs.models import Organization, OrganizationMember from orgs.models import Organization
from assets.models import Node from assets.models import Node
from perms.models import (AssetPermission, ApplicationPermission) from perms.models import (AssetPermission, ApplicationPermission)
from users.models import UserGroup, User from users.models import UserGroup, User
from applications.models import Application from applications.models import Application
from terminal.models import Session from terminal.models import Session
from assets.models import Asset, AdminUser, SystemUser, Domain, Gateway from assets.models import Asset, SystemUser, Domain, Gateway
from common.const.signals import POST_PREFIX
from orgs.caches import OrgResourceStatisticsCache from orgs.caches import OrgResourceStatisticsCache
@ -32,20 +30,20 @@ def on_user_delete_refresh_cache(sender, instance, **kwargs):
refresh_user_amount_on_user_create_or_delete(instance.id) refresh_user_amount_on_user_create_or_delete(instance.id)
@receiver(m2m_changed, sender=OrganizationMember) # @receiver(m2m_changed, sender=OrganizationMember)
def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set, **kwargs): # def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set, **kwargs):
if not action.startswith(POST_PREFIX): # if not action.startswith(POST_PREFIX):
return # return
#
if reverse: # if reverse:
orgs = Organization.objects.filter(id__in=pk_set) # orgs = Organization.objects.filter(id__in=pk_set)
else: # else:
orgs = [instance] # orgs = [instance]
#
for org in orgs: # for org in orgs:
org_cache = OrgResourceStatisticsCache(org) # org_cache = OrgResourceStatisticsCache(org)
org_cache.expire('users_amount') # org_cache.expire('users_amount')
OrgResourceStatisticsCache(Organization.root()).expire('users_amount') # OrgResourceStatisticsCache(Organization.root()).expire('users_amount')
class OrgResourceStatisticsRefreshUtil: class OrgResourceStatisticsRefreshUtil:

View File

@ -10,7 +10,7 @@ from django.db.models.signals import m2m_changed
from django.db.models.signals import post_save, pre_delete from django.db.models.signals import post_save, pre_delete
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from orgs.models import Organization, OrganizationMember from orgs.models import Organization
from orgs.hands import set_current_org, Node, get_current_org from orgs.hands import set_current_org, Node, get_current_org
from perms.models import (AssetPermission, ApplicationPermission) from perms.models import (AssetPermission, ApplicationPermission)
from users.models import UserGroup, User from users.models import UserGroup, User
@ -20,6 +20,7 @@ from common.signals import django_ready
from common.utils import get_logger from common.utils import get_logger
from common.utils.connection import RedisPubSub from common.utils.connection import RedisPubSub
from assets.models import CommandFilterRule from assets.models import CommandFilterRule
from users.signals import post_user_leave_org
logger = get_logger(__file__) logger = get_logger(__file__)
@ -55,6 +56,7 @@ def subscribe_orgs_mapping_expire(sender, **kwargs):
t.start() t.start()
# 创建对应的root
@receiver(post_save, sender=Organization) @receiver(post_save, sender=Organization)
def on_org_create_or_update(sender, instance, created=False, **kwargs): def on_org_create_or_update(sender, instance, created=False, **kwargs):
# 必须放到最开始, 因为下面调用Node.save方法时会获取当前组织的org_id(即instance.org_id), 如果不过期会找不到 # 必须放到最开始, 因为下面调用Node.save方法时会获取当前组织的org_id(即instance.org_id), 如果不过期会找不到
@ -72,9 +74,6 @@ def on_org_create_or_update(sender, instance, created=False, **kwargs):
def on_org_delete(sender, instance, **kwargs): def on_org_delete(sender, instance, **kwargs):
expire_orgs_mapping_for_memory(instance.id) expire_orgs_mapping_for_memory(instance.id)
@receiver(pre_delete, sender=Organization)
def on_org_delete(sender, instance, **kwargs):
# 删除该组织下所有 节点 # 删除该组织下所有 节点
with tmp_to_org(instance): with tmp_to_org(instance):
root_node = Node.org_root() root_node = Node.org_root()
@ -144,25 +143,6 @@ def _clear_users_from_org(org, users):
_remove_users(CommandFilterRule, users, org, user_field_name='reviewers') _remove_users(CommandFilterRule, users, org, user_field_name='reviewers')
@receiver(m2m_changed, sender=OrganizationMember)
def on_org_user_changed(action, instance, reverse, pk_set, **kwargs):
if action == 'post_remove':
if reverse:
user = instance
org_pk_set = pk_set
orgs = Organization.objects.filter(id__in=org_pk_set)
for org in orgs:
if not org.members.filter(id=user.id).exists():
_clear_users_from_org(org, user)
else:
org = instance
user_pk_set = pk_set
leaved_users = set(pk_set) - set(org.members.filter(id__in=user_pk_set).values_list('id', flat=True))
_clear_users_from_org(org, leaved_users)
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
@on_transaction_commit @on_transaction_commit
def on_user_created_set_default_org(sender, instance, created, **kwargs): def on_user_created_set_default_org(sender, instance, created, **kwargs):
@ -171,3 +151,9 @@ def on_user_created_set_default_org(sender, instance, created, **kwargs):
if instance.orgs.count() > 0: if instance.orgs.count() > 0:
return return
Organization.default().members.add(instance) Organization.default().members.add(instance)
@receiver(post_user_leave_org)
def on_user_leave_org(sender, user=None, org=None, **kwargs):
logger.debug('User leave org signal recv: {} <> {}'.format(user, org))
_clear_users_from_org(org, [user])

View File

@ -11,13 +11,6 @@ app_name = 'orgs'
router = BulkRouter() router = BulkRouter()
router.register(r'orgs', api.OrgViewSet, 'org') router.register(r'orgs', api.OrgViewSet, 'org')
router.register(r'org-member-relation', api.OrgMemberRelationBulkViewSet, 'org-member-relation')
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
api.OrgMemberAdminRelationBulkViewSet, 'membership-admins')
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
api.OrgMemberUserRelationBulkViewSet, 'membership-users'),
urlpatterns = [ urlpatterns = [
path('orgs/current/', api.CurrentOrgDetailApi.as_view(), name='current-org-detail'), path('orgs/current/', api.CurrentOrgDetailApi.as_view(), name='current-org-detail'),

View File

@ -9,7 +9,6 @@ from applications.models import Application
from orgs.mixins.api import OrgRelationMixin from orgs.mixins.api import OrgRelationMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org from orgs.utils import current_org
from common.permissions import IsOrgAdmin
from perms import serializers from perms import serializers
from perms import models from perms import models
@ -36,7 +35,6 @@ class RelationMixin(OrgRelationMixin, OrgBulkModelViewSet):
class ApplicationPermissionUserRelationViewSet(RelationMixin): class ApplicationPermissionUserRelationViewSet(RelationMixin):
serializer_class = serializers.ApplicationPermissionUserRelationSerializer serializer_class = serializers.ApplicationPermissionUserRelationSerializer
m2m_field = models.ApplicationPermission.users.field m2m_field = models.ApplicationPermission.users.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', "user", "applicationpermission", 'id', "user", "applicationpermission",
] ]
@ -51,7 +49,6 @@ class ApplicationPermissionUserRelationViewSet(RelationMixin):
class ApplicationPermissionUserGroupRelationViewSet(RelationMixin): class ApplicationPermissionUserGroupRelationViewSet(RelationMixin):
serializer_class = serializers.ApplicationPermissionUserGroupRelationSerializer serializer_class = serializers.ApplicationPermissionUserGroupRelationSerializer
m2m_field = models.ApplicationPermission.user_groups.field m2m_field = models.ApplicationPermission.user_groups.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', "usergroup", "applicationpermission" 'id', "usergroup", "applicationpermission"
] ]
@ -66,7 +63,6 @@ class ApplicationPermissionUserGroupRelationViewSet(RelationMixin):
class ApplicationPermissionApplicationRelationViewSet(RelationMixin): class ApplicationPermissionApplicationRelationViewSet(RelationMixin):
serializer_class = serializers.ApplicationPermissionApplicationRelationSerializer serializer_class = serializers.ApplicationPermissionApplicationRelationSerializer
m2m_field = models.ApplicationPermission.applications.field m2m_field = models.ApplicationPermission.applications.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', 'application', 'applicationpermission', 'id', 'application', 'applicationpermission',
] ]
@ -81,7 +77,6 @@ class ApplicationPermissionApplicationRelationViewSet(RelationMixin):
class ApplicationPermissionSystemUserRelationViewSet(RelationMixin): class ApplicationPermissionSystemUserRelationViewSet(RelationMixin):
serializer_class = serializers.ApplicationPermissionSystemUserRelationSerializer serializer_class = serializers.ApplicationPermissionSystemUserRelationSerializer
m2m_field = models.ApplicationPermission.system_users.field m2m_field = models.ApplicationPermission.system_users.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', 'systemuser', 'applicationpermission', 'id', 'systemuser', 'applicationpermission',
] ]
@ -100,7 +95,6 @@ class ApplicationPermissionSystemUserRelationViewSet(RelationMixin):
class ApplicationPermissionAllApplicationListApi(generics.ListAPIView): class ApplicationPermissionAllApplicationListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.ApplicationPermissionAllApplicationSerializer serializer_class = serializers.ApplicationPermissionAllApplicationSerializer
only_fields = serializers.ApplicationPermissionAllApplicationSerializer.Meta.only_fields only_fields = serializers.ApplicationPermissionAllApplicationSerializer.Meta.only_fields
filterset_fields = ('name',) filterset_fields = ('name',)
@ -115,7 +109,6 @@ class ApplicationPermissionAllApplicationListApi(generics.ListAPIView):
class ApplicationPermissionAllUserListApi(generics.ListAPIView): class ApplicationPermissionAllUserListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.ApplicationPermissionAllUserSerializer serializer_class = serializers.ApplicationPermissionAllUserSerializer
only_fields = serializers.ApplicationPermissionAllUserSerializer.Meta.only_fields only_fields = serializers.ApplicationPermissionAllUserSerializer.Meta.only_fields
filterset_fields = ('username', 'name') filterset_fields = ('username', 'name')

View File

@ -4,7 +4,6 @@
from django.db.models import Q from django.db.models import Q
from rest_framework.generics import ListAPIView from rest_framework.generics import ListAPIView
from common.permissions import IsOrgAdminOrAppUser
from common.mixins.api import CommonApiMixin from common.mixins.api import CommonApiMixin
from applications.models import Application from applications.models import Application
from perms import serializers from perms import serializers
@ -18,11 +17,13 @@ class UserGroupGrantedApplicationsApi(CommonApiMixin, ListAPIView):
""" """
获取用户组直接授权的应用 获取用户组直接授权的应用
""" """
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AppGrantedSerializer serializer_class = serializers.AppGrantedSerializer
only_fields = serializers.AppGrantedSerializer.Meta.only_fields only_fields = serializers.AppGrantedSerializer.Meta.only_fields
filterset_fields = ['id', 'name', 'category', 'type', 'comment'] filterset_fields = ['id', 'name', 'category', 'type', 'comment']
search_fields = ['name', 'comment'] search_fields = ['name', 'comment']
rbac_perms = {
'list': 'perms.view_applicationpermission'
}
def get_queryset(self): def get_queryset(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')

View File

@ -16,8 +16,7 @@ from perms.utils.application.permission import (
get_application_system_user_ids, get_application_system_user_ids,
validate_permission, validate_permission,
) )
from perms.api.asset.user_permission.mixin import RoleAdminMixin, RoleUserMixin from .mixin import RoleAdminMixin, RoleUserMixin
from common.permissions import IsOrgAdminOrAppUser
from perms.hands import User, SystemUser from perms.hands import User, SystemUser
from perms import serializers from perms import serializers
@ -56,7 +55,9 @@ class MyGrantedApplicationSystemUsersApi(RoleUserMixin, GrantedApplicationSystem
@method_decorator(tmp_to_root_org(), name='get') @method_decorator(tmp_to_root_org(), name='get')
class ValidateUserApplicationPermissionApi(APIView): class ValidateUserApplicationPermissionApi(APIView):
permission_classes = (IsOrgAdminOrAppUser,) rbac_perms = {
'GET': 'view_applicationpermission'
}
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
user_id = request.query_params.get('user_id', '') user_id = request.query_params.get('user_id', '')

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
#
from common.mixins.api import RoleAdminMixin as _RoleAdminMixin
from common.mixins.api import RoleUserMixin as _RoleUserMixin
from orgs.utils import tmp_to_root_org
class RoleAdminMixin(_RoleAdminMixin):
rbac_perms = (
('list', 'perms.view_userapp'),
('retrieve', 'perms.view_userapps'),
('get_tree', 'perms.view_userapps'),
('GET', 'perms.view_userapps'),
)
class RoleUserMixin(_RoleUserMixin):
rbac_perms = (
('list', 'perms.view_myapps'),
('retrieve', 'perms.view_myapps'),
('get_tree', 'perms.view_myapps'),
('GET', 'perms.view_myapps'),
)
def get(self, request, *args, **kwargs):
with tmp_to_root_org():
return super().get(request, *args, **kwargs)

View File

@ -4,19 +4,15 @@ from perms.filters import AssetPermissionFilter
from perms.models import AssetPermission from perms.models import AssetPermission
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from perms import serializers from perms import serializers
from common.permissions import IsOrgAdmin
__all__ = [ __all__ = ['AssetPermissionViewSet']
'AssetPermissionViewSet',
]
class AssetPermissionViewSet(OrgBulkModelViewSet): class AssetPermissionViewSet(OrgBulkModelViewSet):
""" """
资产授权列表的增删改查api 资产授权列表的增删改查api
""" """
permission_classes = (IsOrgAdmin,)
model = AssetPermission model = AssetPermission
serializer_class = serializers.AssetPermissionSerializer serializer_class = serializers.AssetPermissionSerializer
filterset_class = AssetPermissionFilter filterset_class = AssetPermissionFilter

View File

@ -2,15 +2,12 @@
# #
from rest_framework import generics from rest_framework import generics
from django.db.models import F, Value from django.db.models import F, Value
from django.db.models import Q
from django.db.models.functions import Concat from django.db.models.functions import Concat
from django.shortcuts import get_object_or_404 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 OrgRelationMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org from orgs.utils import current_org
from common.permissions import IsOrgAdmin
from perms import serializers from perms import serializers
from perms import models from perms import models
from perms.utils.asset.user_permission import UserGrantedAssetsQueryUtils from perms.utils.asset.user_permission import UserGrantedAssetsQueryUtils
@ -36,7 +33,6 @@ class RelationMixin(OrgRelationMixin, OrgBulkModelViewSet):
class AssetPermissionUserRelationViewSet(RelationMixin): class AssetPermissionUserRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionUserRelationSerializer serializer_class = serializers.AssetPermissionUserRelationSerializer
m2m_field = models.AssetPermission.users.field m2m_field = models.AssetPermission.users.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', "user", "assetpermission", 'id', "user", "assetpermission",
] ]
@ -50,7 +46,6 @@ class AssetPermissionUserRelationViewSet(RelationMixin):
class AssetPermissionAllUserListApi(generics.ListAPIView): class AssetPermissionAllUserListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionAllUserSerializer serializer_class = serializers.AssetPermissionAllUserSerializer
filterset_fields = ("username", "name") filterset_fields = ("username", "name")
search_fields = filterset_fields search_fields = filterset_fields
@ -67,7 +62,6 @@ class AssetPermissionAllUserListApi(generics.ListAPIView):
class AssetPermissionUserGroupRelationViewSet(RelationMixin): class AssetPermissionUserGroupRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionUserGroupRelationSerializer serializer_class = serializers.AssetPermissionUserGroupRelationSerializer
m2m_field = models.AssetPermission.user_groups.field m2m_field = models.AssetPermission.user_groups.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', "usergroup", "assetpermission" 'id', "usergroup", "assetpermission"
] ]
@ -83,7 +77,6 @@ class AssetPermissionUserGroupRelationViewSet(RelationMixin):
class AssetPermissionAssetRelationViewSet(RelationMixin): class AssetPermissionAssetRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionAssetRelationSerializer serializer_class = serializers.AssetPermissionAssetRelationSerializer
m2m_field = models.AssetPermission.assets.field m2m_field = models.AssetPermission.assets.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', 'asset', 'assetpermission', 'id', 'asset', 'assetpermission',
] ]
@ -97,7 +90,6 @@ class AssetPermissionAssetRelationViewSet(RelationMixin):
class AssetPermissionAllAssetListApi(generics.ListAPIView): class AssetPermissionAllAssetListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionAllAssetSerializer serializer_class = serializers.AssetPermissionAllAssetSerializer
filterset_fields = ("hostname", "ip") filterset_fields = ("hostname", "ip")
search_fields = filterset_fields search_fields = filterset_fields
@ -112,7 +104,6 @@ class AssetPermissionAllAssetListApi(generics.ListAPIView):
class AssetPermissionNodeRelationViewSet(RelationMixin): class AssetPermissionNodeRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionNodeRelationSerializer serializer_class = serializers.AssetPermissionNodeRelationSerializer
m2m_field = models.AssetPermission.nodes.field m2m_field = models.AssetPermission.nodes.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', 'node', 'assetpermission', 'id', 'node', 'assetpermission',
] ]
@ -128,7 +119,6 @@ class AssetPermissionNodeRelationViewSet(RelationMixin):
class AssetPermissionSystemUserRelationViewSet(RelationMixin): class AssetPermissionSystemUserRelationViewSet(RelationMixin):
serializer_class = serializers.AssetPermissionSystemUserRelationSerializer serializer_class = serializers.AssetPermissionSystemUserRelationSerializer
m2m_field = models.AssetPermission.system_users.field m2m_field = models.AssetPermission.system_users.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [ filterset_fields = [
'id', 'systemuser', 'assetpermission', 'id', 'systemuser', 'assetpermission',
] ]

View File

@ -6,7 +6,6 @@ from django.db.models import Q
from rest_framework.generics import ListAPIView from rest_framework.generics import ListAPIView
from rest_framework.response import Response from rest_framework.response import Response
from common.permissions import IsOrgAdminOrAppUser
from common.utils import lazyproperty from common.utils import lazyproperty
from perms.models import AssetPermission from perms.models import AssetPermission
from assets.models import Asset, Node from assets.models import Asset, Node
@ -32,11 +31,13 @@ class UserGroupMixin:
class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedAssetsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
filterset_fields = ['hostname', 'ip', 'id', 'comment'] filterset_fields = ['hostname', 'ip', 'id', 'comment']
search_fields = ['hostname', 'ip', 'comment'] search_fields = ['hostname', 'ip', 'comment']
rbac_perms = {
'list': 'perms.view_userassets'
}
def get_queryset(self): def get_queryset(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')
@ -65,11 +66,13 @@ class UserGroupGrantedAssetsApi(ListAPIView):
class UserGroupGrantedNodeAssetsApi(ListAPIView): class UserGroupGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
filterset_fields = ['hostname', 'ip', 'id', 'comment'] filterset_fields = ['hostname', 'ip', 'id', 'comment']
search_fields = ['hostname', 'ip', 'comment'] search_fields = ['hostname', 'ip', 'comment']
rbac_perms = {
'list': 'perms.view_userassets'
}
def get_queryset(self): def get_queryset(self):
if getattr(self, 'swagger_fake_view', False): if getattr(self, 'swagger_fake_view', False):
@ -119,7 +122,9 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
class UserGroupGrantedNodesApi(ListAPIView): class UserGroupGrantedNodesApi(ListAPIView):
serializer_class = serializers.NodeGrantedSerializer serializer_class = serializers.NodeGrantedSerializer
permission_classes = (IsOrgAdminOrAppUser,) rbac_perms = {
'list': 'view_userassets'
}
def get_queryset(self): def get_queryset(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')
@ -131,7 +136,9 @@ class UserGroupGrantedNodesApi(ListAPIView):
class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIView): class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,) rbac_perms = {
'list': 'view_userassets'
}
def get_children_nodes(self, parent_key): def get_children_nodes(self, parent_key):
return Node.objects.filter(parent_key=parent_key) return Node.objects.filter(parent_key=parent_key)

View File

@ -13,7 +13,7 @@ from rest_framework.generics import (
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_by_user, validate_permission from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_by_user, validate_permission
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin, IsValidUser from common.permissions import IsValidUser
from common.utils import get_logger, lazyproperty from common.utils import get_logger, lazyproperty
from perms.hands import User, Asset, SystemUser from perms.hands import User, Asset, SystemUser
@ -33,8 +33,10 @@ __all__ = [
@method_decorator(tmp_to_root_org(), name='get') @method_decorator(tmp_to_root_org(), name='get')
class GetUserAssetPermissionActionsApi(RetrieveAPIView): class GetUserAssetPermissionActionsApi(RetrieveAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.ActionsSerializer serializer_class = serializers.ActionsSerializer
rbac_perms = {
'retrieve': 'perms.view_userassets'
}
def get_user(self): def get_user(self):
user_id = self.request.query_params.get('user_id', '') user_id = self.request.query_params.get('user_id', '')
@ -61,10 +63,9 @@ class GetUserAssetPermissionActionsApi(RetrieveAPIView):
@method_decorator(tmp_to_root_org(), name='get') @method_decorator(tmp_to_root_org(), name='get')
class ValidateUserAssetPermissionApi(APIView): class ValidateUserAssetPermissionApi(APIView):
permission_classes = (IsOrgAdminOrAppUser,) rbac_perms = {
'GET': 'perms.view_userassets'
def get_cache_policy(self): }
return 0
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
user_id = self.request.query_params.get('user_id', '') user_id = self.request.query_params.get('user_id', '')
@ -97,16 +98,16 @@ class ValidateUserAssetPermissionApi(APIView):
# TODO 删除 # TODO 删除
class RefreshAssetPermissionCacheApi(RetrieveAPIView): class RefreshAssetPermissionCacheApi(RetrieveAPIView):
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
return Response({'msg': True}, status=200) return Response({'msg': True}, status=200)
class UserGrantedAssetSystemUsersForAdminApi(ListAPIView): class UserGrantedAssetSystemUsersForAdminApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetSystemUserSerializer serializer_class = serializers.AssetSystemUserSerializer
only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields
rbac_perms = {
'list': 'perms.view_userassets'
}
@lazyproperty @lazyproperty
def user(self): def user(self):
@ -142,7 +143,5 @@ class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi):
# TODO 删除 # TODO 删除
class UserAssetPermissionsCacheApi(DestroyAPIView): class UserAssetPermissionsCacheApi(DestroyAPIView):
permission_classes = (IsOrgAdmin,)
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
return Response(status=204) return Response(status=204)

View File

@ -2,7 +2,6 @@
# #
from rest_framework.request import Request from rest_framework.request import Request
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
from common.http import is_true from common.http import is_true
from common.mixins.api import RoleAdminMixin as _RoleAdminMixin from common.mixins.api import RoleAdminMixin as _RoleAdminMixin
from common.mixins.api import RoleUserMixin as _RoleUserMixin from common.mixins.api import RoleUserMixin as _RoleUserMixin
@ -22,11 +21,21 @@ class PermBaseMixin:
class RoleAdminMixin(PermBaseMixin, _RoleAdminMixin): class RoleAdminMixin(PermBaseMixin, _RoleAdminMixin):
permission_classes = (IsOrgAdminOrAppUser,) rbac_perms = (
('list', 'perms.view_userassets'),
('retrieve', 'perms.view_userassets'),
('get_tree', 'perms.view_userassets'),
('GET', 'perms.view_userassets'),
)
class RoleUserMixin(PermBaseMixin, _RoleUserMixin): class RoleUserMixin(PermBaseMixin, _RoleUserMixin):
permission_classes = (IsValidUser,) rbac_perms = (
('list', 'perms.view_myassets'),
('retrieve', 'perms.view_myassets'),
('get_tree', 'perms.view_myassets'),
('GET', 'perms.view_myassets'),
)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
with tmp_to_root_org(): with tmp_to_root_org():

View File

@ -113,7 +113,9 @@ class UserGrantedNodeChildrenAsTreeForAdminApi(RoleAdminMixin, UserGrantedNodeCh
class MyGrantedNodeChildrenAsTreeApi(RoleUserMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenAsTreeApi): class MyGrantedNodeChildrenAsTreeApi(RoleUserMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenAsTreeApi):
pass def get_permissions(self):
permissions = super().get_permissions()
return permissions
class UserGrantedNodesForAdminApi(RoleAdminMixin, UserGrantedNodesMixin, BaseGrantedNodeApi): class UserGrantedNodesForAdminApi(RoleAdminMixin, UserGrantedNodesMixin, BaseGrantedNodeApi):

View File

@ -1,5 +1,4 @@
from django.db.models import Q from django.db.models import Q
from common.permissions import IsOrgAdmin
from common.utils import get_object_or_none from common.utils import get_object_or_none
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from assets.models import SystemUser from assets.models import SystemUser
@ -14,7 +13,6 @@ class BasePermissionViewSet(OrgBulkModelViewSet):
'user_id', 'username', 'system_user_id', 'system_user', 'user_id', 'username', 'system_user_id', 'system_user',
'user_group_id', 'user_group' 'user_group_id', 'user_group'
] ]
permission_classes = (IsOrgAdmin,)
def filter_valid(self, queryset): def filter_valid(self, queryset):
valid_query = self.request.query_params.get('is_valid', None) valid_query = self.request.query_params.get('is_valid', None)

View File

@ -1,10 +1,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class PermsConfig(AppConfig): class PermsConfig(AppConfig):
name = 'perms' name = 'perms'
verbose_name = _('Permissions')
def ready(self): def ready(self):
super().ready() super().ready()

View File

@ -0,0 +1,21 @@
# Generated by Django 3.1.13 on 2022-01-04 07:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('perms', '0020_auto_20210910_1103'),
]
operations = [
migrations.AlterModelOptions(
name='applicationpermission',
options={'ordering': ('name',), 'permissions': [('view_myapps', 'Can view my apps'), ('connect_myapps', 'Can connect my apps'), ('view_userapps', 'Can view user apps'), ('view_usergroupapps', 'Can view usergroup apps')], 'verbose_name': 'Application permission'},
),
migrations.AlterModelOptions(
name='assetpermission',
options={'ordering': ('name',), 'permissions': [('view_myassets', 'Can view my assets'), ('connect_myassets', 'Can connect my assets'), ('view_userassets', 'Can view user assets'), ('view_usergroupassets', 'Can view usergroup assets')], 'verbose_name': 'Asset permission'},
),
]

View File

@ -36,6 +36,12 @@ class ApplicationPermission(BasePermission):
unique_together = [('org_id', 'name')] unique_together = [('org_id', 'name')]
verbose_name = _('Application permission') verbose_name = _('Application permission')
ordering = ('name',) ordering = ('name',)
permissions = [
('view_myapps', _('Can view my apps')),
('connect_myapps', _('Can connect my apps')),
('view_userapps', _('Can view user apps')),
('view_usergroupapps', _('Can view usergroup apps')),
]
@property @property
def category_remote_app(self): def category_remote_app(self):

View File

@ -29,6 +29,12 @@ class AssetPermission(BasePermission):
unique_together = [('org_id', 'name')] unique_together = [('org_id', 'name')]
verbose_name = _("Asset permission") verbose_name = _("Asset permission")
ordering = ('name',) ordering = ('name',)
permissions = [
('view_myassets', _('Can view my assets')),
('connect_myassets', _('Can connect my assets')),
('view_userassets', _('Can view user assets')),
('view_usergroupassets', _('Can view usergroup assets')),
]
@lazyproperty @lazyproperty
def users_amount(self): def users_amount(self):

0
apps/rbac/__init__.py Normal file
View File

3
apps/rbac/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

Some files were not shown because too many files have changed in this diff Show More