mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
refac
This commit is contained in:
parent
e3cce68ef2
commit
1f0dc90abe
11 changed files with 287 additions and 82 deletions
|
|
@ -1843,6 +1843,10 @@ USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING = (
|
|||
os.getenv('USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING = (
|
||||
os.getenv('USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING', 'False').lower() == 'true'
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_CHAT_EXPORT = os.getenv('USER_PERMISSIONS_CHAT_EXPORT', 'True').lower() == 'true'
|
||||
|
||||
USER_PERMISSIONS_CHAT_IMPORT = os.getenv('USER_PERMISSIONS_CHAT_IMPORT', 'True').lower() == 'true'
|
||||
|
|
@ -1929,6 +1933,7 @@ DEFAULT_USER_PERMISSIONS = {
|
|||
'public_notes': USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING,
|
||||
'folders': USER_PERMISSIONS_FOLDERS_ALLOW_SHARING,
|
||||
'public_chats': USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING,
|
||||
'open_chats': USER_PERMISSIONS_CHAT_ALLOW_OPEN_SHARING,
|
||||
'public_calendars': USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING,
|
||||
},
|
||||
'access_grants': {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PRINCIPAL_TYPE_ANYONE = 'anyone'
|
||||
PRINCIPAL_TYPE_GROUP = 'group'
|
||||
PRINCIPAL_TYPE_USER = 'user'
|
||||
WILDCARD_PRINCIPAL_ID = '*'
|
||||
|
||||
|
||||
####################
|
||||
# AccessGrant DB Schema
|
||||
|
|
@ -23,7 +28,7 @@ class AccessGrant(Base):
|
|||
id = Column(Text, primary_key=True)
|
||||
resource_type = Column(Text, nullable=False) # "knowledge", "model", "prompt", "tool", "note", "channel", "file"
|
||||
resource_id = Column(Text, nullable=False)
|
||||
principal_type = Column(Text, nullable=False) # "user" or "group"
|
||||
principal_type = Column(Text, nullable=False) # "user", "group", or "anyone"
|
||||
principal_id = Column(Text, nullable=False) # user_id, group_id, or "*" (wildcard for public)
|
||||
permission = Column(Text, nullable=False) # "read" or "write"
|
||||
created_at = Column(BigInteger, nullable=False)
|
||||
|
|
@ -163,12 +168,16 @@ def normalize_access_grants(access_grants: Optional[list]) -> list[dict]:
|
|||
principal_id = grant.get('principal_id')
|
||||
permission = grant.get('permission')
|
||||
|
||||
if principal_type not in ('user', 'group'):
|
||||
if principal_type not in (PRINCIPAL_TYPE_USER, PRINCIPAL_TYPE_GROUP, PRINCIPAL_TYPE_ANYONE):
|
||||
continue
|
||||
if permission not in ('read', 'write'):
|
||||
continue
|
||||
if not isinstance(principal_id, str) or not principal_id:
|
||||
continue
|
||||
if principal_type == PRINCIPAL_TYPE_ANYONE and (
|
||||
principal_id != WILDCARD_PRINCIPAL_ID or permission != 'read'
|
||||
):
|
||||
continue
|
||||
|
||||
key = (principal_type, principal_id, permission)
|
||||
deduped[key] = {
|
||||
|
|
@ -186,7 +195,11 @@ def has_public_read_access_grant(access_grants: Optional[list]) -> bool:
|
|||
Returns True when a direct grant list includes wildcard public-read.
|
||||
"""
|
||||
for grant in normalize_access_grants(access_grants):
|
||||
if grant['principal_type'] == 'user' and grant['principal_id'] == '*' and grant['permission'] == 'read':
|
||||
if (
|
||||
grant['principal_type'] == PRINCIPAL_TYPE_USER
|
||||
and grant['principal_id'] == WILDCARD_PRINCIPAL_ID
|
||||
and grant['permission'] == 'read'
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -196,7 +209,25 @@ def has_public_write_access_grant(access_grants: Optional[list]) -> bool:
|
|||
Returns True when a direct grant list includes wildcard public-write.
|
||||
"""
|
||||
for grant in normalize_access_grants(access_grants):
|
||||
if grant['principal_type'] == 'user' and grant['principal_id'] == '*' and grant['permission'] == 'write':
|
||||
if (
|
||||
grant['principal_type'] == PRINCIPAL_TYPE_USER
|
||||
and grant['principal_id'] == WILDCARD_PRINCIPAL_ID
|
||||
and grant['permission'] == 'write'
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def has_anyone_read_access_grant(access_grants: Optional[list]) -> bool:
|
||||
"""
|
||||
Returns True when a direct grant list includes no-auth anyone-read.
|
||||
"""
|
||||
for grant in normalize_access_grants(access_grants):
|
||||
if (
|
||||
grant['principal_type'] == PRINCIPAL_TYPE_ANYONE
|
||||
and grant['principal_id'] == WILDCARD_PRINCIPAL_ID
|
||||
and grant['permission'] == 'read'
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -206,7 +237,7 @@ def has_user_access_grant(access_grants: Optional[list]) -> bool:
|
|||
Returns True when a direct grant list includes any non-wildcard user grant.
|
||||
"""
|
||||
for grant in normalize_access_grants(access_grants):
|
||||
if grant['principal_type'] == 'user' and grant['principal_id'] != '*':
|
||||
if grant['principal_type'] == PRINCIPAL_TYPE_USER and grant['principal_id'] != WILDCARD_PRINCIPAL_ID:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -223,12 +254,27 @@ def strip_user_access_grants(access_grants: Optional[list]) -> list:
|
|||
for grant in access_grants
|
||||
if not (
|
||||
(grant.get('principal_type') if isinstance(grant, dict) else getattr(grant, 'principal_type', None))
|
||||
== 'user'
|
||||
and (grant.get('principal_id') if isinstance(grant, dict) else getattr(grant, 'principal_id', None)) != '*'
|
||||
== PRINCIPAL_TYPE_USER
|
||||
and (grant.get('principal_id') if isinstance(grant, dict) else getattr(grant, 'principal_id', None))
|
||||
!= WILDCARD_PRINCIPAL_ID
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def strip_anyone_access_grants(access_grants: Optional[list]) -> list:
|
||||
"""
|
||||
Remove no-auth anyone grants from the list.
|
||||
"""
|
||||
if not access_grants:
|
||||
return []
|
||||
return [
|
||||
grant
|
||||
for grant in access_grants
|
||||
if (grant.get('principal_type') if isinstance(grant, dict) else getattr(grant, 'principal_type', None))
|
||||
!= PRINCIPAL_TYPE_ANYONE
|
||||
]
|
||||
|
||||
|
||||
def grants_to_access_control(grants: list) -> Optional[dict]:
|
||||
"""
|
||||
Convert a list of grant objects (AccessGrantModel or AccessGrantResponse)
|
||||
|
|
@ -493,6 +539,28 @@ class AccessGrantsTable:
|
|||
result_dict[g.resource_id].append(AccessGrantModel.model_validate(g))
|
||||
return result_dict
|
||||
|
||||
async def has_anyone_access(
|
||||
self,
|
||||
resource_type: str,
|
||||
resource_id: str,
|
||||
permission: str = 'read',
|
||||
db: Optional[AsyncSession] = None,
|
||||
) -> bool:
|
||||
"""Check for a no-auth anyone:* grant. Callers must opt in explicitly."""
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(AccessGrant)
|
||||
.filter(
|
||||
AccessGrant.resource_type == resource_type,
|
||||
AccessGrant.resource_id == resource_id,
|
||||
AccessGrant.principal_type == PRINCIPAL_TYPE_ANYONE,
|
||||
AccessGrant.principal_id == WILDCARD_PRINCIPAL_ID,
|
||||
AccessGrant.permission == permission,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
return result.scalars().first() is not None
|
||||
|
||||
async def has_access(
|
||||
self,
|
||||
user_id: str,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ import logging
|
|||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, Response, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
from fastapi.security import HTTPAuthorizationCredentials
|
||||
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.events import EVENTS, publish_event
|
||||
|
|
@ -37,7 +38,7 @@ from open_webui.socket.main import get_event_emitter
|
|||
from open_webui.tasks import has_active_tasks, stop_item_tasks
|
||||
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
|
||||
from open_webui.utils.access_control.folders import has_folder_access
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.auth import bearer_security, get_admin_user, get_current_user, get_verified_user
|
||||
from open_webui.utils.chat_fork import build_fork_history
|
||||
from open_webui.utils.context_compaction import compact_chat_branch, get_chat_context_usage
|
||||
from open_webui.utils.misc import get_message_list
|
||||
|
|
@ -60,6 +61,47 @@ CHAT_CONFIG_KEYS = {
|
|||
}
|
||||
|
||||
|
||||
async def get_optional_verified_user(
|
||||
request: Request,
|
||||
response: Response,
|
||||
background_tasks: BackgroundTasks,
|
||||
auth_token: HTTPAuthorizationCredentials | None = Depends(bearer_security),
|
||||
):
|
||||
try:
|
||||
user = await get_current_user(request, response, background_tasks, auth_token)
|
||||
except HTTPException:
|
||||
return None
|
||||
|
||||
if user.role not in {'user', 'admin'}:
|
||||
return None
|
||||
return user
|
||||
|
||||
|
||||
async def is_open_shared_chat(shared, db: AsyncSession) -> bool:
|
||||
return await AccessGrants.has_anyone_access(
|
||||
resource_type='shared_chat',
|
||||
resource_id=shared.chat_id,
|
||||
permission='read',
|
||||
db=db,
|
||||
)
|
||||
|
||||
|
||||
async def can_read_shared_chat(user, shared, db: AsyncSession) -> bool:
|
||||
if user.role == 'pending':
|
||||
return False
|
||||
if user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
|
||||
return True
|
||||
if shared.user_id == user.id:
|
||||
return True
|
||||
return await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='shared_chat',
|
||||
resource_id=shared.chat_id,
|
||||
permission='read',
|
||||
db=db,
|
||||
)
|
||||
|
||||
|
||||
async def add_active_state_to_chat_list(
|
||||
request: Request, chat_list: list[ChatTitleIdResponse]
|
||||
) -> list[ChatTitleIdResponse]:
|
||||
|
|
@ -1082,38 +1124,30 @@ async def get_shared_session_user_chat_list(
|
|||
|
||||
@router.get('/share/{share_id}', response_model=ChatResponse | None)
|
||||
async def get_shared_chat_by_id(
|
||||
share_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
|
||||
share_id: str, user=Depends(get_optional_verified_user), db: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
if user.role == 'pending':
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
shared = await SharedChats.get_by_id(share_id, db=db)
|
||||
if shared:
|
||||
if await is_open_shared_chat(shared, db=db) or (
|
||||
user is not None and await can_read_shared_chat(user, shared, db=db)
|
||||
):
|
||||
chat = await Chats.get_chat_by_share_id(share_id, db=db)
|
||||
if chat:
|
||||
return ChatResponse(**chat.model_dump())
|
||||
|
||||
chat = await Chats.get_chat_by_share_id(share_id, db=db)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED if user else ERROR_MESSAGES.INVALID_TOKEN,
|
||||
)
|
||||
|
||||
# Fallback: admins can also access any chat directly by chat ID
|
||||
if not chat and user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
|
||||
chat = None
|
||||
if user is not None and user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
|
||||
chat = await Chats.get_chat_by_id(share_id, db=db)
|
||||
if chat:
|
||||
return ChatResponse(**chat.model_dump())
|
||||
|
||||
if not chat:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
# Look up the original chat_id to check access grants (admins bypass)
|
||||
if user.role != 'admin' or not ENABLE_ADMIN_CHAT_ACCESS:
|
||||
shared = await SharedChats.get_by_id(share_id, db=db)
|
||||
if shared and shared.user_id != user.id:
|
||||
has_grant = await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='shared_chat',
|
||||
resource_id=shared.chat_id,
|
||||
permission='read',
|
||||
db=db,
|
||||
)
|
||||
if not has_grant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
return ChatResponse(**chat.model_dump())
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
|
||||
############################
|
||||
|
|
@ -1753,7 +1787,7 @@ async def clone_shared_chat_by_id(
|
|||
# Enforce access grants (owner and admins bypass)
|
||||
shared = await SharedChats.get_by_id(id, db=db)
|
||||
if shared and user.role != 'admin' and shared.user_id != user.id:
|
||||
has_grant = await AccessGrants.has_access(
|
||||
has_grant = await is_open_shared_chat(shared, db=db) or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='shared_chat',
|
||||
resource_id=shared.chat_id,
|
||||
|
|
@ -1948,6 +1982,8 @@ async def update_shared_chat_access_by_id(
|
|||
user.role,
|
||||
form_data.access_grants,
|
||||
'sharing.public_chats',
|
||||
'sharing.open_chats',
|
||||
db=db,
|
||||
)
|
||||
|
||||
await AccessGrants.set_access_grants('shared_chat', id, form_data.access_grants, db=db)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ from typing import Any
|
|||
|
||||
from open_webui.config import DEFAULT_USER_PERMISSIONS
|
||||
from open_webui.models.access_grants import (
|
||||
has_anyone_read_access_grant,
|
||||
has_public_read_access_grant,
|
||||
has_public_write_access_grant,
|
||||
has_user_access_grant,
|
||||
strip_anyone_access_grants,
|
||||
strip_user_access_grants,
|
||||
)
|
||||
from open_webui.models.groups import Groups
|
||||
|
|
@ -215,13 +217,31 @@ async def filter_allowed_access_grants(
|
|||
user_role: str,
|
||||
access_grants: list,
|
||||
public_permission_key: str,
|
||||
anyone_permission_key: str | None = None,
|
||||
db: AsyncSession | None = None,
|
||||
) -> list:
|
||||
"""
|
||||
Checks if the user has the required permissions to grant access to a resource.
|
||||
Returns the filtered list of access grants if permissions are missing.
|
||||
"""
|
||||
if user_role == 'admin' or not access_grants:
|
||||
if not access_grants:
|
||||
return access_grants
|
||||
|
||||
if has_anyone_read_access_grant(access_grants) and (
|
||||
not anyone_permission_key
|
||||
or (
|
||||
user_role != 'admin'
|
||||
and not await has_permission(
|
||||
user_id,
|
||||
anyone_permission_key,
|
||||
default_permissions,
|
||||
db=db,
|
||||
)
|
||||
)
|
||||
):
|
||||
access_grants = strip_anyone_access_grants(access_grants)
|
||||
|
||||
if user_role == 'admin':
|
||||
return access_grants
|
||||
|
||||
# Check if user can share publicly
|
||||
|
|
|
|||
|
|
@ -494,6 +494,25 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex w-full justify-between my-1">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Chats Open Sharing')}
|
||||
</div>
|
||||
<Switch
|
||||
bind:state={permissions.sharing.open_chats}
|
||||
ariaLabel={$i18n.t('Chats Open Sharing')}
|
||||
/>
|
||||
</div>
|
||||
{#if defaultPermissions?.sharing?.open_chats && !permissions.sharing.open_chats}
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t('This is a default user permission and will remain enabled.')}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if permissions.features.calendar}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
bind:accessGrants
|
||||
accessRoles={['read']}
|
||||
sharePublic={$user?.permissions?.sharing?.public_chats || $user?.role === 'admin'}
|
||||
shareOpen={$user?.permissions?.sharing?.open_chats || $user?.role === 'admin'}
|
||||
shareUsers={($user?.permissions?.access_grants?.allow_users ?? true) ||
|
||||
$user?.role === 'admin'}
|
||||
onChange={saveAccessGrants}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
type AccessGrant = {
|
||||
id?: string;
|
||||
principal_type: 'user' | 'group';
|
||||
principal_type: 'user' | 'group' | 'anyone';
|
||||
principal_id: string;
|
||||
permission: 'read' | 'write';
|
||||
};
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
export let share = true;
|
||||
export let sharePublic = true;
|
||||
export let shareOpen = false;
|
||||
export let shareUsers = true;
|
||||
export let allowGroups = true;
|
||||
export let defaultPermission: 'read' | 'write' = 'read';
|
||||
|
|
@ -162,6 +163,14 @@
|
|||
grant.principal_type === 'user' && grant.principal_id === '*' && grant.permission === 'read'
|
||||
);
|
||||
|
||||
const hasAnyoneReadGrant = (grants: AccessGrant[]): boolean =>
|
||||
grants.some(
|
||||
(grant) =>
|
||||
grant.principal_type === 'anyone' &&
|
||||
grant.principal_id === '*' &&
|
||||
grant.permission === 'read'
|
||||
);
|
||||
|
||||
const hasPublicWriteGrant = (grants: AccessGrant[]): boolean =>
|
||||
grants.some(
|
||||
(grant) =>
|
||||
|
|
@ -170,16 +179,17 @@
|
|||
grant.permission === 'write'
|
||||
);
|
||||
|
||||
const currentGrants = (): AccessGrant[] =>
|
||||
Array.isArray(accessGrants) ? (accessGrants as AccessGrant[]) : [];
|
||||
const currentGrants = (grants: AccessGrant[] | any = accessGrants): AccessGrant[] =>
|
||||
Array.isArray(grants) ? (grants as AccessGrant[]) : [];
|
||||
|
||||
const getPrincipalIdsByPermission = (
|
||||
principalType: 'user' | 'group',
|
||||
permission: 'read' | 'write'
|
||||
permission: 'read' | 'write',
|
||||
grants: AccessGrant[] | any = accessGrants
|
||||
): string[] =>
|
||||
Array.from(
|
||||
new Set(
|
||||
currentGrants()
|
||||
currentGrants(grants)
|
||||
.filter(
|
||||
(grant) => grant.principal_type === principalType && grant.permission === permission
|
||||
)
|
||||
|
|
@ -188,7 +198,7 @@
|
|||
);
|
||||
|
||||
const hasPrincipalGrant = (
|
||||
principalType: 'user' | 'group',
|
||||
principalType: 'user' | 'group' | 'anyone',
|
||||
principalId: string,
|
||||
permission: 'read' | 'write'
|
||||
): boolean =>
|
||||
|
|
@ -204,17 +214,32 @@
|
|||
onChange(accessGrants);
|
||||
};
|
||||
|
||||
const setPublic = (isPublic: boolean) => {
|
||||
// Remove all user:* grants
|
||||
const getVisibility = (grants: AccessGrant[]): 'private' | 'public' | 'open' => {
|
||||
if (hasAnyoneReadGrant(grants)) return 'open';
|
||||
if (hasPublicReadGrant(grants)) return 'public';
|
||||
return 'private';
|
||||
};
|
||||
|
||||
const setVisibility = (visibility: 'private' | 'public' | 'open') => {
|
||||
const filtered = currentGrants().filter(
|
||||
(grant) => !(grant.principal_type === 'user' && grant.principal_id === '*')
|
||||
(grant) =>
|
||||
!(
|
||||
(grant.principal_type === 'user' || grant.principal_type === 'anyone') &&
|
||||
grant.principal_id === '*'
|
||||
)
|
||||
);
|
||||
if (isPublic) {
|
||||
if (visibility === 'public') {
|
||||
filtered.push({
|
||||
principal_type: 'user',
|
||||
principal_id: '*',
|
||||
permission: 'read'
|
||||
});
|
||||
} else if (visibility === 'open') {
|
||||
filtered.push({
|
||||
principal_type: 'anyone',
|
||||
principal_id: '*',
|
||||
permission: 'read'
|
||||
});
|
||||
}
|
||||
commitAccessGrants(filtered);
|
||||
};
|
||||
|
|
@ -237,7 +262,7 @@
|
|||
};
|
||||
|
||||
const upsertPrincipalGrant = (
|
||||
principalType: 'user' | 'group',
|
||||
principalType: 'user' | 'group' | 'anyone',
|
||||
principalId: string,
|
||||
permission: 'read' | 'write',
|
||||
grants: AccessGrant[]
|
||||
|
|
@ -263,7 +288,7 @@
|
|||
};
|
||||
|
||||
const removePrincipalGrant = (
|
||||
principalType: 'user' | 'group',
|
||||
principalType: 'user' | 'group' | 'anyone',
|
||||
principalId: string,
|
||||
permission: 'read' | 'write',
|
||||
grants: AccessGrant[]
|
||||
|
|
@ -277,14 +302,14 @@
|
|||
)
|
||||
);
|
||||
|
||||
const removePrincipal = (principalType: 'user' | 'group', principalId: string) => {
|
||||
const removePrincipal = (principalType: 'user' | 'group' | 'anyone', principalId: string) => {
|
||||
let next = [...currentGrants()];
|
||||
next = removePrincipalGrant(principalType, principalId, 'read', next);
|
||||
next = removePrincipalGrant(principalType, principalId, 'write', next);
|
||||
commitAccessGrants(next);
|
||||
};
|
||||
|
||||
const togglePrincipalWrite = (principalType: 'user' | 'group', principalId: string) => {
|
||||
const togglePrincipalWrite = (principalType: 'user' | 'group' | 'anyone', principalId: string) => {
|
||||
let next = [...currentGrants()];
|
||||
const hasWrite = hasPrincipalGrant(principalType, principalId, 'write');
|
||||
if (hasWrite) {
|
||||
|
|
@ -379,12 +404,14 @@
|
|||
$: if (readGroupIds.length > 0 || writeGroupIds.length > 0) {
|
||||
void ensureGroupsByIds([...readGroupIds, ...writeGroupIds]);
|
||||
}
|
||||
$: readGroupIds = (accessGrants, getPrincipalIdsByPermission('group', 'read'));
|
||||
$: writeGroupIds = (accessGrants, getPrincipalIdsByPermission('group', 'write'));
|
||||
$: readUserIds =
|
||||
(accessGrants, getPrincipalIdsByPermission('user', 'read').filter((id) => id !== '*'));
|
||||
$: writeUserIds =
|
||||
(accessGrants, getPrincipalIdsByPermission('user', 'write').filter((id) => id !== '*'));
|
||||
$: readGroupIds = getPrincipalIdsByPermission('group', 'read', accessGrants);
|
||||
$: writeGroupIds = getPrincipalIdsByPermission('group', 'write', accessGrants);
|
||||
$: readUserIds = getPrincipalIdsByPermission('user', 'read', accessGrants).filter(
|
||||
(id) => id !== '*'
|
||||
);
|
||||
$: writeUserIds = getPrincipalIdsByPermission('user', 'write', accessGrants).filter(
|
||||
(id) => id !== '*'
|
||||
);
|
||||
|
||||
$: selectedUserIds = Array.from(new Set([...readUserIds, ...writeUserIds]));
|
||||
|
||||
|
|
@ -450,7 +477,7 @@
|
|||
<div class="flex gap-2 items-center">
|
||||
<div>
|
||||
<div class="p-2 bg-black/5 dark:bg-white/5 rounded-full">
|
||||
{#if !hasPublicReadGrant(accessGrants ?? [])}
|
||||
{#if getVisibility(accessGrants ?? []) === 'private'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
|
|
@ -486,36 +513,41 @@
|
|||
|
||||
<div>
|
||||
<Tooltip
|
||||
content={!(share && sharePublic) && !hasPublicReadGrant(accessGrants ?? [])
|
||||
content={!(share && sharePublic) && getVisibility(accessGrants ?? []) === 'private'
|
||||
? $i18n.t('You do not have permission to make this public')
|
||||
: ''}
|
||||
>
|
||||
<select
|
||||
id="models"
|
||||
class="outline-none bg-transparent text-sm font-normal block w-fit pr-8 max-w-full placeholder-gray-400"
|
||||
value={!hasPublicReadGrant(accessGrants ?? []) ? 'private' : 'public'}
|
||||
value={getVisibility(accessGrants ?? [])}
|
||||
on:change={(e) => {
|
||||
setPublic((e.target as HTMLSelectElement).value === 'public');
|
||||
setVisibility((e.target as HTMLSelectElement).value as 'private' | 'public' | 'open');
|
||||
}}
|
||||
>
|
||||
<option class=" text-gray-700" value="private">{$i18n.t('Private')}</option>
|
||||
{#if (share && sharePublic) || hasPublicReadGrant(accessGrants ?? [])}
|
||||
<option class=" text-gray-700" value="public">{$i18n.t('Public')}</option>
|
||||
{/if}
|
||||
{#if (share && shareOpen) || hasAnyoneReadGrant(accessGrants ?? [])}
|
||||
<option class=" text-gray-700" value="open">{$i18n.t('Open')}</option>
|
||||
{/if}
|
||||
</select>
|
||||
</Tooltip>
|
||||
|
||||
<div class=" text-xs text-gray-400 font-normal">
|
||||
{#if !hasPublicReadGrant(accessGrants ?? [])}
|
||||
{#if getVisibility(accessGrants ?? []) === 'private'}
|
||||
{$i18n.t('Only select users and groups with permission can access')}
|
||||
{:else}
|
||||
{:else if getVisibility(accessGrants ?? []) === 'public'}
|
||||
{$i18n.t('Accessible to all users')}
|
||||
{:else}
|
||||
{$i18n.t('Anyone with the link can view')}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if hasPublicReadGrant(accessGrants ?? []) && accessRoles.includes('write')}
|
||||
{#if hasPublicReadGrant(accessGrants ?? []) && !hasAnyoneReadGrant(accessGrants ?? []) && accessRoles.includes('write')}
|
||||
<div class="flex w-full justify-between mt-1.5 ml-0.5">
|
||||
<div class="self-center text-xs">
|
||||
{$i18n.t('Allow public write access')}
|
||||
|
|
@ -660,7 +692,7 @@
|
|||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if !hasPublicReadGrant(accessGrants ?? []) && accessGroups.length === 0 && selectedUsers.length === 0}
|
||||
{#if getVisibility(accessGrants ?? []) === 'private' && accessGroups.length === 0 && selectedUsers.length === 0}
|
||||
<div class="text-xs text-gray-500 text-center py-3">
|
||||
{$i18n.t('No access grants. Private to you.')}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
type AccessGrant = {
|
||||
id?: string;
|
||||
principal_type: 'user' | 'group';
|
||||
principal_type: 'user' | 'group' | 'anyone';
|
||||
principal_id: string;
|
||||
permission: 'read' | 'write';
|
||||
};
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
export let share = true;
|
||||
export let sharePublic = true;
|
||||
export let shareOpen = false;
|
||||
export let shareUsers = true;
|
||||
|
||||
export let onChange = () => {};
|
||||
|
|
@ -49,6 +50,7 @@
|
|||
{accessRoles}
|
||||
{share}
|
||||
{sharePublic}
|
||||
{shareOpen}
|
||||
{shareUsers}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export const DEFAULT_PERMISSIONS = {
|
|||
public_notes: false,
|
||||
folders: false,
|
||||
public_chats: false,
|
||||
open_chats: false,
|
||||
public_calendars: false
|
||||
},
|
||||
access_grants: {
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@
|
|||
"and create a new shared link.": "",
|
||||
"Android": "",
|
||||
"Anyone": "",
|
||||
"Anyone with the link can view": "",
|
||||
"API Auth String": "",
|
||||
"API Base URL": "",
|
||||
"API Key": "",
|
||||
|
|
@ -432,6 +433,7 @@
|
|||
"Chat unshared successfully.": "",
|
||||
"chats": "",
|
||||
"Chats": "",
|
||||
"Chats Open Sharing": "",
|
||||
"Chats Public Sharing": "",
|
||||
"Check Again": "",
|
||||
"Check for updates": "",
|
||||
|
|
@ -1908,6 +1910,7 @@
|
|||
"Open Modal To Manage Floating Quick Actions": "",
|
||||
"Open Modal To Manage Image Compression": "",
|
||||
"Open Model Selector": "",
|
||||
"Open": "",
|
||||
"Open note": "",
|
||||
"Open Settings": "",
|
||||
"Open Sidebar": "",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@
|
|||
};
|
||||
|
||||
$: messages = createMessagesList(history, history.currentId);
|
||||
$: canClone =
|
||||
$sessionUser && ($sessionUser.role === 'admin' || ($sessionUser.permissions?.chat?.import ?? true));
|
||||
|
||||
$: if ($page.params.id) {
|
||||
(async () => {
|
||||
|
|
@ -60,10 +62,16 @@
|
|||
//////////////////////////
|
||||
|
||||
const loadSharedChat = async () => {
|
||||
const userSettings = await getUserSettings(localStorage.token).catch((error) => {
|
||||
console.error(error);
|
||||
return null;
|
||||
});
|
||||
const token = localStorage.token ?? '';
|
||||
const shareId = $page.params.id;
|
||||
if (!shareId) return null;
|
||||
|
||||
const userSettings = token
|
||||
? await getUserSettings(token).catch((error) => {
|
||||
console.error(error);
|
||||
return null;
|
||||
})
|
||||
: null;
|
||||
|
||||
if (userSettings) {
|
||||
settings.set(userSettings.ui);
|
||||
|
|
@ -80,22 +88,31 @@
|
|||
}
|
||||
|
||||
await models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
token
|
||||
? await getModels(
|
||||
token,
|
||||
$config?.features?.enable_direct_connections
|
||||
? ($settings?.directConnections ?? null)
|
||||
: null
|
||||
).catch((error) => {
|
||||
console.error(error);
|
||||
return [];
|
||||
})
|
||||
: []
|
||||
);
|
||||
await chatId.set($page.params.id);
|
||||
chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => {
|
||||
await chatId.set(shareId);
|
||||
chat = await getChatByShareId(token, shareId).catch(async (error) => {
|
||||
await goto('/');
|
||||
return null;
|
||||
});
|
||||
|
||||
if (chat) {
|
||||
user = await getUserInfoById(localStorage.token, chat.user_id).catch((error) => {
|
||||
console.error(error);
|
||||
return null;
|
||||
});
|
||||
user = token
|
||||
? await getUserInfoById(token, chat.user_id).catch((error) => {
|
||||
console.error(error);
|
||||
return null;
|
||||
})
|
||||
: null;
|
||||
|
||||
const chatContent = chat.chat;
|
||||
|
||||
|
|
@ -128,7 +145,7 @@
|
|||
};
|
||||
|
||||
const cloneSharedChat = async () => {
|
||||
if (!($sessionUser?.role === 'admin' || ($sessionUser?.permissions?.chat?.import ?? true))) {
|
||||
if (!canClone) {
|
||||
toast.error($i18n.t('Access prohibited'));
|
||||
return;
|
||||
}
|
||||
|
|
@ -152,6 +169,7 @@
|
|||
? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} / ${$WEBUI_NAME}`
|
||||
: `${$WEBUI_NAME}`}
|
||||
</title>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
</svelte:head>
|
||||
|
||||
{#if loaded}
|
||||
|
|
@ -202,7 +220,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{#if $sessionUser?.role === 'admin' || ($sessionUser?.permissions?.chat?.import ?? true)}
|
||||
{#if canClone}
|
||||
<div
|
||||
class="absolute bottom-0 right-0 left-0 flex justify-center w-full bg-linear-to-b from-transparent to-white dark:to-gray-900"
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue