Files

134 lines
5.1 KiB
Python
Raw Permalink Normal View History

2026-03-01 13:49:36 -06:00
import logging
from open_webui.models.access_grants import AccessGrants
2026-03-01 13:49:36 -06:00
from open_webui.models.channels import Channels
from open_webui.models.chats import Chats
from open_webui.models.files import Files
2026-03-01 13:49:36 -06:00
from open_webui.models.groups import Groups
from open_webui.models.knowledge import Knowledges
from open_webui.models.models import Models
from open_webui.models.users import UserModel
2026-04-12 14:22:11 -05:00
from sqlalchemy.ext.asyncio import AsyncSession
2026-03-01 13:49:36 -06:00
log = logging.getLogger(__name__)
2026-04-12 14:22:11 -05:00
async def has_access_to_file(
file_id: str | None,
2026-03-01 13:49:36 -06:00
access_type: str,
user: UserModel,
2026-04-12 14:22:11 -05:00
db: AsyncSession | None = None,
2026-03-01 13:49:36 -06:00
) -> bool:
"""
Check if a user has the specified access to a file through any of:
- Knowledge bases (ownership or access grants)
- Shared workspace models that attach the file directly
2026-03-01 13:49:36 -06:00
- Channels the user is a member of
- Shared chats
NOTE: This does NOT check direct file ownership callers should check
file.user_id == user.id separately before calling this.
"""
2026-04-12 14:22:11 -05:00
file = await Files.get_file_by_id(file_id, db=db)
2026-03-17 17:58:01 -05:00
log.debug(f'Checking if user has {access_type} access to file')
2026-03-01 13:49:36 -06:00
if not file:
return False
2026-03-01 13:54:44 -06:00
# Direct ownership
if file.user_id == user.id:
return True
# Check if the file is associated with any knowledge bases the user has access to.
# An object (knowledge base or workspace model) confers write/delete on a file only when
# 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.
2026-04-12 14:22:11 -05:00
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)}
2026-03-01 13:49:36 -06:00
for knowledge_base in knowledge_bases:
if (
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,
)
) and (access_type == 'read' or knowledge_base.user_id == file.user_id):
2026-03-01 13:49:36 -06:00
return True
2026-03-17 17:58:01 -05:00
knowledge_base_id = file.meta.get('collection_name') if file.meta else None
2026-03-01 13:49:36 -06:00
if knowledge_base_id:
2026-04-12 14:22:11 -05:00
knowledge_bases = await Knowledges.get_knowledge_bases_by_user_id(user.id, access_type, db=db)
2026-03-01 13:49:36 -06:00
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
):
2026-03-01 13:49:36 -06:00
return True
# Check if the file is associated with any channels the user has access to
2026-04-12 14:22:11 -05:00
channels = await Channels.get_channels_by_file_id_and_user_id(file_id, user.id, db=db)
2026-03-17 17:58:01 -05:00
if access_type == 'read' and channels:
2026-03-01 13:49:36 -06:00
return True
# Check if the file is associated with any chats the user has access to
2026-04-17 10:16:32 +09:00
shared_chat_ids = await Chats.get_shared_chat_ids_by_file_id(file_id, db=db)
if access_type == 'read' and shared_chat_ids:
2026-04-17 10:16:32 +09:00
accessible_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='shared_chat',
resource_ids=shared_chat_ids,
permission='read',
user_group_ids=user_group_ids,
db=db,
)
if accessible_ids:
return True
2026-03-01 13:49:36 -06:00
# 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).
2026-04-12 14:22:11 -05:00
for model in await Models.get_models_by_user_id(user.id, permission=access_type, db=db):
2026-03-17 17:58:01 -05:00
knowledge_items = getattr(model.meta, 'knowledge', None) or []
for item in knowledge_items:
2026-03-17 17:58:01 -05:00
if isinstance(item, dict) and item.get('type') == 'file' and item.get('id') == file.id:
if access_type == 'read' or model.user_id == file.user_id:
return True
2026-03-01 13:49:36 -06:00
return False
2026-05-11 02:12:38 +09:00
async def get_accessible_folder_files(
entries: list[dict] | None,
user: UserModel,
db: AsyncSession | None = None,
) -> list[dict]:
"""Filter folder.data['files'] entries to those the caller can read.
Each entry is expected to have 'type' ('file' or 'collection') and 'id'.
Admins bypass all checks. Unknown types are kept as-is.
"""
if not entries:
return []
if user.role == 'admin':
return list(entries)
accessible: list[dict] = []
for entry in entries:
if not isinstance(entry, dict):
continue
entry_type = entry.get('type')
entry_id = entry.get('id')
if not entry_id:
accessible.append(entry)
continue
if entry_type == 'file':
if await has_access_to_file(entry_id, 'read', user, db=db):
accessible.append(entry)
elif entry_type == 'collection':
if await Knowledges.check_access_by_user_id(entry_id, user.id, 'read', db=db):
accessible.append(entry)
else:
accessible.append(entry)
return accessible