refac: async db

This commit is contained in:
Timothy Jaeryang Baek
2026-04-12 14:22:11 -05:00
parent b618d84065
commit 27169124f2
74 changed files with 4831 additions and 4479 deletions
+70 -70
View File
@@ -30,8 +30,8 @@ from open_webui.config import OAUTH_PROVIDERS
from open_webui.env import SCIM_AUTH_PROVIDER
from sqlalchemy.orm import Session
from open_webui.internal.db import get_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_async_session
log = logging.getLogger(__name__)
@@ -326,18 +326,18 @@ def get_scim_provider() -> str:
return SCIM_AUTH_PROVIDER
def find_user_by_external_id(external_id: str, db=None) -> Optional[UserModel]:
async def find_user_by_external_id(external_id: str, db=None) -> Optional[UserModel]:
"""Find a user by SCIM externalId, falling back to OAuth sub match."""
provider = get_scim_provider()
user = Users.get_user_by_scim_external_id(provider, external_id, db=db)
user = await Users.get_user_by_scim_external_id(provider, external_id, db=db)
if user:
return user
# Fallback: check if externalId matches an existing OAuth sub (account linking)
return Users.get_user_by_oauth_sub(provider, external_id, db=db)
return await Users.get_user_by_oauth_sub(provider, external_id, db=db)
def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
async def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
"""Convert internal User model to SCIM User"""
# Parse display name into name components
name_parts = user.name.split(' ', 1) if user.name else ['', '']
@@ -345,7 +345,7 @@ def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
family_name = name_parts[1] if len(name_parts) > 1 else ''
# Get user's groups
user_groups = Groups.get_groups_by_member_id(user.id, db=db)
user_groups = await Groups.get_groups_by_member_id(user.id, db=db)
groups = [
{
'value': group.id,
@@ -379,12 +379,12 @@ def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
)
def group_to_scim(group: GroupModel, request: Request, db=None) -> SCIMGroup:
async def group_to_scim(group: GroupModel, request: Request, db=None) -> SCIMGroup:
"""Convert internal Group model to SCIM Group"""
member_ids = Groups.get_group_user_ids_by_id(group.id, db) or []
member_ids = await Groups.get_group_user_ids_by_id(group.id, db) or []
# Batch-fetch all users to avoid N+1 queries
users = Users.get_users_by_user_ids(member_ids, db=db) if member_ids else []
users = await Users.get_users_by_user_ids(member_ids, db=db) if member_ids else []
members = [
SCIMGroupMember(
value=user.id,
@@ -512,7 +512,7 @@ async def get_users(
count: int = Query(20),
filter: Optional[str] = None,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""List SCIM Users"""
# Clamp per SCIM 2.0 spec (RFC 7644 §3.4.2.4):
@@ -527,25 +527,25 @@ async def get_users(
# Simple filter parsing - supports userName eq, externalId eq
if 'userName eq' in filter:
email = filter.split('"')[1]
user = Users.get_user_by_email(email, db=db)
user = await Users.get_user_by_email(email, db=db)
users_list = [user] if user else []
total = 1 if user else 0
elif 'externalId eq' in filter:
external_id = filter.split('"')[1]
user = find_user_by_external_id(external_id, db=db)
user = await find_user_by_external_id(external_id, db=db)
users_list = [user] if user else []
total = 1 if user else 0
else:
response = Users.get_users(skip=skip, limit=limit, db=db)
response = await Users.get_users(skip=skip, limit=limit, db=db)
users_list = response['users']
total = response['total']
else:
response = Users.get_users(skip=skip, limit=limit, db=db)
response = await Users.get_users(skip=skip, limit=limit, db=db)
users_list = response['users']
total = response['total']
# Convert to SCIM format
scim_users = [user_to_scim(user, request, db=db) for user in users_list]
scim_users = [await user_to_scim(user, request, db=db) for user in users_list]
return SCIMListResponse(
totalResults=total,
@@ -560,14 +560,14 @@ async def get_user(
user_id: str,
request: Request,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get SCIM User by ID"""
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if not user:
return scim_error(status_code=status.HTTP_404_NOT_FOUND, detail=f'User {user_id} not found')
return user_to_scim(user, request, db=db)
return await user_to_scim(user, request, db=db)
@router.post('/Users', response_model=SCIMUser, status_code=status.HTTP_201_CREATED)
@@ -575,12 +575,12 @@ async def create_user(
request: Request,
user_data: SCIMUserCreateRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Create SCIM User"""
# Check for duplicate by externalId
if user_data.externalId:
existing_user = find_user_by_external_id(user_data.externalId, db=db)
existing_user = await find_user_by_external_id(user_data.externalId, db=db)
if existing_user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
@@ -596,7 +596,7 @@ async def create_user(
email = email.lower()
# Check for duplicate by email
existing_user = Users.get_user_by_email(email, db=db)
existing_user = await Users.get_user_by_email(email, db=db)
if existing_user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
@@ -619,7 +619,7 @@ async def create_user(
if user_data.photos and len(user_data.photos) > 0:
profile_image = user_data.photos[0].value
new_user = Users.insert_new_user(
new_user = await Users.insert_new_user(
id=user_id,
name=name,
email=email,
@@ -637,10 +637,10 @@ async def create_user(
# Store externalId in the scim field
if user_data.externalId:
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
new_user = Users.get_user_by_id(user_id, db=db)
await Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
new_user = await Users.get_user_by_id(user_id, db=db)
return user_to_scim(new_user, request, db=db)
return await user_to_scim(new_user, request, db=db)
@router.put('/Users/{user_id}', response_model=SCIMUser)
@@ -649,10 +649,10 @@ async def update_user(
request: Request,
user_data: SCIMUserUpdateRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update SCIM User (full update)"""
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -682,7 +682,7 @@ async def update_user(
if user_data.photos and len(user_data.photos) > 0:
update_data['profile_image_url'] = user_data.photos[0].value
updated_user = Users.update_user_by_id(user_id, update_data, db=db)
updated_user = await Users.update_user_by_id(user_id, update_data, db=db)
if not updated_user:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -692,10 +692,10 @@ async def update_user(
# Update externalId in the scim field
if user_data.externalId:
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
updated_user = Users.get_user_by_id(user_id, db=db)
await Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
updated_user = await Users.get_user_by_id(user_id, db=db)
return user_to_scim(updated_user, request, db=db)
return await user_to_scim(updated_user, request, db=db)
@router.patch('/Users/{user_id}', response_model=SCIMUser)
@@ -704,10 +704,10 @@ async def patch_user(
request: Request,
patch_data: SCIMPatchRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update SCIM User (partial update)"""
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -734,11 +734,11 @@ async def patch_user(
update_data['name'] = value
elif path == 'externalId':
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, value, db=db)
await Users.update_user_scim_by_id(user_id, provider, value, db=db)
# Update user
if update_data:
updated_user = Users.update_user_by_id(user_id, update_data, db=db)
updated_user = await Users.update_user_by_id(user_id, update_data, db=db)
if not updated_user:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -747,7 +747,7 @@ async def patch_user(
else:
updated_user = user
return user_to_scim(updated_user, request, db=db)
return await user_to_scim(updated_user, request, db=db)
@router.delete('/Users/{user_id}', status_code=status.HTTP_204_NO_CONTENT)
@@ -755,17 +755,17 @@ async def delete_user(
user_id: str,
request: Request,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Delete SCIM User"""
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'User {user_id} not found',
)
success = Users.delete_user_by_id(user_id, db=db)
success = await Users.delete_user_by_id(user_id, db=db)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -783,7 +783,7 @@ async def get_groups(
count: int = Query(20),
filter: Optional[str] = None,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""List SCIM Groups"""
# Clamp per SCIM 2.0 spec (RFC 7644 §3.4.2.4):
@@ -795,13 +795,13 @@ async def get_groups(
if filter:
if 'displayName eq' in filter:
display_name = filter.split('"')[1]
group = Groups.get_group_by_name(display_name, db=db)
group = await Groups.get_group_by_name(display_name, db=db)
groups_list = [group] if group else []
else:
# Unrecognized filter — fall back to all groups
groups_list = Groups.get_all_groups(db=db)
groups_list = await Groups.get_all_groups(db=db)
else:
groups_list = Groups.get_all_groups(db=db)
groups_list = await Groups.get_all_groups(db=db)
# Apply pagination
total = len(groups_list)
@@ -810,7 +810,7 @@ async def get_groups(
paginated_groups = groups_list[start:end]
# Convert to SCIM format
scim_groups = [group_to_scim(group, request, db=db) for group in paginated_groups]
scim_groups = [await group_to_scim(group, request, db=db) for group in paginated_groups]
return SCIMListResponse(
totalResults=total,
@@ -825,17 +825,17 @@ async def get_group(
group_id: str,
request: Request,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get SCIM Group by ID"""
group = Groups.get_group_by_id(group_id, db=db)
group = await Groups.get_group_by_id(group_id, db=db)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'Group {group_id} not found',
)
return group_to_scim(group, request, db=db)
return await group_to_scim(group, request, db=db)
@router.post('/Groups', response_model=SCIMGroup, status_code=status.HTTP_201_CREATED)
@@ -843,7 +843,7 @@ async def create_group(
request: Request,
group_data: SCIMGroupCreateRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Create SCIM Group"""
# Extract member IDs
@@ -861,14 +861,14 @@ async def create_group(
)
# Need to get the creating user's ID - we'll use the first admin
admin_user = Users.get_super_admin_user(db=db)
admin_user = await Users.get_super_admin_user(db=db)
if not admin_user:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='No admin user found',
)
new_group = Groups.insert_new_group(admin_user.id, form, db=db)
new_group = await Groups.insert_new_group(admin_user.id, form, db=db)
if not new_group:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -884,12 +884,12 @@ async def create_group(
description=new_group.description,
)
Groups.update_group_by_id(new_group.id, update_form, db=db)
Groups.set_group_user_ids_by_id(new_group.id, member_ids, db=db)
await Groups.update_group_by_id(new_group.id, update_form, db=db)
await Groups.set_group_user_ids_by_id(new_group.id, member_ids, db=db)
new_group = Groups.get_group_by_id(new_group.id, db=db)
new_group = await Groups.get_group_by_id(new_group.id, db=db)
return group_to_scim(new_group, request, db=db)
return await group_to_scim(new_group, request, db=db)
@router.put('/Groups/{group_id}', response_model=SCIMGroup)
@@ -898,10 +898,10 @@ async def update_group(
request: Request,
group_data: SCIMGroupUpdateRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update SCIM Group (full update)"""
group = Groups.get_group_by_id(group_id, db=db)
group = await Groups.get_group_by_id(group_id, db=db)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -919,17 +919,17 @@ async def update_group(
# Handle members if provided
if group_data.members is not None:
member_ids = [member.value for member in group_data.members]
Groups.set_group_user_ids_by_id(group_id, member_ids, db=db)
await Groups.set_group_user_ids_by_id(group_id, member_ids, db=db)
# Update group
updated_group = Groups.update_group_by_id(group_id, update_form, db=db)
updated_group = await Groups.update_group_by_id(group_id, update_form, db=db)
if not updated_group:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Failed to update group',
)
return group_to_scim(updated_group, request, db=db)
return await group_to_scim(updated_group, request, db=db)
@router.patch('/Groups/{group_id}', response_model=SCIMGroup)
@@ -938,10 +938,10 @@ async def patch_group(
request: Request,
patch_data: SCIMPatchRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update SCIM Group (partial update)"""
group = Groups.get_group_by_id(group_id, db=db)
group = await Groups.get_group_by_id(group_id, db=db)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -965,7 +965,7 @@ async def patch_group(
update_form.name = value
elif path == 'members':
# Replace all members
Groups.set_group_user_ids_by_id(group_id, [member['value'] for member in value], db=db)
await Groups.set_group_user_ids_by_id(group_id, [member['value'] for member in value], db=db)
elif op == 'add':
if path == 'members':
@@ -973,22 +973,22 @@ async def patch_group(
if isinstance(value, list):
for member in value:
if isinstance(member, dict) and 'value' in member:
Groups.add_users_to_group(group_id, [member['value']], db=db)
await Groups.add_users_to_group(group_id, [member['value']], db=db)
elif op == 'remove':
if path and path.startswith('members[value eq'):
# Remove specific member
member_id = path.split('"')[1]
Groups.remove_users_from_group(group_id, [member_id], db=db)
await Groups.remove_users_from_group(group_id, [member_id], db=db)
# Update group
updated_group = Groups.update_group_by_id(group_id, update_form, db=db)
updated_group = await Groups.update_group_by_id(group_id, update_form, db=db)
if not updated_group:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Failed to update group',
)
return group_to_scim(updated_group, request, db=db)
return await group_to_scim(updated_group, request, db=db)
@router.delete('/Groups/{group_id}', status_code=status.HTTP_204_NO_CONTENT)
@@ -996,17 +996,17 @@ async def delete_group(
group_id: str,
request: Request,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Delete SCIM Group"""
group = Groups.get_group_by_id(group_id, db=db)
group = await Groups.get_group_by_id(group_id, db=db)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'Group {group_id} not found',
)
success = Groups.delete_group_by_id(group_id, db=db)
success = await Groups.delete_group_by_id(group_id, db=db)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,