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>
pull/7638/head
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 ..models import LoginACL
from .. import serializers
from ..filters import LoginAclFilter
__all__ = ['LoginACLViewSet', ]
__all__ = ['LoginACLViewSet']
class LoginACLViewSet(JMSBulkModelViewSet):
queryset = LoginACL.objects.all()
filterset_class = LoginAclFilter
search_fields = ('name',)
permission_classes = (IsOrgAdmin,)
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 common.permissions import IsOrgAdmin
from .. import models, serializers
@ -10,5 +9,4 @@ class LoginAssetACLViewSet(OrgBulkModelViewSet):
model = models.LoginAssetACL
filterset_fields = ('name', )
search_fields = filterset_fields
permission_classes = (IsOrgAdmin, )
serializer_class = serializers.LoginAssetACLSerializer

View File

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

View File

@ -1,5 +1,7 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class AclsConfig(AppConfig):
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.api import JMSBulkModelViewSet
from rbac.permissions import RBACPermission
from ..models import Account
from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify
from ..hands import NeedMFAVerify
from .. import serializers
@ -31,7 +32,8 @@ class AccountFilterSet(BaseFilterSet):
username = self.get_query_param('username')
if not username:
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
@ -41,7 +43,6 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet):
filterset_class = AccountFilterSet
filterset_fields = ['username', 'app_display', 'type', 'category', 'app']
serializer_class = serializers.AppAccountSerializer
permission_classes = (IsOrgAdmin,)
def get_queryset(self):
queryset = Account.get_queryset()
@ -50,5 +51,9 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet):
class ApplicationAccountSecretViewSet(ApplicationAccountViewSet):
serializer_class = serializers.AppAccountSecretSerializer
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
permission_classes = [RBACPermission, NeedMFAVerify]
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 common.tree import TreeNodeSerializer
from common.mixins.api import SuggestionMixin
from ..hands import IsOrgAdminOrAppUser
from common.mixins.views import SuggestionMixin
from .. import serializers
from ..models import Application
@ -22,12 +21,14 @@ class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet):
'type': ['exact', 'in'],
}
search_fields = ('name', 'type', 'category')
permission_classes = (IsOrgAdminOrAppUser,)
serializer_classes = {
'default': serializers.AppSerializer,
'get_tree': TreeNodeSerializer,
'suggestion': serializers.MiniAppSerializer
}
rbac_perms = {
'get_tree': 'applications.view_application'
}
@action(methods=['GET'], detail=False, url_path='tree')
def get_tree(self, request, *args, **kwargs):

View File

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

View File

@ -1,7 +1,9 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.apps import AppConfig
class ApplicationsConfig(AppConfig):
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

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']
class Meta:
verbose_name = _('Account')
verbose_name = _('Application account')
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):
super().__init__(*args, **kwargs)

View File

@ -219,6 +219,7 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
verbose_name = _('Application')
unique_together = [('org_id', 'name')]
ordering = ('name',)
verbose_name = _("Application")
def __str__(self):
category_display = self.get_category_display()
@ -265,3 +266,4 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
class ApplicationUser(SystemUser):
class Meta:
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 orgs.mixins.api import OrgBulkModelViewSet
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, NeedMFAVerify
from common.drf.filters import BaseFilterSet
from ..tasks.account_connectivity import test_accounts_connectivity_manual
from ..models import AuthBook, Node
@ -62,7 +61,6 @@ class AccountViewSet(OrgBulkModelViewSet):
'default': serializers.AccountSerializer,
'verify_account': serializers.AssetTaskSerializer
}
permission_classes = (IsOrgAdmin,)
def get_queryset(self):
queryset = AuthBook.get_queryset()
@ -82,17 +80,22 @@ class AccountSecretsViewSet(AccountViewSet):
serializer_classes = {
'default': serializers.AccountSecretSerializer
}
permission_classes = (IsOrgAdmin, NeedMFAVerify)
http_method_names = ['get']
rbac_perms = {
'list': 'assets.view_assetsecret',
'retrieve': 'assets.view_assetsecret',
}
class AccountTaskCreateAPI(CreateAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AccountTaskSerializer
filterset_fields = AccountViewSet.filterset_fields
search_fields = AccountViewSet.search_fields
filterset_class = AccountViewSet.filterset_class
def check_permissions(self, request):
return request.user.has_perm('assets.test_assetconnectivity')
def get_accounts(self):
queryset = AuthBook.objects.all()
queryset = self.filter_queryset(queryset)
@ -109,5 +112,4 @@ class AccountTaskCreateAPI(CreateAPIView):
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=400)
return handler

View File

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

View File

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

View File

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

View File

