fix(oidc): 修复use_raw_username绑定验证逻辑 - 占位用户格式存储目标用户ID,find_user_by_oidc_sub返回真实用户
This commit is contained in:
parent
ce011748bc
commit
ea530039c9
@ -475,12 +475,26 @@ async def find_user_by_oidc_sub(db, sub: str) -> User | None:
|
|||||||
if user:
|
if user:
|
||||||
return user
|
return user
|
||||||
|
|
||||||
# 方法2: 检查是否有用户的 user_id 包含 "oidc:" 前缀(兼容旧格式)
|
# 方法2: 检查是否有绑定占位用户格式: "oidc:{sub}:{target_user_id}"(use_raw_username 绑定记录)
|
||||||
legacy_result = await db.execute(
|
legacy_result = await db.execute(
|
||||||
select(User).filter(User.user_id.like(f"{standard_oidc_user_id}:%"), User.is_deleted == 0).order_by(User.id.asc())
|
select(User).filter(User.user_id.like(f"{standard_oidc_user_id}:%"), User.is_deleted == 0).order_by(User.id.asc())
|
||||||
)
|
)
|
||||||
legacy_users = list(legacy_result.scalars().all())
|
legacy_users = list(legacy_result.scalars().all())
|
||||||
if legacy_users:
|
if legacy_users:
|
||||||
|
# 对于绑定占位用户,user_id 格式为 oidc:{sub}:{target_user_id},解析出 target_user_id 并返回真实用户
|
||||||
|
for placeholder in legacy_users:
|
||||||
|
parts = placeholder.user_id.split(":")
|
||||||
|
if len(parts) >= 3:
|
||||||
|
try:
|
||||||
|
target_user_id = int(parts[2])
|
||||||
|
result = await db.execute(select(User).filter(User.id == target_user_id, User.is_deleted == 0))
|
||||||
|
target_user = result.scalar_one_or_none()
|
||||||
|
if target_user:
|
||||||
|
logger.debug(f"Resolved OIDC binding placeholder {placeholder.user_id} to user {target_user_id}")
|
||||||
|
return target_user
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
# 如果没有解析出有效的目标用户,返回第一个legacy用户(向后兼容)
|
||||||
if len(legacy_users) > 1:
|
if len(legacy_users) > 1:
|
||||||
logger.warning(f"Multiple legacy OIDC users matched for sub={sub}, use earliest id={legacy_users[0].id}")
|
logger.warning(f"Multiple legacy OIDC users matched for sub={sub}, use earliest id={legacy_users[0].id}")
|
||||||
return legacy_users[0]
|
return legacy_users[0]
|
||||||
@ -497,37 +511,52 @@ async def find_deleted_oidc_user_by_sub(db, sub: str) -> User | None:
|
|||||||
if deleted_user:
|
if deleted_user:
|
||||||
return deleted_user
|
return deleted_user
|
||||||
|
|
||||||
|
# 检查绑定占位格式 oidc:{sub}:{target_user_id}
|
||||||
legacy_result = await db.execute(
|
legacy_result = await db.execute(
|
||||||
select(User).filter(User.user_id.like(f"{oidc_user_id}:%"), User.is_deleted == 1).order_by(User.id.asc())
|
select(User).filter(User.user_id.like(f"{oidc_user_id}:%"), User.is_deleted == 1).order_by(User.id.asc())
|
||||||
)
|
)
|
||||||
return legacy_result.scalar_one_or_none()
|
legacy_users = list(legacy_result.scalars().all())
|
||||||
|
if legacy_users:
|
||||||
|
for placeholder in legacy_users:
|
||||||
|
parts = placeholder.user_id.split(":")
|
||||||
|
if len(parts) >= 3:
|
||||||
|
try:
|
||||||
|
target_user_id = int(parts[2])
|
||||||
|
result = await db.execute(select(User).filter(User.id == target_user_id, User.is_deleted == 1))
|
||||||
|
target_user = result.scalar_one_or_none()
|
||||||
|
if target_user:
|
||||||
|
return target_user
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return legacy_users[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def _create_oidc_binding_placeholder(db, sub: str, target_user: User) -> None:
|
async def _create_oidc_binding_placeholder(db, sub: str, target_user: User) -> None:
|
||||||
"""创建 OIDC sub 绑定占位用户(仅用于记录绑定关系,不用于登录)
|
"""创建 OIDC sub 绑定占位用户(仅用于记录绑定关系,不用于登录)
|
||||||
|
|
||||||
在 use_raw_username 模式下,我们仍然创建一个 oidc:{sub} 的占位用户,
|
在 use_raw_username 模式下,我们创建一个占位用户格式: oidc:{sub}:{target_user_id},
|
||||||
以便通过 find_user_by_oidc_sub 可以查询到 sub 绑定到哪个用户,
|
以便通过 find_user_by_oidc_sub 解析出绑定的真实用户并返回,
|
||||||
这样就能在不修改User表结构的前提下,保持绑定关系可验证,防止账号冒用。
|
这样就能在不修改User表结构的前提下,保持绑定关系可验证,防止账号冒用。
|
||||||
"""
|
"""
|
||||||
from yuxi.repositories.user_repository import UserRepository
|
from yuxi.repositories.user_repository import UserRepository
|
||||||
user_repo = UserRepository()
|
user_repo = UserRepository()
|
||||||
|
|
||||||
oidc_user_id = f"oidc:{sub}"
|
# 占位用户格式: oidc:{sub}:{target_user_id},这样find_user_by_oidc_sub可以解析出目标用户ID
|
||||||
result = await db.execute(select(User).filter(User.user_id == oidc_user_id, User.is_deleted == 0))
|
oidc_placeholder_id = f"oidc:{sub}:{target_user.id}"
|
||||||
|
result = await db.execute(select(User).filter(User.user_id == oidc_placeholder_id, User.is_deleted == 0))
|
||||||
if result.scalar_one_or_none():
|
if result.scalar_one_or_none():
|
||||||
# 占位用户已存在,无需重复创建
|
# 占位用户已存在,无需重复创建
|
||||||
return
|
return
|
||||||
|
|
||||||
# 创建占位用户:使用随机密码,soft deleted 标记不参与实际登录
|
# 创建占位用户:使用随机密码,不用于实际登录,仅存储绑定关系
|
||||||
# 但我们保留它存在只是为了记录 sub -> target_user 的绑定关系
|
|
||||||
random_password = secrets.token_urlsafe(32)
|
random_password = secrets.token_urlsafe(32)
|
||||||
password_hash = AuthUtils.hash_password(random_password)
|
password_hash = AuthUtils.hash_password(random_password)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await user_repo.create({
|
await user_repo.create({
|
||||||
"username": f"oidc-binding-{sub[:8]}",
|
"username": f"oidc-binding-{sub[:8]}",
|
||||||
"user_id": oidc_user_id,
|
"user_id": oidc_placeholder_id,
|
||||||
"phone_number": None,
|
"phone_number": None,
|
||||||
"avatar": None,
|
"avatar": None,
|
||||||
"password_hash": password_hash,
|
"password_hash": password_hash,
|
||||||
@ -535,7 +564,7 @@ async def _create_oidc_binding_placeholder(db, sub: str, target_user: User) -> N
|
|||||||
"department_id": target_user.department_id,
|
"department_id": target_user.department_id,
|
||||||
"last_login": utc_now_naive(),
|
"last_login": utc_now_naive(),
|
||||||
})
|
})
|
||||||
logger.info(f"Created OIDC binding placeholder for sub {sub} -> user {target_user.user_id}")
|
logger.info(f"Created OIDC binding placeholder for sub {sub} -> user {target_user.id} ({target_user.user_id})")
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
# 并发创建冲突,忽略
|
# 并发创建冲突,忽略
|
||||||
await db.rollback()
|
await db.rollback()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user