diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index ff72dcfc65..d50f722494 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -303,26 +303,26 @@ class ModelsTable: ] async def get_models_by_user_id( - self, user_id: str, permission: str = 'write', db: AsyncSession | None = None + self, + user_id: str, + permission: str = 'write', + db: AsyncSession | None = None, + user_group_ids: set[str] | None = None, ) -> list[ModelUserResponse]: models = await self.get_models(db=db) - user_groups = await Groups.get_groups_by_member_id(user_id, db=db) - user_group_ids = {group.id for group in user_groups} + if user_group_ids is None: + user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user_id, db=db)} - result = [] - for model in models: - if model.user_id == user_id: - result.append(model) - elif await AccessGrants.has_access( - user_id=user_id, - resource_type='model', - resource_id=model.id, - permission=permission, - user_group_ids=user_group_ids, - db=db, - ): - result.append(model) - return result + # One grants query for all non-owned models instead of one per model + accessible_ids = await AccessGrants.get_accessible_resource_ids( + user_id=user_id, + resource_type='model', + resource_ids=[model.id for model in models if model.user_id != user_id], + permission=permission, + user_group_ids=user_group_ids, + db=db, + ) + return [model for model in models if model.user_id == user_id or model.id in accessible_ids] def _has_permission(self, db, query, filter: dict, permission: str = 'read'): return AccessGrants.has_permission_filter( diff --git a/backend/open_webui/utils/access_control/files.py b/backend/open_webui/utils/access_control/files.py index ddb6acb066..4d6880cdfa 100644 --- a/backend/open_webui/utils/access_control/files.py +++ b/backend/open_webui/utils/access_control/files.py @@ -18,6 +18,7 @@ async def has_access_to_file( access_type: str, user: UserModel, db: AsyncSession | None = None, + user_group_ids: set[str] | None = None, ) -> bool: """ Check if a user has the specified access to a file through any of: @@ -43,7 +44,8 @@ async def has_access_to_file( # the object's OWNER owns that file; otherwise a read-only file laundered into an object # the user controls would gain write/delete on it (CWE-863). Read access is unaffected. knowledge_bases = await Knowledges.get_knowledges_by_file_id(file_id, db=db) - user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)} + if user_group_ids is None: + user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)} for knowledge_base in knowledge_bases: if ( knowledge_base.user_id == user.id @@ -60,12 +62,25 @@ async def has_access_to_file( knowledge_base_id = file.meta.get('collection_name') if file.meta else None if knowledge_base_id: - knowledge_bases = await Knowledges.get_knowledge_bases_by_user_id(user.id, access_type, db=db) - for knowledge_base in knowledge_bases: - if knowledge_base.id == knowledge_base_id and ( - access_type == 'read' or knowledge_base.user_id == file.user_id - ): - return True + # Fetch the one referenced knowledge base instead of listing every + # knowledge base the user can access just to scan for this id. + knowledge_base = await Knowledges.get_knowledge_by_id(knowledge_base_id, db=db) + if ( + knowledge_base + and (access_type == 'read' or knowledge_base.user_id == file.user_id) + and ( + knowledge_base.user_id == user.id + or await AccessGrants.has_access( + user_id=user.id, + resource_type='knowledge', + resource_id=knowledge_base.id, + permission=access_type, + user_group_ids=user_group_ids, + db=db, + ) + ) + ): + return True # Check if the file is associated with any channels the user has access to channels = await Channels.get_channels_by_file_id_and_user_id(file_id, user.id, db=db) @@ -88,7 +103,9 @@ async def has_access_to_file( # Check if the file is directly attached to a shared workspace model (per the ownership # note above, model write is conferred only for files the model owner owns). - for model in await Models.get_models_by_user_id(user.id, permission=access_type, db=db): + for model in await Models.get_models_by_user_id( + user.id, permission=access_type, db=db, user_group_ids=user_group_ids + ): knowledge_items = getattr(model.meta, 'knowledge', None) or [] for item in knowledge_items: if isinstance(item, dict) and item.get('type') == 'file' and item.get('id') == file.id: @@ -113,6 +130,9 @@ async def get_accessible_folder_files( if user.role == 'admin': return list(entries) + # One group-membership fetch for the whole folder listing + user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)} + accessible: list[dict] = [] for entry in entries: if not isinstance(entry, dict): @@ -123,7 +143,7 @@ async def get_accessible_folder_files( accessible.append(entry) continue if entry_type == 'file': - if await has_access_to_file(entry_id, 'read', user, db=db): + if await has_access_to_file(entry_id, 'read', user, db=db, user_group_ids=user_group_ids): accessible.append(entry) elif entry_type == 'collection': if await Knowledges.check_access_by_user_id(entry_id, user.id, 'read', db=db):