@ -9,7 +9,6 @@ from common.utils import reverse
from common.utils import lazyproperty
from orgs.mixins.api import OrgBulkModelViewSet
from tickets.api import GenericTicketStatusRetrieveCloseAPI
from ..hands import IsOrgAdmin, IsAppUser
from ..models import CommandFilter, CommandFilterRule
from .. import serializers
@ -23,7 +22,6 @@ class CommandFilterViewSet(OrgBulkModelViewSet):
model = CommandFilter
filterset_fields = ("name",)
search_fields = filterset_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterSerializer
@ -31,7 +29,6 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet):
model = CommandFilterRule
filterset_fields = ('content',)
search_fields = filterset_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterRuleSerializer
def get_queryset(self):
@ -43,8 +40,10 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet):
class CommandConfirmAPI(CreateAPIView):
permission_classes = (IsAppUser,)
serializer_class = serializers.CommandConfirmSerializer
rbac_perms = {
'create': 'tickets.add_ticket'
}
def create(self, request, *args, **kwargs):
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 common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins.api import OrgBulkModelViewSet
from ..models import Domain, Gateway
from .. import serializers
@ -20,7 +19,6 @@ class DomainViewSet(OrgBulkModelViewSet):
model = Domain
filterset_fields = ("name", )
search_fields = filterset_fields
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.DomainSerializer
ordering_fields = ('name',)
ordering = ('name', )
@ -35,13 +33,14 @@ class GatewayViewSet(OrgBulkModelViewSet):
model = Gateway
filterset_fields = ("domain__name", "name", "username", "ip", "domain")
search_fields = ("domain__name", "name", "username", "ip")
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GatewaySerializer
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsOrgAdmin,)
object = None
rbac_perms = {
'POST': 'assets.change_gateway'
}
def post(self, request, *args, **kwargs):
self.object = self.get_object(Gateway.objects.all())

View File

@ -3,7 +3,6 @@
from orgs.mixins.api import OrgModelViewSet
from assets.models import GatheredUser
from common.permissions import IsOrgAdmin
from ..serializers import GatheredUserSerializer
from ..filters import AssetRelatedByNodeFilterBackend
@ -15,7 +14,6 @@ __all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet):
model = GatheredUser
serializer_class = GatheredUserSerializer
permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
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 orgs.mixins.api import OrgBulkModelViewSet
from ..hands import IsOrgAdmin
from ..models import Label
from .. import serializers
@ -30,7 +29,6 @@ class LabelViewSet(OrgBulkModelViewSet):
model = Label
filterset_fields = ("name", "value")
search_fields = filterset_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LabelSerializer
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 import generics
from orgs.utils import current_org
from ..hands import IsOrgAdmin
from ..models import Node
from ..tasks import (
update_node_assets_hardware_info_manual,
@ -46,7 +45,6 @@ class NodeViewSet(SuggestionMixin, OrgBulkModelViewSet):
model = Node
filterset_fields = ('value', 'key', 'id')
search_fields = ('value', )
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
@action(methods=[POST], detail=False, url_path='check_assets_amount_task')
@ -85,7 +83,6 @@ class NodeListAsTreeApi(generics.ListAPIView):
]
"""
model = Node
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer
@staticmethod
@ -100,7 +97,6 @@ class NodeListAsTreeApi(generics.ListAPIView):
class NodeChildrenApi(generics.ListCreateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
instance = None
is_initial = False
@ -199,7 +195,6 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer
def get_queryset(self):
@ -214,7 +209,6 @@ class NodeAssetsApi(generics.ListAPIView):
class NodeAddChildrenApi(generics.UpdateAPIView):
model = Node
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer
instance = None
@ -231,7 +225,6 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
class NodeAddAssetsApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
@ -243,7 +236,6 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
class NodeRemoveAssetsApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
@ -262,7 +254,6 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
class MoveAssetsToNodeApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
@ -305,8 +296,6 @@ class MoveAssetsToNodeApi(generics.UpdateAPIView):
class NodeTaskCreateApi(generics.CreateAPIView):
model = Node
serializer_class = serializers.NodeTaskSerializer
permission_classes = (IsOrgAdmin,)
def get_object(self):
node_id = self.kwargs.get('pk')
node = get_object_or_none(self.model, id=node_id)

View File

@ -1,16 +1,15 @@
# ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404
from django.middleware import csrf
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.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 import generics
from common.mixins.api import SuggestionMixin
from orgs.utils import tmp_to_root_org
from rest_framework.decorators import action
from ..models import SystemUser, CommandFilterRule
from .. import serializers
from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer
@ -46,7 +45,6 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet):
}
ordering_fields = ('name', 'protocol', 'login_mode')
ordering = ('name', )
permission_classes = (IsOrgAdminOrAppUser,)
@action(methods=['get'], detail=False, url_path='su-from')
def su_from(self, request, *args, **kwargs):
@ -80,8 +78,13 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
Get system user auth info
"""
model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
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):
instance = self.get_object()
@ -123,7 +126,6 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
Get system user with asset auth info
"""
model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer
def get_object(self):
@ -140,8 +142,10 @@ class SystemUserAppAuthInfoApi(generics.RetrieveAPIView):
Get system user with asset auth info
"""
model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer
rbac_perms = {
'retrieve': 'assets.view_systemusersecret',
}
def get_object(self):
instance = super().get_object()
@ -153,7 +157,6 @@ class SystemUserAppAuthInfoApi(generics.RetrieveAPIView):
class SystemUserTaskApi(generics.CreateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.SystemUserTaskSerializer
def do_push(self, system_user, asset_ids=None):
@ -175,6 +178,18 @@ class SystemUserTaskApi(generics.CreateAPIView):
pk = self.kwargs.get('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):
action = serializer.validated_data["action"]
asset = serializer.validated_data.get('asset')
@ -198,7 +213,9 @@ class SystemUserTaskApi(generics.CreateAPIView):
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
rbac_perms = {
'list': 'assets.view_commandfilterule'
}
def get_serializer_class(self):
from ..serializers import CommandFilterRuleSerializer
@ -224,10 +241,12 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
class SystemUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer
filterset_fields = ("hostname", "ip")
search_fields = filterset_fields
rbac_perms = {
'list': 'assets.view_asset'
}
def get_object(self):
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.functions import Concat
from common.permissions import IsOrgAdmin
from common.utils import get_logger
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
@ -71,7 +70,6 @@ class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet):
class SystemUserAssetRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserAssetRelationSerializer
model = models.SystemUser.assets.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'asset', 'systemuser',
]
@ -97,7 +95,6 @@ class SystemUserAssetRelationViewSet(BaseRelationViewSet):
class SystemUserNodeRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserNodeRelationSerializer
model = models.SystemUser.nodes.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'node', 'systemuser',
]
@ -118,7 +115,6 @@ class SystemUserNodeRelationViewSet(BaseRelationViewSet):
class SystemUserUserRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserUserRelationSerializer
model = models.SystemUser.users.through
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'user', 'systemuser',
]

