fix: fix rbac to dev (#7636)

* feat: 添加 RBAC 应用模块

* feat: 添加 RBAC Model、API

* feat: 添加 RBAC Model、API 2

* feat: 添加 RBAC Model、API 3

* feat: 添加 RBAC Model、API 4

* feat: RBAC

* feat: RBAC

* feat: RBAC

* feat: RBAC

* feat: RBAC

* feat: RBAC 整理权限位

* feat: RBAC 整理权限位2

* feat: RBAC 整理权限位2

* feat: RBAC 整理权限位

* feat: RBAC 添加默认角色

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

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

* feat: RBAC 修改用户模块API

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

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

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

* feat: RBAC No.1

* xxx

* perf: 暂存

* perf: ...

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

* stash

* perf: 使用init

* perf: 修改migrations

* perf: rbac

* stash

* stash

* pref: 修改rbac

* stash it

* stash: 先去修复其他bug

* perf: 修改 role 添加 users

* pref: 修改 RBAC Model

* feat: 添加权限的 tree api

* stash: 暂存一下

* stash: 暂存一下

* perf: 修改 model verbose name

* feat: 添加model各种 verbose name

* perf: 生成 migrations

* perf: 优化权限位

* perf: 添加迁移脚本

* feat: 添加组织角色迁移

* perf: 添加迁移脚本

* stash

* perf: 添加migrateion

* perf: 暂存一下

* perf: 修改rbac

* perf: stash it

* fix: 迁移冲突

* fix: 迁移冲突

* perf: 暂存一下

* perf: 修改 rbac 逻辑

* stash: 暂存一下

* perf: 修改内置角色

* perf: 解决 root 组织的问题

* perf: stash it

* perf: 优化 rbac

* perf: 优化 rolebinding 处理

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

* perf: 暂存一下

* perf: 修改翻译

* perf: 去掉了 IsSuperUser

* perf: IsAppUser 去掉完成

* perf: 修改 connection token 的权限

* perf: 去掉导入的问题

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

* perf: 修改 permission

* perf: 去掉一些 org admin

* perf: 去掉部分 org admin

* perf: 再去掉点 org admin role

* perf: 再去掉部分 org admin

* perf: user 角色搜索

* perf: 去掉很多 js

* perf: 添加权限位

* perf: 修改权限

* perf: 去掉一个 todo

* merge: with dev

* fix: 修复冲突

Co-authored-by: Bai <bugatti_it@163.com>
Co-authored-by: Michael Bai <baijiangjie@gmail.com>
Co-authored-by: ibuler <ibuler@qq.com>
This commit is contained in:
fit2bot
2022-02-17 20:13:31 +08:00
committed by GitHub
parent b088362ae3
commit e259d2a9e9
263 changed files with 3049 additions and 62465 deletions

View File

@@ -6,24 +6,23 @@ from rest_framework.decorators import action
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from django.db.models import Prefetch
from common.permissions import (
IsOrgAdmin, IsOrgAdminOrAppUser,
CanUpdateDeleteUser, IsSuperUser
)
from common.mixins import CommonApiMixin
from common.utils import get_logger
from orgs.utils import current_org
from orgs.models import ROLE as ORG_ROLE, OrganizationMember
from rbac.models import Role, RoleBinding
from users.utils import LoginBlockUtil, MFABlockUtils
from .mixins import UserQuerysetMixin
from ..notifications import ResetMFAMsg
from .. import serializers
from ..serializers import UserSerializer, MiniUserSerializer, InviteSerializer
from ..serializers import (
UserSerializer,
MiniUserSerializer, InviteSerializer
)
from ..models import User
from ..signals import post_user_create
from ..filters import OrgRoleUserFilterBackend, UserFilter
from ..filters import UserFilter
logger = get_logger(__name__)
__all__ = [
@@ -35,92 +34,64 @@ __all__ = [
class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
filterset_class = UserFilter
search_fields = ('username', 'email', 'name', 'id', 'source', 'role')
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
serializer_classes = {
'default': UserSerializer,
'suggestion': MiniUserSerializer,
'invite': InviteSerializer,
}
extra_filter_backends = [OrgRoleUserFilterBackend]
ordering_fields = ('name',)
ordering = ('name', )
rbac_perms = {
'suggestion': 'users.match_user',
'invite': 'users.invite_user',
'remove': 'users.remove_user',
'bulk_remove': 'users.remove_user',
}
def get_queryset(self):
queryset = super().get_queryset().prefetch_related(
'groups'
)
if not current_org.is_root():
# 为在列表中计算用户在真实组织里的角色
queryset = queryset.prefetch_related(
Prefetch(
'm2m_org_members',
queryset=OrganizationMember.objects.filter(org__id=current_org.id)
)
)
queryset = super().get_queryset().prefetch_related('groups')
return queryset
def send_created_signal(self, users):
if not isinstance(users, list):
users = [users]
for user in users:
post_user_create.send(self.__class__, user=user)
@staticmethod
def set_users_to_org(users, org_roles, update=False):
# 只有真实存在的组织才真正关联用户
if not current_org or current_org.is_root():
return
for user, roles in zip(users, org_roles):
if update and roles is None:
continue
if not roles:
# 当前组织创建的用户,至少是该组织的`User`
roles = [ORG_ROLE.USER]
OrganizationMember.objects.set_user_roles(current_org, user, roles)
def set_users_roles_for_cache(queryset):
# Todo: 未来有机会用 SQL 实现
queryset_list = queryset
user_ids = [u.id for u in queryset_list]
role_bindings = RoleBinding.objects.filter(user__in=user_ids) \
.values('user_id', 'role_id', 'scope')
role_mapper = {r.id: r for r in Role.objects.all()}
user_org_role_mapper = defaultdict(set)
user_system_role_mapper = defaultdict(set)
for binding in role_bindings:
role_id = binding['role_id']
user_id = binding['user_id']
if binding['scope'] == RoleBinding.Scope.system:
user_system_role_mapper[user_id].add(role_mapper[role_id])
else:
user_org_role_mapper[user_id].add(role_mapper[role_id])
for u in queryset_list:
system_roles = user_system_role_mapper[u.id]
org_roles = user_org_role_mapper[u.id]
u.roles.cache_set(system_roles | org_roles)
u.org_roles.cache_set(org_roles)
u.system_roles.cache_set(system_roles)
return queryset_list
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset_list = self.set_users_roles_for_cache(queryset)
return queryset_list
def perform_create(self, serializer):
org_roles = self.get_serializer_org_roles(serializer)
# 创建用户
users = serializer.save()
if isinstance(users, User):
users = [users]
self.set_users_to_org(users, org_roles)
self.send_created_signal(users)
def get_permissions(self):
if self.action in ["retrieve", "list"]:
if self.request.query_params.get('all'):
self.permission_classes = (IsSuperUser,)
else:
self.permission_classes = (IsOrgAdminOrAppUser,)
elif self.action in ['destroy']:
self.permission_classes = (IsSuperUser,)
return super().get_permissions()
def perform_bulk_destroy(self, objects):
for obj in objects:
self.check_object_permissions(self.request, obj)
self.perform_destroy(obj)
@staticmethod
def get_serializer_org_roles(serializer):
validated_data = serializer.validated_data
# `org_roles` 先 `pop`
if isinstance(validated_data, list):
org_roles = [item.pop('org_roles', None) for item in validated_data]
else:
org_roles = [validated_data.pop('org_roles', None)]
return org_roles
def perform_update(self, serializer):
org_roles = self.get_serializer_org_roles(serializer)
users = serializer.save()
if isinstance(users, User):
users = [users]
self.set_users_to_org(users, org_roles, update=True)
def perform_bulk_update(self, serializer):
# TODO: 需要测试
user_ids = [
d.get("id") or d.get("pk") for d in serializer.validated_data
]
@@ -129,46 +100,42 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
self.check_object_permissions(self.request, user)
return super().perform_bulk_update(serializer)
@action(methods=['get'], detail=False, permission_classes=(IsOrgAdmin,))
def perform_bulk_destroy(self, objects):
for obj in objects:
self.check_object_permissions(self.request, obj)
self.perform_destroy(obj)
@action(methods=['get'], detail=False)
def suggestion(self, *args, **kwargs):
queryset = User.objects.exclude(role=User.ROLE.APP)
queryset = User.get_nature_users()
queryset = self.filter_queryset(queryset)[:6]
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(methods=['post'], detail=False, permission_classes=(IsOrgAdmin,))
@action(methods=['post'], detail=False)
def invite(self, request):
data = request.data
if not isinstance(data, list):
data = [request.data]
if not current_org or current_org.is_root():
error = {"error": "Not a valid org"}
return Response(error, status=400)
serializer_cls = self.get_serializer_class()
serializer = serializer_cls(data=data, many=True)
serializer = serializer_cls(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data
users_by_role = defaultdict(list)
for i in validated_data:
users_by_role[i['role']].append(i['user'])
OrganizationMember.objects.add_users_by_role(
current_org,
users=users_by_role[ORG_ROLE.USER],
admins=users_by_role[ORG_ROLE.ADMIN],
auditors=users_by_role[ORG_ROLE.AUDITOR]
)
users = validated_data['users']
org_roles = validated_data['org_roles']
for user in users:
user.org_roles.set(org_roles)
return Response(serializer.data, status=201)
@action(methods=['post'], detail=True, permission_classes=(IsOrgAdmin,))
@action(methods=['post'], detail=True)
def remove(self, request, *args, **kwargs):
instance = self.get_object()
instance.remove()
return Response(status=204)
@action(methods=['post'], detail=False, permission_classes=(IsOrgAdmin,), url_path='remove')
@action(methods=['post'], detail=False, url_path='remove')
def bulk_remove(self, request, *args, **kwargs):
qs = self.get_queryset()
filtered = self.filter_queryset(qs)
@@ -177,9 +144,14 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
instance.remove()
return Response(status=204)
def send_created_signal(self, users):
if not isinstance(users, list):
users = [users]
for user in users:
post_user_create.send(self.__class__, user=user)
class UserChangePasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.ChangeUserPasswordSerializer
def perform_update(self, serializer):
@@ -189,7 +161,6 @@ class UserChangePasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
class UserUnblockPKApi(UserQuerysetMixin, generics.UpdateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.UserSerializer
def perform_update(self, serializer):
@@ -200,7 +171,6 @@ class UserUnblockPKApi(UserQuerysetMixin, generics.UpdateAPIView):
class UserResetMFAApi(UserQuerysetMixin, generics.RetrieveAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.ResetOTPSerializer
def retrieve(self, request, *args, **kwargs):