This commit is contained in:
Classic298 2026-09-30 19:07:13 +04:00 • committed by GitHub
commit 77c6af0756
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 77 additions and 0 deletions

View file

@ -515,6 +515,22 @@ class AccessGrantsTable:
grants = result.scalars().all()
return [AccessGrantModel.model_validate(g) for g in grants]
async def get_grants_by_group_id(
self,
group_id: str,
db: Optional[AsyncSession] = None,
) -> list[AccessGrantModel]:
"""Get all grants given to a specific group."""
async with get_async_db_context(db) as db:
result = await db.execute(
select(AccessGrant).filter_by(
principal_type=PRINCIPAL_TYPE_GROUP,
principal_id=group_id,
)
)
grants = result.scalars().all()
return [AccessGrantModel.model_validate(g) for g in grants]
async def get_grants_by_resources(
self,
resource_type: str,

View file

@ -60,6 +60,7 @@ from open_webui.models.users import (
Users,
UserStatus,
)
from open_webui.socket.main import leave_group_rooms_for_users
from open_webui.utils.access_control import get_permissions, has_permission
from open_webui.utils.auth import (
create_api_key,
@ -695,7 +696,13 @@ async def ldap_auth(
try:
if ENABLE_LDAP_GROUP_CREATION:
await Groups.create_groups_by_group_names(user.id, user_groups, db=db)
previous_group_ids = {
group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)
}
await Groups.sync_groups_by_group_names(user.id, user_groups, db=db)
current_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
for group_id in previous_group_ids - current_group_ids:
await leave_group_rooms_for_users(group_id, [user.id])
log.info('Successfully synced groups for user %s: %s', user.id, user_groups)
except Exception as e:
log.error(f'Failed to sync groups for user {user.id}: {e}')
@ -766,7 +773,11 @@ async def signin(
group_names = [name.strip() for name in group_names if name.strip()]
if group_names:
previous_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
await Groups.sync_groups_by_group_names(user.id, group_names, db=db)
current_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
for group_id in previous_group_ids - current_group_ids:
await leave_group_rooms_for_users(group_id, [user.id])
if WEBUI_AUTH_TRUSTED_ROLE_HEADER:
trusted_role = request.headers.get(WEBUI_AUTH_TRUSTED_ROLE_HEADER, '').lower().strip()

View file

@ -21,6 +21,7 @@ from open_webui.models.knowledge import Knowledges
from open_webui.models.models import Models
from open_webui.models.tools import Tools
from open_webui.models.users import UserInfoResponse, Users
from open_webui.socket.main import leave_group_rooms_for_users
from open_webui.utils.auth import get_admin_user, get_verified_user
from sqlalchemy.ext.asyncio import AsyncSession
@ -270,6 +271,7 @@ async def remove_users_from_group(
try:
group = await Groups.remove_users_from_group(id, form_data.user_ids, db=db)
if group:
await leave_group_rooms_for_users(id, form_data.user_ids)
await publish_event(
request,
EVENTS.GROUP_MEMBER_REMOVED,
@ -306,8 +308,10 @@ async def delete_group_by_id(
request: Request, id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)
):
try:
user_ids = await Groups.get_group_user_ids_by_id(id, db=db)
result = await Groups.delete_group_by_id(id, db=db)
if result:
await leave_group_rooms_for_users(id, user_ids)
await publish_event(
request,
EVENTS.GROUP_DELETED,

View file

@ -21,6 +21,7 @@ from open_webui.env import SCIM_AUTH_PROVIDER
from open_webui.internal.db import get_async_session
from open_webui.models.groups import GroupModel, Groups
from open_webui.models.users import UserModel, Users
from open_webui.socket.main import leave_group_rooms_for_users
from open_webui.utils.auth import (
decode_token,
get_admin_user,
@ -1064,6 +1065,7 @@ async def update_group(
data={'member_ids': added_member_ids, 'count': len(added_member_ids)},
)
if removed_member_ids:
await leave_group_rooms_for_users(group_id, removed_member_ids)
await publish_event(
request,
EVENTS.GROUP_MEMBER_REMOVED,
@ -1156,6 +1158,7 @@ async def patch_group(
data={'member_ids': sorted(set(added_member_ids)), 'count': len(set(added_member_ids))},
)
if removed_member_ids:
await leave_group_rooms_for_users(group_id, removed_member_ids)
await publish_event(
request,
EVENTS.GROUP_MEMBER_REMOVED,
@ -1182,12 +1185,14 @@ async def delete_group(
detail=f'Group {group_id} not found',
)
member_ids = await Groups.get_group_user_ids_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,
detail='Failed to delete group',
)
await leave_group_rooms_for_users(group_id, member_ids)
await publish_event(
request,

View file

@ -38,6 +38,7 @@ from open_webui.models.channels import Channels
from open_webui.models.chats import Chats
from open_webui.models.config import Config
from open_webui.models.folders import Folders
from open_webui.models.groups import Groups
from open_webui.models.notes import Notes, NoteUpdateForm
from open_webui.models.users import UserNameResponse, Users
from open_webui.socket.redis_room_channels import AsyncRedisRoomChannelManager
@ -414,6 +415,44 @@ async def leave_room_for_users(room: str, user_ids: list[str]):
log.debug('Failed to make session %s leave room %s: %s', sid, room, e)
async def leave_group_rooms_for_users(group_id: str, user_ids: list[str]):
"""Make users leave the note and channel rooms they could only read through a group."""
users = await Users.get_users_by_user_ids(user_ids)
non_admin_user_ids = [user.id for user in users if user.role != 'admin']
group_ids_by_user_id = {
user_id: {group.id for group in groups}
for user_id, groups in (await Groups.get_groups_by_member_ids(non_admin_user_ids)).items()
}
for grant in await AccessGrants.get_grants_by_group_id(group_id):
if grant.permission != 'read':
continue
if grant.resource_type == 'note':
resource = await Notes.get_note_by_id(grant.resource_id)
rooms = [f'note:{grant.resource_id}', f'doc_note:{grant.resource_id}']
elif grant.resource_type == 'channel':
resource = await Channels.get_channel_by_id(grant.resource_id)
# Group and DM channels use membership instead of access grants.
if resource and resource.type in ['group', 'dm']:
continue
rooms = [f'channel:{grant.resource_id}']
else:
continue
if not resource:
continue
revoked_user_ids = [
user_id
for user_id in non_admin_user_ids
if user_id != resource.user_id
and not await AccessGrants.has_access(
user_id, grant.resource_type, grant.resource_id, user_group_ids=group_ids_by_user_id[user_id]
)
]
for room in rooms:
await leave_room_for_users(room, revoked_user_ids)
async def disconnect_user_sessions(user_id: str):
"""Disconnect all Socket.IO sessions belonging to a user.

View file

@ -83,6 +83,7 @@ from open_webui.models.groups import GroupForm, GroupModel, Groups, GroupUpdateF
from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.models.users import Users
from open_webui.retrieval.web.utils import get_ssrf_safe_session, validate_url
from open_webui.socket.main import leave_group_rooms_for_users
from open_webui.utils.auth import (
create_token,
get_password_hash,
@ -1746,6 +1747,7 @@ class OAuthManager:
# Remove group from user
log.debug('Removing user from group %s as it is no longer in their oauth groups', group_model.name)
if await Groups.remove_users_from_group(group_model.id, [user.id], db=db):
await leave_group_rooms_for_users(group_model.id, [user.id])
await publish_event(
request,
EVENTS.GROUP_MEMBER_REMOVED,