View File

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

View File

@ -11,5 +11,4 @@
"""
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
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')]
verbose_name = _("Asset")
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:
verbose_name = _('AuthBook')
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):
super().__init__(*args, **kwargs)

View File

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

View File

@ -325,7 +325,7 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
verbose_name = _("System user")
# Todo: 准备废弃
# Deprecated: 准备废弃
class AdminUser(BaseUser):
"""
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.functions import Concat
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin
from common.drf.filters import DatetimeRangeFilter
from common.api import CommonGenericViewSet
from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin
@ -20,7 +19,6 @@ class FTPLogViewSet(CreateModelMixin,
OrgGenericViewSet):
model = FTPLog
serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,)
extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [
('date_start', ('date_from', 'date_to'))
@ -32,7 +30,6 @@ class FTPLogViewSet(CreateModelMixin,
class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet):
queryset = UserLoginLog.objects.all()
permission_classes = [IsOrgAdmin | IsOrgAuditor]
serializer_class = UserLoginLogSerializer
extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [
@ -58,7 +55,6 @@ class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet):
class OperateLogViewSet(ListModelMixin, OrgGenericViewSet):
model = OperateLog
serializer_class = OperateLogSerializer
permission_classes = [IsOrgAdmin | IsOrgAuditor]
extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [
('datetime', ('date_from', 'date_to'))
@ -70,7 +66,6 @@ class OperateLogViewSet(ListModelMixin, OrgGenericViewSet):
class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet):
queryset = PasswordChangeLog.objects.all()
permission_classes = [IsOrgAdmin | IsOrgAuditor]
serializer_class = PasswordChangeLogSerializer
extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [
@ -91,7 +86,6 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet):
class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
model = CommandExecution
serializer_class = CommandExecutionSerializer
permission_classes = [IsOrgAdmin | IsOrgAuditor]
extra_filter_backends = [DatetimeRangeFilter]
date_range_filter_fields = [
('date_start', ('date_from', 'date_to'))
@ -117,7 +111,6 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
serializer_class = CommandExecutionHostsRelationSerializer
m2m_field = CommandExecution.hosts.field
permission_classes = [IsOrgAdmin | IsOrgAuditor]
filterset_fields = [
'id', 'asset', 'commandexecution'
]

View File

@ -1,10 +1,12 @@
from django.apps import AppConfig
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import post_save
class AuditsConfig(AppConfig):
name = 'audits'
verbose_name = _('Audits')
def ready(self):
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"))
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
class Meta:
verbose_name = _("File transfer log")
class OperateLog(OrgModelMixin):
ACTION_CREATE = 'create'
@ -73,6 +76,9 @@ class OperateLog(OrgModelMixin):
self.org_id = Organization.ROOT_ID
return super(OperateLog, self).save(*args, **kwargs)
class Meta:
verbose_name = _("Operate log")
class PasswordChangeLog(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
@ -84,6 +90,9 @@ class PasswordChangeLog(models.Model):
def __str__(self):
return "{} change {}'s password".format(self.change_by, self.user)
class Meta:
verbose_name = _('Password change log')
class UserLoginLog(models.Model):
LOGIN_TYPE_CHOICE = (
@ -155,3 +164,4 @@ class UserLoginLog(models.Model):
class Meta:
ordering = ['-datetime', 'username']
verbose_name = _('User login log')

View File

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

View File

@ -24,12 +24,10 @@ from applications.models import Application
from authentication.signals import post_auth_failed
from common.utils import get_logger, random_string
from common.mixins.api import SerializerMixin
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
from common.utils.common import get_file_by_arch
from orgs.mixins.api import RootOrgViewMixin
from common.http import is_true
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.asset.permission import get_asset_actions
@ -42,6 +40,14 @@ __all__ = ['UserConnectionTokenViewSet']
class ClientProtocolMixin:
"""
下载客户端支持的连接文件里面包含了 token 其他连接信息
- [x] RDP
- [ ] KoKo
本质上这里还是暴露出 token 进行使用
"""
request: Request
get_serializer: Callable
create_token: Callable
@ -167,7 +173,7 @@ class ClientProtocolMixin:
rst = rst.decode('ascii')
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):
if self.request.method == 'GET':
data = self.request.query_params
@ -214,7 +220,7 @@ class ClientProtocolMixin:
}
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):
serializer = self.get_valid_serializer()
try:
@ -271,8 +277,14 @@ class SecretDetailMixin:
'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):
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', '')
try:
value, user, system_user, asset, app, expired_at, actions = self.valid_token(token)
@ -307,12 +319,18 @@ class UserConnectionTokenViewSet(
RootOrgViewMixin, SerializerMixin, ClientProtocolMixin,
SecretDetailMixin, GenericViewSet
):
permission_classes = (IsSuperUserOrAppUser,)
serializer_classes = {
'default': ConnectionTokenSerializer,
'get_secret_detail': ConnectionTokenSecretSerializer,
}
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
def check_resource_permission(user, asset, application, system_user):
@ -403,14 +421,6 @@ class UserConnectionTokenViewSet(
raise serializers.ValidationError('Permission expired or invalid')
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):
token = request.query_params.get('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.models import User
from common.utils import get_logger
from common.permissions import IsOrgAdmin
from common.mixins.api import RoleUserMixin, RoleAdminMixin
from authentication import errors
@ -32,4 +31,4 @@ class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase):
class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase):
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.models import User
from common.utils import get_logger
from common.permissions import IsOrgAdmin
from common.mixins.api import RoleUserMixin, RoleAdminMixin
from authentication import errors
@ -32,7 +31,6 @@ class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase):
class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase):
user_id_url_kwarg = 'user_id'
permission_classes = (IsOrgAdmin,)
class FeiShuEventSubscriptionCallback(APIView):

