fix: scope user organization labels to the current tenant.

This commit is contained in:
zlwu committed 2026-10-11 12:37:32 +08:00
1 parent 02d1e16036
commit 81bda0cb79
3 files changed
+221 -7

No files matched your search

+10 -4
View File
@@ -156,13 +156,19 @@ def get_user(db: Session, user_id: str) -> Optional[SysUser]:
)
def list_post_ids_for_user(db: Session, user_id: str) -> List[str]:
def list_post_ids_for_user(
db: Session, user_id: str, tenant_no: Optional[str] = None
) -> List[str]:
"""用户当前有效职位 ID 列表。"""
query = db.query(SysUserPost).filter(
and_(SysUserPost.user_id == user_id, SysUserPost.del_flag == 0)
)
tenant = (tenant_no or "").strip()
if tenant:
query = query.filter(SysUserPost.tenant_no == tenant)
return [
r.post_id
for r in db.query(SysUserPost)
.filter(and_(SysUserPost.user_id == user_id, SysUserPost.del_flag == 0))
.all()
for r in query.all()
]
+15 -3
View File
@@ -32,23 +32,35 @@ def _avatar_url_field(user: SysUser) -> Optional[str]:
def resolve_user_org_labels(db: Session, user: SysUser) -> Tuple[Optional[str], List[str]]:
"""解析用户所属部门名称与职位名称列表(保序)。"""
tenant_no = (getattr(user, "tenant_no", None) or "").strip()
dept_name: Optional[str] = None
dept_id = (getattr(user, "dept_id", None) or "").strip()
if dept_id:
dept = (
db.query(SysDept)
.filter(SysDept.id == dept_id, SysDept.del_flag == 0)
.filter(
SysDept.id == dept_id,
SysDept.tenant_no == tenant_no,
SysDept.del_flag == 0,
)
.first()
)
if dept and (dept.dept_name or "").strip():
dept_name = str(dept.dept_name).strip()
post_names: List[str] = []
post_ids = list_post_ids_for_user(db, user.id)
post_ids = list_post_ids_for_user(
db, user.id, tenant_no=tenant_no
)
if post_ids:
posts = (
db.query(SysPost)
.filter(SysPost.id.in_(post_ids), SysPost.del_flag == 0)
.filter(
SysPost.id.in_(post_ids),
SysPost.tenant_no == tenant_no,
SysPost.status == "1",
SysPost.del_flag == 0,
)
.all()
)
name_map = {
@@ -584,3 +584,199 @@ def test_public_auth_entrypoints_pass_the_users_tenant_to_role_queries(
"names": [("u1", "tenant-b")] * 2,
"menus": [("u1", "tenant-b")],
}
def test_org_labels_are_tenant_scoped_in_auth_profile_and_token_payloads(
monkeypatch,
):
from app.api import public
from app.core.config import settings
from app.crud.user import list_post_ids_for_user
from app.schemas.auth import ProfileUpdateRequest
from app.services import issue_user_tokens
db = _session()
_add_auth_user(db)
_add_role_menu(
db,
tenant_no="tenant-b",
role_id="tenant-b-role",
menu_id="tenant-b-menu",
perm_code="mini:market:specialist:presign",
role_name="市场专员",
data_scope="4",
)
user = db.query(SysUser).filter_by(id="u1").one()
user.dept_id = "default-dept"
db.add_all(
[
SysUserRole(
id="tenant-b-role-link",
user_id="u1",
role_id="tenant-b-role",
tenant_no="tenant-b",
create_dept_id="",
del_flag=0,
),
SysDept(
id="default-dept",
dept_name="默认租户部门",
tenant_no=settings.DEFAULT_TENANT_NO,
create_dept_id="",
del_flag=0,
),
SysDept(
id="tenant-b-dept",
dept_name="租户B部门",
tenant_no="tenant-b",
create_dept_id="",
del_flag=0,
),
SysDept(
id="tenant-b-deleted-dept",
dept_name="租户B已删除部门",
tenant_no="tenant-b",
create_dept_id="",
del_flag=1,
),
SysPost(
id="default-post",
post_name="默认租户职位",
post_code="default-post",
status="1",
tenant_no=settings.DEFAULT_TENANT_NO,
create_dept_id="",
del_flag=0,
),
SysPost(
id="tenant-b-post",
post_name="租户B有效职位",
post_code="tenant-b-post",
status="1",
tenant_no="tenant-b",
create_dept_id="",
del_flag=0,
),
SysPost(
id="tenant-b-disabled-post",
post_name="租户B停用职位",
post_code="tenant-b-disabled-post",
status="0",
tenant_no="tenant-b",
create_dept_id="",
del_flag=0,
),
SysPost(
id="tenant-b-deleted-post",
post_name="租户B已删除职位",
post_code="tenant-b-deleted-post",
status="1",
tenant_no="tenant-b",
create_dept_id="",
del_flag=1,
),
SysPost(
id="tenant-b-unbound-post",
post_name="租户B已解绑职位",
post_code="tenant-b-unbound-post",
status="1",
tenant_no="tenant-b",
create_dept_id="",
del_flag=0,
),
SysUserPost(
id="default-user-post",
user_id="u1",
post_id="default-post",
tenant_no=settings.DEFAULT_TENANT_NO,
create_dept_id="",
del_flag=0,
),
SysUserPost(
id="tenant-b-user-post",
user_id="u1",
post_id="tenant-b-post",
tenant_no="tenant-b",
create_dept_id="",
del_flag=0,
),
SysUserPost(
id="tenant-b-disabled-user-post",
user_id="u1",
post_id="tenant-b-disabled-post",
tenant_no="tenant-b",
create_dept_id="",
del_flag=0,
),
SysUserPost(
id="tenant-b-deleted-user-post",
user_id="u1",
post_id="tenant-b-deleted-post",
tenant_no="tenant-b",
create_dept_id="",
del_flag=0,
),
SysUserPost(
id="tenant-b-unbound-user-post",
user_id="u1",
post_id="tenant-b-unbound-post",
tenant_no="tenant-b",
create_dept_id="",
del_flag=1,
),
]
)
db.commit()
dept_name, post_names = issue_user_tokens.resolve_user_org_labels(db, user)
assert dept_name is None
assert post_names == ["租户B有效职位"]
assert set(list_post_ids_for_user(db, "u1")) == {
"default-post",
"tenant-b-post",
"tenant-b-disabled-post",
"tenant-b-deleted-post",
}
assert set(list_post_ids_for_user(db, "u1", tenant_no="tenant-b")) == {
"tenant-b-post",
"tenant-b-disabled-post",
"tenant-b-deleted-post",
}
user.dept_id = "tenant-b-deleted-dept"
db.commit()
dept_name, post_names = issue_user_tokens.resolve_user_org_labels(db, user)
assert dept_name is None
assert post_names == ["租户B有效职位"]
user.dept_id = "tenant-b-dept"
db.commit()
authenticated = _authenticate(db, monkeypatch)
assert authenticated["dept_name"] == "租户B部门"
assert authenticated["post_names"] == ["租户B有效职位"]
async def no_op(*args, **kwargs):
return None
monkeypatch.setattr(issue_user_tokens, "store_session", no_op)
monkeypatch.setattr(issue_user_tokens, "store_refresh_token", no_op)
issued = asyncio.run(issue_user_tokens.issue_tokens_for_user(db, user))
assert issued["userInfo"]["deptName"] == "租户B部门"
assert issued["userInfo"]["postNames"] == ["租户B有效职位"]
current_user = {"user_id": "u1", "tenant_no": "tenant-b"}
async def resolve_current(request, db_arg):
return current_user
monkeypatch.setattr(public, "_resolve_current_user_from_request", resolve_current)
monkeypatch.setattr(public, "update_user", lambda *args, **kwargs: None)
profiled = asyncio.run(
public.update_current_user_profile(
request=None,
body=ProfileUpdateRequest(),
db=db,
)
)
assert profiled["data"]["deptName"] == "租户B部门"
assert profiled["data"]["postNames"] == ["租户B有效职位"]