Skip to content

Commit 49261d5

Browse files
committed
fix: avoid login tenant context import shadowing
1 parent d268e8e commit 49261d5

2 files changed

Lines changed: 66 additions & 10 deletions

File tree

src/backend/bisheng/user/domain/services/user.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,6 @@ async def _reject_login_if_user_has_no_usable_access(
387387
# qualifies for login. Without bypass, every DAO below trips
388388
# NoTenantContextError because do_orm_execute can't infer a tenant for
389389
# tenant-aware tables (userrole/department/usergroup/roleaccess).
390-
from bisheng.core.context.tenant import bypass_tenant_filter
391-
392390
with bypass_tenant_filter():
393391
if role_ids is None:
394392
roles = await UserRoleDao.aget_user_roles(db_user.user_id)
@@ -479,7 +477,10 @@ async def user_login(cls, request: Request, user: UserLogin, auth_jwt: AuthJwt =
479477
NoTenantsAvailableError,
480478
TenantDisabledError,
481479
)
482-
from bisheng.core.context.tenant import DEFAULT_TENANT_ID, bypass_tenant_filter
480+
from bisheng.database.models.department import (
481+
UserDepartmentDao,
482+
)
483+
from bisheng.database.models.tenant import TenantDao, UserTenantDao
483484

484485
# Reject login when the user's primary department mounts to a
485486
# disabled child tenant. TenantResolver intentionally walks past
@@ -488,11 +489,6 @@ async def user_login(cls, request: Request, user: UserLogin, auth_jwt: AuthJwt =
488489
# treats it as a member-level freeze. Without this guard a member
489490
# of a disabled tenant would get fallback-routed to Root and log
490491
# in normally, which contradicts the operator's intent.
491-
from bisheng.database.models.department import (
492-
UserDepartmentDao,
493-
)
494-
from bisheng.database.models.tenant import TenantDao, UserTenantDao
495-
496492
with bypass_tenant_filter():
497493
primary_dept = await UserDepartmentDao.aget_user_primary_department(
498494
db_user.user_id,
@@ -571,7 +567,6 @@ async def user_login(cls, request: Request, user: UserLogin, auth_jwt: AuthJwt =
571567
# DB.status is authoritative; Redis blacklist is a defensive cross-check.
572568
if settings.multi_tenant.enabled and tenant_id and tenant_id > 0:
573569
from bisheng.common.errcode.tenant import TenantDisabledError
574-
from bisheng.core.context.tenant import bypass_tenant_filter
575570
from bisheng.database.models.tenant import TenantDao
576571

577572
with bypass_tenant_filter():
@@ -656,7 +651,6 @@ async def user_login(cls, request: Request, user: UserLogin, auth_jwt: AuthJwt =
656651
extra_fields["tenant_id"] = tenant_id
657652
tenant_info = next((t for t in (tenants_list or []) if t.get("tenant_id") == tenant_id), None)
658653
if not tenant_info and settings.multi_tenant.enabled:
659-
from bisheng.core.context.tenant import bypass_tenant_filter
660654
from bisheng.database.models.tenant import TenantDao
661655

662656
with bypass_tenant_filter():

src/backend/test/user/test_user_access_request_dedup.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,65 @@ async def test_login_access_guard_reuses_resolved_roles_and_department_flag():
105105
role_ids=[2],
106106
is_department_admin=False,
107107
)
108+
109+
110+
async def test_user_login_enters_tenant_bypass_before_multi_tenant_resolution():
111+
db_user = SimpleNamespace(user_id=12, delete=0)
112+
rejected_response = object()
113+
login_request = SimpleNamespace(user_name="alice", password="encrypted")
114+
captcha_setting = AsyncMock(return_value=False)
115+
116+
with (
117+
patch.object(
118+
user_service_module,
119+
"settings",
120+
SimpleNamespace(aget_from_db=captcha_setting),
121+
),
122+
patch.object(
123+
user_service_module.UserDao,
124+
"aget_login_candidates_by_account",
125+
new_callable=AsyncMock,
126+
return_value=[db_user],
127+
),
128+
patch.object(
129+
user_service_module.UserService,
130+
"decrypt_md5_password",
131+
return_value="password",
132+
),
133+
patch.object(
134+
user_service_module.UserService,
135+
"judge_user_password",
136+
new_callable=AsyncMock,
137+
),
138+
patch.object(
139+
user_service_module.UserService,
140+
"clear_error_password_key",
141+
new_callable=AsyncMock,
142+
),
143+
patch.object(
144+
user_service_module.UserRoleDao,
145+
"aget_user_roles",
146+
new_callable=AsyncMock,
147+
return_value=[],
148+
),
149+
patch.object(
150+
user_service_module.DepartmentDao,
151+
"aget_user_admin_departments",
152+
new_callable=AsyncMock,
153+
return_value=[],
154+
),
155+
patch.object(
156+
user_service_module.UserService,
157+
"_reject_login_if_user_has_no_usable_access",
158+
new_callable=AsyncMock,
159+
return_value=rejected_response,
160+
),
161+
):
162+
result = await user_service_module.UserService.user_login(
163+
SimpleNamespace(),
164+
login_request,
165+
auth_jwt=SimpleNamespace(),
166+
)
167+
168+
assert result is rejected_response
169+
captcha_setting.assert_awaited_once_with("use_captcha")

0 commit comments

Comments
 (0)