View File

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

View File

@ -39,14 +39,6 @@ class MFASendCodeApi(AuthMixin, CreateAPIView):
username = ''
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):
"""避免暴力测试用户名"""
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.drf.api import JMSGenericViewSet
from common.drf.serializers import EmptySerializer
from common.permissions import IsSuperUser
from common.permissions import OnlySuperUser
from common.utils import reverse
from users.models import User
from ..serializers import SSOTokenSerializer
@ -32,9 +32,8 @@ class SSOViewSet(AuthMixin, JMSGenericViewSet):
'login_url': SSOTokenSerializer,
'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):
if not settings.AUTH_SSO:
raise SSOAuthClosed()

View File

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

View File

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

View File

@ -29,6 +29,9 @@ def get_request_date_header(request):
class JMSModelBackend(ModelBackend):
def has_perm(self, user_obj, perm, obj=None):
return False
def user_can_authenticate(self, user):
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):
return str(self.id)
class Meta:
verbose_name = _("Access key")
class PrivateToken(Token):
"""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'))
expired = models.BooleanField(default=False, verbose_name=_('Expired'))
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 rest_framework import permissions
from rest_framework.decorators import action
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):
@ -23,3 +26,17 @@ class PermissionsMixin(UserPassesTestMixin):
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 common.exceptions import MFAVerifyRequired
from orgs.utils import current_org
from common.utils import is_uuid
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
"""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
class IsAppUser(IsValidUser):
"""Allows access only to app user """
class OnlySuperUser(IsValidUser):
def has_permission(self, request, view):
return super(IsAppUser, self).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) \
return super().has_permission(request, view) \
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):
def has_permission(self, request, view):
authorization = request.META.get('HTTP_AUTHORIZATION', '')
@ -100,21 +29,6 @@ class WithBootstrapToken(permissions.BasePermission):
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):
def has_permission(self, request, view):
if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
@ -126,80 +40,7 @@ class NeedMFAVerify(permissions.BasePermission):
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):
def has_object_permission(self, request, view, obj):
return (super().has_object_permission(request, view, obj) and
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 = ""
parentInfo = ''
meta = {}
checked = False
_tree = None
@ -101,4 +102,7 @@ class TreeNodeSerializer(serializers.Serializer):
open = serializers.BooleanField(default=False)
iconSkin = serializers.CharField(max_length=128, allow_blank=True)
nocheck = serializers.BooleanField(default=False)
checked = serializers.BooleanField(default=False)
halfCheck = serializers.BooleanField(default=False)
chkDisabled = serializers.BooleanField(default=False)
meta = serializers.JSONField()

View File

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

View File

@ -139,6 +139,7 @@ OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS
AUTH_BACKEND_MODEL = 'authentication.backends.api.JMSModelBackend'
RBAC_BACKEND = 'rbac.backends.RBACBackend'
AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend'
AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend'
AUTH_BACKEND_OIDC_PASSWORD = 'jms_oidc_rp.backends.OIDCAuthPasswordBackend'
@ -154,7 +155,7 @@ AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend'
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_SSO,
]

View File

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

View File

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

View File

@ -24,6 +24,7 @@ api_v1 = [
path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')),
path('acls/', include('acls.urls.api_urls', namespace='api-acls')),
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()),
]

View File

