mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 04:15:25 +00:00
enh: channel read/write perm
This commit is contained in:
parent
6d69ea3ac7
commit
ac879513e5
8 changed files with 58 additions and 14 deletions
|
|
@ -57,6 +57,10 @@ class ChannelModel(BaseModel):
|
||||||
####################
|
####################
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelResponse(ChannelModel):
|
||||||
|
write_access: bool = False
|
||||||
|
|
||||||
|
|
||||||
class ChannelForm(BaseModel):
|
class ChannelForm(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,13 @@ from pydantic import BaseModel
|
||||||
from open_webui.socket.main import sio, get_user_ids_from_room
|
from open_webui.socket.main import sio, get_user_ids_from_room
|
||||||
from open_webui.models.users import Users, UserNameResponse
|
from open_webui.models.users import Users, UserNameResponse
|
||||||
|
|
||||||
from open_webui.models.channels import Channels, ChannelModel, ChannelForm
|
from open_webui.models.groups import Groups
|
||||||
|
from open_webui.models.channels import (
|
||||||
|
Channels,
|
||||||
|
ChannelModel,
|
||||||
|
ChannelForm,
|
||||||
|
ChannelResponse,
|
||||||
|
)
|
||||||
from open_webui.models.messages import (
|
from open_webui.models.messages import (
|
||||||
Messages,
|
Messages,
|
||||||
MessageModel,
|
MessageModel,
|
||||||
|
|
@ -80,7 +86,7 @@ async def create_new_channel(form_data: ChannelForm, user=Depends(get_admin_user
|
||||||
############################
|
############################
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id}", response_model=Optional[ChannelModel])
|
@router.get("/{id}", response_model=Optional[ChannelResponse])
|
||||||
async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
|
async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
|
||||||
channel = Channels.get_channel_by_id(id)
|
channel = Channels.get_channel_by_id(id)
|
||||||
if not channel:
|
if not channel:
|
||||||
|
|
@ -95,7 +101,16 @@ async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
)
|
)
|
||||||
|
|
||||||
return ChannelModel(**channel.model_dump())
|
write_access = has_access(
|
||||||
|
user.id, type="write", access_control=channel.access_control, strict=False
|
||||||
|
)
|
||||||
|
|
||||||
|
return ChannelResponse(
|
||||||
|
**{
|
||||||
|
**channel.model_dump(),
|
||||||
|
"write_access": write_access or user.role == "admin",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
|
|
@ -362,7 +377,7 @@ async def new_message_handler(
|
||||||
)
|
)
|
||||||
|
|
||||||
if user.role != "admin" and not has_access(
|
if user.role != "admin" and not has_access(
|
||||||
user.id, type="read", access_control=channel.access_control
|
user.id, type="write", access_control=channel.access_control, strict=False
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
|
@ -658,7 +673,7 @@ async def add_reaction_to_message(
|
||||||
)
|
)
|
||||||
|
|
||||||
if user.role != "admin" and not has_access(
|
if user.role != "admin" and not has_access(
|
||||||
user.id, type="read", access_control=channel.access_control
|
user.id, type="write", access_control=channel.access_control, strict=False
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
|
@ -724,7 +739,7 @@ async def remove_reaction_by_id_and_user_id_and_name(
|
||||||
)
|
)
|
||||||
|
|
||||||
if user.role != "admin" and not has_access(
|
if user.role != "admin" and not has_access(
|
||||||
user.id, type="read", access_control=channel.access_control
|
user.id, type="write", access_control=channel.access_control, strict=False
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
|
@ -806,7 +821,9 @@ async def delete_message_by_id(
|
||||||
if (
|
if (
|
||||||
user.role != "admin"
|
user.role != "admin"
|
||||||
and message.user_id != user.id
|
and message.user_id != user.id
|
||||||
and not has_access(user.id, type="read", access_control=channel.access_control)
|
and not has_access(
|
||||||
|
user.id, type="write", access_control=channel.access_control, strict=False
|
||||||
|
)
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
|
|
||||||
|
|
@ -110,9 +110,13 @@ def has_access(
|
||||||
type: str = "write",
|
type: str = "write",
|
||||||
access_control: Optional[dict] = None,
|
access_control: Optional[dict] = None,
|
||||||
user_group_ids: Optional[Set[str]] = None,
|
user_group_ids: Optional[Set[str]] = None,
|
||||||
|
strict: bool = True,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if access_control is None:
|
if access_control is None:
|
||||||
return type == "read"
|
if strict:
|
||||||
|
return type == "read"
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
if user_group_ids is None:
|
if user_group_ids is None:
|
||||||
user_groups = Groups.get_groups_by_member_id(user_id)
|
user_groups = Groups.get_groups_by_member_id(user_id)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
import Drawer from '../common/Drawer.svelte';
|
import Drawer from '../common/Drawer.svelte';
|
||||||
import EllipsisVertical from '../icons/EllipsisVertical.svelte';
|
import EllipsisVertical from '../icons/EllipsisVertical.svelte';
|
||||||
import Thread from './Thread.svelte';
|
import Thread from './Thread.svelte';
|
||||||
|
import i18n from '$lib/i18n';
|
||||||
|
|
||||||
export let id = '';
|
export let id = '';
|
||||||
|
|
||||||
|
|
@ -252,6 +253,10 @@
|
||||||
{typingUsers}
|
{typingUsers}
|
||||||
userSuggestions={true}
|
userSuggestions={true}
|
||||||
channelSuggestions={true}
|
channelSuggestions={true}
|
||||||
|
disabled={!channel?.write_access}
|
||||||
|
placeholder={!channel?.write_access
|
||||||
|
? $i18n.t('You do not have permission to send messages in this channel.')
|
||||||
|
: $i18n.t('Type here...')}
|
||||||
{onChange}
|
{onChange}
|
||||||
onSubmit={submitHandler}
|
onSubmit={submitHandler}
|
||||||
{scrollToBottom}
|
{scrollToBottom}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
import MentionList from './MessageInput/MentionList.svelte';
|
import MentionList from './MessageInput/MentionList.svelte';
|
||||||
import Skeleton from '../chat/Messages/Skeleton.svelte';
|
import Skeleton from '../chat/Messages/Skeleton.svelte';
|
||||||
|
|
||||||
export let placeholder = $i18n.t('Send a Message');
|
export let placeholder = $i18n.t('Type here...');
|
||||||
|
|
||||||
export let id = null;
|
export let id = null;
|
||||||
export let chatInputElement;
|
export let chatInputElement;
|
||||||
|
|
@ -53,6 +53,7 @@
|
||||||
export let scrollEnd = true;
|
export let scrollEnd = true;
|
||||||
export let scrollToBottom: Function = () => {};
|
export let scrollToBottom: Function = () => {};
|
||||||
|
|
||||||
|
export let disabled = false;
|
||||||
export let acceptFiles = true;
|
export let acceptFiles = true;
|
||||||
export let showFormattingToolbar = true;
|
export let showFormattingToolbar = true;
|
||||||
|
|
||||||
|
|
@ -731,7 +732,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="">
|
<div
|
||||||
|
class="{disabled ? 'opacity-50 pointer-events-none cursor-not-allowed' : ''} relative z-20"
|
||||||
|
>
|
||||||
{#if recording}
|
{#if recording}
|
||||||
<VoiceRecording
|
<VoiceRecording
|
||||||
bind:recording
|
bind:recording
|
||||||
|
|
@ -836,6 +839,8 @@
|
||||||
bind:this={chatInputElement}
|
bind:this={chatInputElement}
|
||||||
json={true}
|
json={true}
|
||||||
messageInput={true}
|
messageInput={true}
|
||||||
|
editable={!disabled}
|
||||||
|
{placeholder}
|
||||||
richText={$settings?.richTextInput ?? true}
|
richText={$settings?.richTextInput ?? true}
|
||||||
showFormattingToolbar={$settings?.showFormattingToolbar ?? false}
|
showFormattingToolbar={$settings?.showFormattingToolbar ?? false}
|
||||||
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
|
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,10 @@
|
||||||
<div class=" pb-[1rem] px-2.5 w-full">
|
<div class=" pb-[1rem] px-2.5 w-full">
|
||||||
<MessageInput
|
<MessageInput
|
||||||
id={threadId}
|
id={threadId}
|
||||||
|
disabled={!channel?.write_access}
|
||||||
|
placeholder={!channel?.write_access
|
||||||
|
? $i18n.t('You do not have permission to send messages in this thread.')
|
||||||
|
: $i18n.t('Reply to thread...')}
|
||||||
typingUsersClassName="from-gray-50 dark:from-gray-850"
|
typingUsersClassName="from-gray-50 dark:from-gray-850"
|
||||||
{typingUsers}
|
{typingUsers}
|
||||||
userSuggestions={true}
|
userSuggestions={true}
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@
|
||||||
export let documentId = '';
|
export let documentId = '';
|
||||||
|
|
||||||
export let className = 'input-prose';
|
export let className = 'input-prose';
|
||||||
export let placeholder = 'Type here...';
|
export let placeholder = $i18n.t('Type here...');
|
||||||
let _placeholder = placeholder;
|
let _placeholder = placeholder;
|
||||||
|
|
||||||
$: if (placeholder !== _placeholder) {
|
$: if (placeholder !== _placeholder) {
|
||||||
|
|
@ -689,7 +689,7 @@
|
||||||
link: link
|
link: link
|
||||||
}),
|
}),
|
||||||
...(dragHandle ? [ListItemDragHandle] : []),
|
...(dragHandle ? [ListItemDragHandle] : []),
|
||||||
Placeholder.configure({ placeholder: () => _placeholder }),
|
Placeholder.configure({ placeholder: () => _placeholder, showOnlyWhenEditable: false }),
|
||||||
SelectionDecoration,
|
SelectionDecoration,
|
||||||
|
|
||||||
...(richText
|
...(richText
|
||||||
|
|
@ -1123,4 +1123,9 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div bind:this={element} class="relative w-full min-w-full h-full min-h-fit {className}" />
|
<div
|
||||||
|
bind:this={element}
|
||||||
|
class="relative w-full min-w-full h-full min-h-fit {className} {!editable
|
||||||
|
? 'cursor-not-allowed'
|
||||||
|
: ''}"
|
||||||
|
/>
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@
|
||||||
|
|
||||||
<div class="my-2 -mx-2">
|
<div class="my-2 -mx-2">
|
||||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
|
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
|
||||||
<AccessControl bind:accessControl />
|
<AccessControl bind:accessControl accessRoles={['read', 'write']} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue