Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update role-based data permissions #465

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion backend/app/admin/api/v1/sys/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

from fastapi import APIRouter, Depends, Path, Query, Request

from backend.app.admin.schema.role import CreateRoleParam, GetRoleListDetails, UpdateRoleMenuParam, UpdateRoleParam
from backend.app.admin.schema.role import (
CreateRoleParam,
GetRoleListDetails,
UpdateRoleDataScopeParam,
UpdateRoleDeptParam,
UpdateRoleMenuParam,
UpdateRoleParam,
)
from backend.app.admin.service.menu_service import menu_service
from backend.app.admin.service.role_service import role_service
from backend.common.pagination import DependsPagination, paging_data
Expand Down Expand Up @@ -109,6 +116,38 @@ async def update_role_menus(
return response_base.fail()


@router.put(
'/{pk}/data-scope',
summary='更新角色数据范围',
dependencies=[
Depends(RequestPermission('sys:role:data_scope:edit')),
DependsRBAC,
],
)
async def update_role_data_scope(request: Request, pk: Annotated[int, Path(...)], data_scope: UpdateRoleDataScopeParam):
count = await role_service.update_role_data_scope(request=request, pk=pk, data_scope=data_scope)
if count > 0:
return response_base.success()
return response_base.fail()


@router.put(
'/{pk}/dept',
summary='更新角色部门',
dependencies=[
Depends(RequestPermission('sys:role:dept:edit')),
DependsRBAC,
],
)
async def update_role_depts(
request: Request, pk: Annotated[int, Path(...)], dept_ids: UpdateRoleDeptParam
) -> ResponseModel:
count = await role_service.update_role_dept(request=request, pk=pk, dept_ids=dept_ids)
if count > 0:
return response_base.success()
return response_base.fail()


@router.delete(
'',
summary='(批量)删除角色',
Expand Down
37 changes: 35 additions & 2 deletions backend/app/admin/crud/crud_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
from typing import Sequence

from sqlalchemy import Select, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from sqlalchemy_crud_plus import CRUDPlus

from backend.app.admin.model import Menu, Role, User
from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam
from backend.app.admin.model import Dept, Menu, Role, User
from backend.app.admin.schema.role import (
CreateRoleParam,
UpdateRoleDataScopeParam,
UpdateRoleDeptParam,
UpdateRoleMenuParam,
UpdateRoleParam,
)


class CRUDRole(CRUDPlus[Role]):
Expand Down Expand Up @@ -122,6 +129,32 @@ async def update_menus(self, db, role_id: int, menu_ids: UpdateRoleMenuParam) ->
current_role.menus = menus.scalars().all()
return len(current_role.menus)

async def update_data_scope(self, db: AsyncSession, role_id: int, data_scope: UpdateRoleDataScopeParam):
"""
更新用户数据范围

:param db:
:param role_id:
:param data_scope:
:return:
"""
return await self.update_model(db, role_id, data_scope)

async def update_depts(self, db, role_id: int, dept_ids: UpdateRoleDeptParam) -> int:
"""
更新角色部门

:param db:
:param role_id:
:param dept_ids:
:return:
"""
stmt = select(Dept).where(Dept.id.in_(dept_ids.depts))
depts = await db.execute(stmt)
current_role = await self.get_with_relation(db, role_id)
current_role.depts = depts.scalars().all()
return len(current_role.depts)

async def delete(self, db, role_id: list[int]) -> int:
"""
删除角色
Expand Down
14 changes: 8 additions & 6 deletions backend/app/admin/crud/crud_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ async def get_list(self, dept: int = None, username: str = None, phone: str = No
"""
stmt = (
select(self.model)
.options(selectinload(self.model.dept))
.options(selectinload(self.model.roles).selectinload(Role.menus))
.options(
selectinload(self.model.dept),
selectinload(self.model.roles).selectinload(Role.menus),
)
.order_by(desc(self.model.join_time))
)
where_list = []
Expand Down Expand Up @@ -298,10 +300,10 @@ async def get_with_relation(self, db: AsyncSession, *, user_id: int = None, user
:param username:
:return:
"""
stmt = (
select(self.model)
.options(selectinload(self.model.dept))
.options(selectinload(self.model.roles).joinedload(Role.menus))
stmt = select(self.model).options(
selectinload(self.model.dept),
selectinload(self.model.roles).joinedload(Role.menus),
selectinload(self.model.roles).joinedload(Role.depts),
)
filters = []
if user_id:
Expand Down
29 changes: 29 additions & 0 deletions backend/app/admin/model/m2m.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import INT, Column, ForeignKey, Integer, Table

from backend.common.model import MappedBase

sys_user_role = Table(
'sys_user_role',
MappedBase.metadata,
Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column('user_id', Integer, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'),
Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'),
)

sys_role_menu = Table(
'sys_role_menu',
MappedBase.metadata,
Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'),
Column('menu_id', Integer, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'),
)

sys_role_dept = Table(
'sys_role_dept',
MappedBase.metadata,
Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'),
Column('dept_id', Integer, ForeignKey('sys_dept.id', ondelete='CASCADE'), primary_key=True, comment='部门ID'),
)
6 changes: 6 additions & 0 deletions backend/app/admin/model/sys_dept.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship

from backend.app.admin.model.m2m import sys_role_dept
from backend.common.model import Base, id_key


Expand All @@ -22,11 +23,16 @@ class Dept(Base):
email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱')
status: Mapped[int] = mapped_column(default=1, comment='部门状态(0停用 1正常)')
del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)')

# 父级部门一对多
parent_id: Mapped[int | None] = mapped_column(
ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, index=True, comment='父部门ID'
)
parent: Mapped[Union['Dept', None]] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[list['Dept'] | None] = relationship(init=False, back_populates='parent')

# 部门用户一对多
users: Mapped[list['User']] = relationship(init=False, back_populates='dept') # noqa: F821

# 部门角色多对多
roles: Mapped[list['Role']] = relationship(init=False, secondary=sys_role_dept, back_populates='depts') # noqa: F821
5 changes: 4 additions & 1 deletion backend/app/admin/model/sys_dict_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class DictData(Base):
sort: Mapped[int] = mapped_column(default=0, comment='排序')
status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)')
remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注')

# 字典类型一对多
type_id: Mapped[int] = mapped_column(ForeignKey('sys_dict_type.id'), default=0, comment='字典类型关联ID')
type_id: Mapped[int] = mapped_column(
ForeignKey('sys_dict_type.id', ondelete='CASCADE'), default=0, comment='字典类型关联ID'
)
type: Mapped['DictType'] = relationship(init=False, back_populates='datas') # noqa: F821
1 change: 1 addition & 0 deletions backend/app/admin/model/sys_dict_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ class DictType(Base):
code: Mapped[str] = mapped_column(String(32), unique=True, comment='字典类型编码')
status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)')
remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注')

# 字典类型一对多
datas: Mapped[list['DictData']] = relationship(init=False, back_populates='type') # noqa: F821
8 changes: 4 additions & 4 deletions backend/app/admin/model/sys_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship

from backend.app.admin.model.sys_role_menu import sys_role_menu
from backend.app.admin.model.m2m import sys_role_menu
from backend.common.model import Base, id_key


Expand All @@ -29,13 +29,13 @@ class Menu(Base):
show: Mapped[int] = mapped_column(default=1, comment='是否显示(0否 1是)')
cache: Mapped[int] = mapped_column(default=1, comment='是否缓存(0否 1是)')
remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注')

# 父级菜单一对多
parent_id: Mapped[int | None] = mapped_column(
ForeignKey('sys_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID'
)
parent: Mapped[Union['Menu', None]] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[list['Menu'] | None] = relationship(init=False, back_populates='parent')

# 菜单角色多对多
roles: Mapped[list['Role']] = relationship( # noqa: F821
init=False, secondary=sys_role_menu, back_populates='menus'
)
roles: Mapped[list['Role']] = relationship(init=False, secondary=sys_role_menu, back_populates='menus') # noqa: F821
21 changes: 12 additions & 9 deletions backend/app/admin/model/sys_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship

from backend.app.admin.model.sys_role_menu import sys_role_menu
from backend.app.admin.model.sys_user_role import sys_user_role
from backend.app.admin.model.m2m import sys_role_dept, sys_role_menu, sys_user_role
from backend.common.model import Base, id_key


Expand All @@ -16,14 +15,18 @@ class Role(Base):

id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称')
data_scope: Mapped[int | None] = mapped_column(default=2, comment='权限范围(1:全部数据权限 2:自定义数据权限)')
data_scope: Mapped[int | None] = mapped_column(
default=0,
comment='数据权限范围(0: 全部数据,1: 自定义数据,2: 所在部门及以下数据,3: 所在部门数据,4: 仅本人数据)',
)
status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)')
remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注')

# 角色用户多对多
users: Mapped[list['User']] = relationship( # noqa: F821
init=False, secondary=sys_user_role, back_populates='roles'
)
users: Mapped[list['User']] = relationship(init=False, secondary=sys_user_role, back_populates='roles') # noqa: F821

# 角色菜单多对多
menus: Mapped[list['Menu']] = relationship( # noqa: F821
init=False, secondary=sys_role_menu, back_populates='roles'
)
menus: Mapped[list['Menu']] = relationship(init=False, secondary=sys_role_menu, back_populates='roles') # noqa: F821

# 角色部门多对多
depts: Mapped[list['Dept']] = relationship(init=False, secondary=sys_role_dept, back_populates='roles') # noqa: F821
13 changes: 0 additions & 13 deletions backend/app/admin/model/sys_role_menu.py

This file was deleted.

13 changes: 7 additions & 6 deletions backend/app/admin/model/sys_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sqlalchemy import VARBINARY, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship

from backend.app.admin.model.sys_user_role import sys_user_role
from backend.app.admin.model.m2m import sys_user_role
from backend.common.model import Base, id_key
from backend.database.db_mysql import uuid4_str
from backend.utils.timezone import timezone
Expand All @@ -32,14 +32,15 @@ class User(Base):
phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机号')
join_time: Mapped[datetime] = mapped_column(init=False, default_factory=timezone.now, comment='注册时间')
last_login_time: Mapped[datetime | None] = mapped_column(init=False, onupdate=timezone.now, comment='上次登录')

# 部门用户一对多
dept_id: Mapped[int | None] = mapped_column(
ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, comment='部门关联ID'
)
dept: Mapped[Union['Dept', None]] = relationship(init=False, back_populates='users') # noqa: F821
# 用户角色多对多
roles: Mapped[list['Role']] = relationship( # noqa: F821
init=False, secondary=sys_user_role, back_populates='users'
)
# 用户 OAuth2 一对多

# 用户社交信息一对多
socials: Mapped[list['UserSocial']] = relationship(init=False, back_populates='user') # noqa: F821

# 用户角色多对多
roles: Mapped[list['Role']] = relationship(init=False, secondary=sys_user_role, back_populates='users') # noqa: F821
13 changes: 0 additions & 13 deletions backend/app/admin/model/sys_user_role.py

This file was deleted.

3 changes: 2 additions & 1 deletion backend/app/admin/model/sys_user_social.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class UserSocial(Base):
union_id: Mapped[str | None] = mapped_column(String(20), default=None, comment='第三方用户的 union id')
scope: Mapped[str | None] = mapped_column(String(120), default=None, comment='第三方用户授予的权限')
code: Mapped[str | None] = mapped_column(String(50), default=None, comment='用户的授权 code')
# 用户 OAuth2 一对多

# 用户社交信息一对多
user_id: Mapped[int | None] = mapped_column(
ForeignKey('sys_user.id', ondelete='SET NULL'), default=None, comment='用户关联ID'
)
Expand Down
17 changes: 14 additions & 3 deletions backend/app/admin/schema/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@

from pydantic import ConfigDict, Field

from backend.app.admin.schema.dept import GetDeptListDetails
from backend.app.admin.schema.menu import GetMenuListDetails
from backend.common.enums import RoleDataScopeType, StatusType
from backend.common.schema import SchemaBase


class RoleSchemaBase(SchemaBase):
name: str
data_scope: RoleDataScopeType = Field(
default=RoleDataScopeType.custom, description='权限范围(1:全部数据权限 2:自定义数据权限)'
)
status: StatusType = Field(default=StatusType.enable)
remark: str | None = None

Expand All @@ -30,10 +28,23 @@ class UpdateRoleMenuParam(SchemaBase):
menus: list[int]


class UpdateRoleDataScopeParam(SchemaBase):
data_scope: RoleDataScopeType = Field(
default=RoleDataScopeType.all,
description='数据权限范围(0: 全部数据,1: 自定义数据,2: 所在部门及以下数据,3: 所在部门数据,4: 仅本人数据)',
)


class UpdateRoleDeptParam(SchemaBase):
depts: list[int]


class GetRoleListDetails(RoleSchemaBase):
model_config = ConfigDict(from_attributes=True)

id: int
data_scope: RoleDataScopeType
created_time: datetime
updated_time: datetime | None = None
menus: list[GetMenuListDetails]
depts: list[GetDeptListDetails] | None = None
Loading