@ -3,7 +3,7 @@ from rest_framework.views import APIView
from rest_framework.response import Response
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.models import SystemMsgSubscription, UserMsgSubscription
from notifications.backends import BACKEND
@ -25,7 +25,7 @@ class BackendListView(APIView):
'name': backend,
'name_display': backend.label
}
for backend in BACKEND
for backend in BACKEND.choices
if backend.is_enable
]
return Response(data=data)
@ -41,6 +41,9 @@ class SystemMsgSubscriptionViewSet(ListModelMixin,
'update': SystemMsgSubscriptionSerializer,
'partial_update': SystemMsgSubscriptionSerializer
}
rbac_perms = {
}
def list(self, request, *args, **kwargs):
data = []
@ -80,9 +83,13 @@ class UserMsgSubscriptionViewSet(ListModelMixin,
UpdateModelMixin,
JMSGenericViewSet):
lookup_field = 'user_id'
queryset = UserMsgSubscription.objects.all()
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):

View File

@ -1,8 +1,10 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class NotificationsConfig(AppConfig):
name = 'notifications'
verbose_name = _('Notifications')
def ready(self):
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.views import Response
from common.drf.api import JMSBulkModelViewSet
from common.permissions import IsOrgAdmin
from common.drf.serializers import CeleryTaskSerializer
from ..models import Task, AdHoc, AdHocExecution
from ..serializers import (
@ -18,7 +16,6 @@ from ..serializers import (
)
from ..tasks import run_ansible_task
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
__all__ = [
'TaskViewSet', 'TaskRun', 'AdHocViewSet', 'AdHocRunHistoryViewSet'
@ -30,7 +27,6 @@ class TaskViewSet(OrgBulkModelViewSet):
filterset_fields = ("name",)
search_fields = filterset_fields
serializer_class = TaskSerializer
permission_classes = (IsOrgAdmin,)
def get_serializer_class(self):
if self.action == 'retrieve':
@ -46,7 +42,6 @@ class TaskViewSet(OrgBulkModelViewSet):
class TaskRun(generics.RetrieveAPIView):
queryset = Task.objects.all()
serializer_class = CeleryTaskSerializer
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
task = self.get_object()
@ -57,7 +52,6 @@ class TaskRun(generics.RetrieveAPIView):
class AdHocViewSet(viewsets.ModelViewSet):
queryset = AdHoc.objects.all()
serializer_class = AdHocSerializer
permission_classes = (IsOrgAdmin,)
def get_serializer_class(self):
if self.action == 'retrieve':
@ -75,7 +69,6 @@ class AdHocViewSet(viewsets.ModelViewSet):
class AdHocRunHistoryViewSet(viewsets.ModelViewSet):
queryset = AdHocExecution.objects.all()
serializer_class = AdHocExecutionSerializer
permission_classes = (IsOrgAdmin,)
def get_queryset(self):
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 django_celery_beat.models import PeriodicTask
from common.permissions import IsValidUser, IsSuperUser
from common.permissions import IsValidUser
from common.api import LogTailApi
from ..models import CeleryTask
from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer
@ -88,7 +88,6 @@ class CeleryResultApi(generics.RetrieveAPIView):
class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):
queryset = PeriodicTask.objects.all()
serializer_class = CeleryPeriodTaskSerializer
permission_classes = (IsSuperUser,)
http_method_names = ('get', 'head', 'options', 'patch')
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"))
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"))
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')
total_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'
unique_together = ('name', 'org_id')
ordering = ('-date_updated',)
verbose_name = _("Task")
get_latest_by = 'date_created'
permissions = [
('view_taskmonitor', _('Can view task monitor'))
]
class AdHoc(OrgModelMixin):
@ -235,6 +240,7 @@ class AdHoc(OrgModelMixin):
class Meta:
db_table = "ops_adhoc"
get_latest_by = 'date_created'
verbose_name = _('AdHoc')
class AdHocExecution(OrgModelMixin):
@ -330,3 +336,4 @@ class AdHocExecution(OrgModelMixin):
class Meta:
db_table = "ops_adhoc_execution"
get_latest_by = 'date_start'
verbose_name = _("AdHoc execution")

View File

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

View File

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

View File

@ -32,7 +32,11 @@ class ServerPerformanceMessage(SystemMessage):
@classmethod
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.receive_backends = [BACKEND.EMAIL]
subscription.save()

View File

@ -3,15 +3,18 @@
from django.views.generic import TemplateView
from django.conf import settings
from common.permissions import IsOrgAdmin, IsOrgAuditor
from common.mixins.views import PermissionsMixin
from rbac.permissions import RBACPermission
__all__ = ['CeleryTaskLogView']
class CeleryTaskLogView(PermissionsMixin, TemplateView):
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):
context = super().get_context_data(**kwargs)

View File

@ -2,20 +2,14 @@
#
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.generics import RetrieveAPIView
from rest_framework.exceptions import PermissionDenied
from common.permissions import IsSuperUserOrAppUser, IsValidUser, UserCanAnyPermCurrentOrg
from common.drf.api import JMSBulkRelationModelViewSet
from .models import Organization, ROLE
from common.permissions import IsValidUser
from .models import Organization
from .serializers import (
OrgSerializer, OrgReadSerializer,
OrgRetrieveSerializer, OrgMemberSerializer,
OrgMemberAdminSerializer, OrgMemberUserSerializer,
CurrentOrgSerializer
OrgSerializer, CurrentOrgSerializer
)
from users.models import User, UserGroup
from assets.models import (
@ -26,8 +20,6 @@ from applications.models import Application
from perms.models import AssetPermission, ApplicationPermission
from orgs.utils import current_org, tmp_to_root_org
from common.utils import get_logger
from .filters import OrgMemberRelationFilterSet
from .models import OrganizationMember
logger = get_logger(__file__)
@ -47,23 +39,20 @@ class OrgViewSet(BulkModelViewSet):
search_fields = ('name', 'comment')
queryset = Organization.objects.all()
serializer_class = OrgSerializer
permission_classes = (IsSuperUserOrAppUser,)
ordering_fields = ('name',)
ordering = ('name', )
def get_serializer_class(self):
mapper = {
'list': OrgReadSerializer,
'retrieve': OrgRetrieveSerializer
'list': OrgSerializer,
'retrieve': OrgSerializer
}
return mapper.get(self.action, super().get_serializer_class())
@tmp_to_root_org()
def get_data_from_model(self, org, model):
if model == User:
data = model.objects.filter(
orgs__id=org.id, m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR]
)
data = model.get_org_users(org=org)
elif model == Node:
# 根节点不能手动删除,所以排除检查
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)
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):
serializer_class = CurrentOrgSerializer
permission_classes = (IsValidUser, UserCanAnyPermCurrentOrg)
permission_classes = (IsValidUser,)
def get_object(self):
return current_org

View File

@ -1,8 +1,10 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class OrgsConfig(AppConfig):
name = 'orgs'
verbose_name = _('Organizations')
def ready(self):
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.utils import get_logger
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 applications.models import Application
from perms.models import AssetPermission, ApplicationPermission
from .models import OrganizationMember
logger = get_logger(__file__)
@ -84,13 +83,8 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
return SystemUser.objects.filter(type=SystemUser.Type.common).count()
def compute_users_amount(self):
users = User.objects.exclude(role='App')
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
amount = User.get_org_users(self.org).count()
return amount
def compute_assets_amount(self):
if self.org.is_root():

View File

@ -1,17 +1,12 @@
# -*- coding: utf-8 -*-
#
from .utils import current_org, get_org_from_request
from .models import Organization
from .utils import get_org_from_request
def org_processor(request):
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),
# 'HAS_ORG_PERM': current_org.can_admin_by(request.user),
}
return context

View File

@ -1,16 +1,6 @@
from django_filters.rest_framework import filterset
from django_filters.rest_framework import filters
from .models import OrganizationMember
class UUIDInFilter(filters.BaseInFilter, filters.UUIDFilter):
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 rbac.models import RoleBinding
class OrgMiddleware:
@ -14,22 +15,19 @@ class OrgMiddleware:
return
if not request.user.is_authenticated:
return
if request.user.is_common_user:
return
org = get_org_from_request(request)
if org.can_admin_by(request.user):
return
if org.can_audit_by(request.user):
return
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)
search_org = None if org.is_root() else org
has_roles = RoleBinding.objects.filter(user=request.user, org=search_org).exists()
if has_roles:
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):
self.set_permed_org_if_need(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
from functools import partial
from itertools import chain
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 common.utils import lazyproperty, settings
from common.const import choices
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):
@ -25,7 +13,7 @@ class Organization(models.Model):
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'))
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_NAME = _('GLOBAL')
@ -35,6 +23,9 @@ class Organization(models.Model):
class Meta:
verbose_name = _("Organization")
permissions = (
('view_rootorg', _('Can view root org')),
)
def __str__(self):
return str(self.name)
@ -79,113 +70,9 @@ class Organization(models.Model):
def expire_orgs_mapping(cls):
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):
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
def default(cls):
defaults = dict(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME)
@ -209,13 +96,18 @@ class Organization(models.Model):
@lazyproperty
def resource_statistics_cache(self):
# Todo: 由于 redis 问题,没能获取到
return {}
from .caches import OrgResourceStatisticsCache
return OrgResourceStatisticsCache(self)
def get_members(self):
return self.members.all().distinct()
def get_total_resources_amount(self):
from django.apps import apps
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():
models_cls = app_config.get_models()
for model in models_cls:
@ -249,178 +141,24 @@ class Organization(models.Model):
return node
def _convert_to_uuid_set(users):
rst = set()
for user in users:
if isinstance(user, models.Model):
rst.add(user.id)
elif not isinstance(user, uuid.UUID):
rst.add(uuid.UUID(user))
return rst
def _none2list(*args):
return ([] if v is None else v for v in args)
def _users2pks_if_need(users, admins, auditors):
pks = []
for user in chain(users, admins, auditors):
if hasattr(user, 'pk'):
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 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")
class OrganizationMember(models.Model):
@ -431,16 +169,15 @@ class OrganizationMember(models.Model):
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'))
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_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
objects = OrgMemberManager()
# objects = OrgMemberManager()
class Meta:
unique_together = [('org', 'user', 'role')]
db_table = 'orgs_organization_members'
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 import serializers
from django.utils.translation import ugettext_lazy as _
from users.models.user import User
from common.drf.serializers import BulkModelSerializer
from common.db.models import concated_display as display
from .models import Organization, OrganizationMember, ROLE
from .utils import get_current_org
from .models import Organization
class ResourceStatisticsSerializer(serializers.Serializer):
@ -25,11 +21,7 @@ class ResourceStatisticsSerializer(serializers.Serializer):
app_perms_amount = serializers.IntegerField(required=False)
class OrgSerializer(BulkModelSerializer):
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)
class OrgSerializer(ModelSerializer):
resource_statistics = ResourceStatisticsSerializer(source='resource_statistics_cache', read_only=True)
class Meta:
@ -38,104 +30,26 @@ class OrgSerializer(BulkModelSerializer):
fields_small = fields_mini + [
'resource_statistics',
'is_default', 'is_root',
'date_created',
'date_created', 'created_by',
'comment', 'created_by',
]
fields_m2m = ['users', 'admins', 'auditors']
fields_m2m = []
fields = fields_small + fields_m2m
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 Meta:
model = Organization
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.dispatch import receiver
from orgs.models import Organization, OrganizationMember
from orgs.models import Organization
from assets.models import Node
from perms.models import (AssetPermission, ApplicationPermission)
from users.models import UserGroup, User
from applications.models import Application
from terminal.models import Session
from assets.models import Asset, AdminUser, SystemUser, Domain, Gateway
from common.const.signals import POST_PREFIX
from assets.models import Asset, SystemUser, Domain, Gateway
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)
@receiver(m2m_changed, sender=OrganizationMember)
def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set, **kwargs):
if not action.startswith(POST_PREFIX):
return
if reverse:
orgs = Organization.objects.filter(id__in=pk_set)
else:
orgs = [instance]
for org in orgs:
org_cache = OrgResourceStatisticsCache(org)
org_cache.expire('users_amount')
OrgResourceStatisticsCache(Organization.root()).expire('users_amount')
# @receiver(m2m_changed, sender=OrganizationMember)
# def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set, **kwargs):
# if not action.startswith(POST_PREFIX):
# return
#
# if reverse:
# orgs = Organization.objects.filter(id__in=pk_set)
# else:
# orgs = [instance]
#
# for org in orgs:
# org_cache = OrgResourceStatisticsCache(org)
# org_cache.expire('users_amount')
# OrgResourceStatisticsCache(Organization.root()).expire('users_amount')
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 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 perms.models import (AssetPermission, ApplicationPermission)
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.connection import RedisPubSub
from assets.models import CommandFilterRule
from users.signals import post_user_leave_org
logger = get_logger(__file__)
@ -55,6 +56,7 @@ def subscribe_orgs_mapping_expire(sender, **kwargs):
t.start()
# 创建对应的root
@receiver(post_save, sender=Organization)
def on_org_create_or_update(sender, instance, created=False, **kwargs):
# 必须放到最开始, 因为下面调用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):
expire_orgs_mapping_for_memory(instance.id)
@receiver(pre_delete, sender=Organization)
def on_org_delete(sender, instance, **kwargs):
# 删除该组织下所有 节点
with tmp_to_org(instance):
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')
@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)
@on_transaction_commit
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:
return
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.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 = [
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 OrgBulkModelViewSet
from orgs.utils import current_org
from common.permissions import IsOrgAdmin
from perms import serializers
from perms import models
@ -36,7 +35,6 @@ class RelationMixin(OrgRelationMixin, OrgBulkModelViewSet):
class ApplicationPermissionUserRelationViewSet(RelationMixin):
serializer_class = serializers.ApplicationPermissionUserRelationSerializer
m2m_field = models.ApplicationPermission.users.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', "user", "applicationpermission",
]
@ -51,7 +49,6 @@ class ApplicationPermissionUserRelationViewSet(RelationMixin):
class ApplicationPermissionUserGroupRelationViewSet(RelationMixin):
serializer_class = serializers.ApplicationPermissionUserGroupRelationSerializer
m2m_field = models.ApplicationPermission.user_groups.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', "usergroup", "applicationpermission"
]
@ -66,7 +63,6 @@ class ApplicationPermissionUserGroupRelationViewSet(RelationMixin):
class ApplicationPermissionApplicationRelationViewSet(RelationMixin):
serializer_class = serializers.ApplicationPermissionApplicationRelationSerializer
m2m_field = models.ApplicationPermission.applications.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'application', 'applicationpermission',
]
@ -81,7 +77,6 @@ class ApplicationPermissionApplicationRelationViewSet(RelationMixin):
class ApplicationPermissionSystemUserRelationViewSet(RelationMixin):
serializer_class = serializers.ApplicationPermissionSystemUserRelationSerializer
m2m_field = models.ApplicationPermission.system_users.field
permission_classes = (IsOrgAdmin,)
filterset_fields = [
'id', 'systemuser', 'applicationpermission',
]
@ -100,7 +95,6 @@ class ApplicationPermissionSystemUserRelationViewSet(RelationMixin):
class ApplicationPermissionAllApplicationListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.ApplicationPermissionAllApplicationSerializer
only_fields = serializers.ApplicationPermissionAllApplicationSerializer.Meta.only_fields
filterset_fields = ('name',)
@ -115,7 +109,6 @@ class ApplicationPermissionAllApplicationListApi(generics.ListAPIView):
class ApplicationPermissionAllUserListApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.ApplicationPermissionAllUserSerializer
only_fields = serializers.ApplicationPermissionAllUserSerializer.Meta.only_fields
filterset_fields = ('username', 'name')

View File

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

View File

@ -16,8 +16,7 @@ from perms.utils.application.permission import (
get_application_system_user_ids,
validate_permission,
)
from perms.api.asset.user_permission.mixin import RoleAdminMixin, RoleUserMixin
from common.permissions import IsOrgAdminOrAppUser
from .mixin import RoleAdminMixin, RoleUserMixin
from perms.hands import User, SystemUser
from perms import serializers
@ -56,7 +55,9 @@ class MyGrantedApplicationSystemUsersApi(RoleUserMixin, GrantedApplicationSystem
@method_decorator(tmp_to_root_org(), name='get')
class ValidateUserApplicationPermissionApi(APIView):
permission_classes = (IsOrgAdminOrAppUser,)
rbac_perms = {
'GET': 'view_applicationpermission'
}
def get(self, request, *args, **kwargs):
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 orgs.mixins.api import OrgBulkModelViewSet
from perms import serializers
from common.permissions import IsOrgAdmin
__all__ = [
'AssetPermissionViewSet',
]
__all__ = ['AssetPermissionViewSet']
class AssetPermissionViewSet(OrgBulkModelViewSet):
"""
资产授权列表的增删改查api
"""
permission_classes = (IsOrgAdmin,)
model = AssetPermission
serializer_class = serializers.AssetPermissionSerializer
filterset_class = AssetPermissionFilter

View File

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

View File

@ -6,7 +6,6 @@ from django.db.models import Q
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from common.permissions import IsOrgAdminOrAppUser
from common.utils import lazyproperty
from perms.models import AssetPermission
from assets.models import Asset, Node
@ -32,11 +31,13 @@ class UserGroupMixin:
class UserGroupGrantedAssetsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
filterset_fields = ['hostname', 'ip', 'id', 'comment']
search_fields = ['hostname', 'ip', 'comment']
rbac_perms = {
'list': 'perms.view_userassets'
}
def get_queryset(self):
user_group_id = self.kwargs.get('pk', '')
@ -65,11 +66,13 @@ class UserGroupGrantedAssetsApi(ListAPIView):
class UserGroupGrantedNodeAssetsApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
filterset_fields = ['hostname', 'ip', 'id', 'comment']
search_fields = ['hostname', 'ip', 'comment']
rbac_perms = {
'list': 'perms.view_userassets'
}
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
@ -119,7 +122,9 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
class UserGroupGrantedNodesApi(ListAPIView):
serializer_class = serializers.NodeGrantedSerializer
permission_classes = (IsOrgAdminOrAppUser,)
rbac_perms = {
'list': 'view_userassets'
}
def get_queryset(self):
user_group_id = self.kwargs.get('pk', '')
@ -131,7 +136,9 @@ class UserGroupGrantedNodesApi(ListAPIView):
class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
rbac_perms = {
'list': 'view_userassets'
}
def get_children_nodes(self, 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 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 perms.hands import User, Asset, SystemUser
@ -33,8 +33,10 @@ __all__ = [
@method_decorator(tmp_to_root_org(), name='get')
class GetUserAssetPermissionActionsApi(RetrieveAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.ActionsSerializer
rbac_perms = {
'retrieve': 'perms.view_userassets'
}
def get_user(self):
user_id = self.request.query_params.get('user_id', '')
@ -61,10 +63,9 @@ class GetUserAssetPermissionActionsApi(RetrieveAPIView):
@method_decorator(tmp_to_root_org(), name='get')
class ValidateUserAssetPermissionApi(APIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get_cache_policy(self):
return 0
rbac_perms = {
'GET': 'perms.view_userassets'
}
def get(self, request, *args, **kwargs):
user_id = self.request.query_params.get('user_id', '')
@ -97,16 +98,16 @@ class ValidateUserAssetPermissionApi(APIView):
# TODO 删除
class RefreshAssetPermissionCacheApi(RetrieveAPIView):
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
return Response({'msg': True}, status=200)
class UserGrantedAssetSystemUsersForAdminApi(ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetSystemUserSerializer
only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields
rbac_perms = {
'list': 'perms.view_userassets'
}
@lazyproperty
def user(self):
@ -142,7 +143,5 @@ class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi):
# TODO 删除
class UserAssetPermissionsCacheApi(DestroyAPIView):
permission_classes = (IsOrgAdmin,)
def destroy(self, request, *args, **kwargs):
return Response(status=204)

View File

@ -2,7 +2,6 @@
#
from rest_framework.request import Request
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
from common.http import is_true
from common.mixins.api import RoleAdminMixin as _RoleAdminMixin
from common.mixins.api import RoleUserMixin as _RoleUserMixin
@ -22,11 +21,21 @@ class PermBaseMixin:
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):
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):
with tmp_to_root_org():

View File

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

View File

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

View File

@ -1,10 +1,12 @@
from __future__ import unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class PermsConfig(AppConfig):
name = 'perms'
verbose_name = _('Permissions')
def ready(self):
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')]
verbose_name = _('Application permission')
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
def category_remote_app(self):

View File

@ -29,6 +29,12 @@ class AssetPermission(BasePermission):
unique_together = [('org_id', 'name')]
verbose_name = _("Asset permission")
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
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