mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	enh/refac: permissions
This commit is contained in:
		
							parent
							
								
									2aa82d98cc
								
							
						
					
					
						commit
						56f57928c2
					
				@ -820,6 +820,10 @@ USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS = (
 | 
			
		||||
    os.environ.get("USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS", "False").lower() == "true"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
USER_PERMISSIONS_CHAT_CONTROLS = (
 | 
			
		||||
    os.environ.get("USER_PERMISSIONS_CHAT_CONTROLS", "True").lower() == "true"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
USER_PERMISSIONS_CHAT_FILE_UPLOAD = (
 | 
			
		||||
    os.environ.get("USER_PERMISSIONS_CHAT_FILE_UPLOAD", "True").lower() == "true"
 | 
			
		||||
)
 | 
			
		||||
@ -836,23 +840,39 @@ USER_PERMISSIONS_CHAT_TEMPORARY = (
 | 
			
		||||
    os.environ.get("USER_PERMISSIONS_CHAT_TEMPORARY", "True").lower() == "true"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
USER_PERMISSIONS_FEATURES_WEB_SEARCH = (
 | 
			
		||||
    os.environ.get("USER_PERMISSIONS_FEATURES_WEB_SEARCH", "True").lower() == "true"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
USER_PERMISSIONS_FEATURES_IMAGE_GENERATION = (
 | 
			
		||||
    os.environ.get("USER_PERMISSIONS_FEATURES_IMAGE_GENERATION", "True").lower()
 | 
			
		||||
    == "true"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEFAULT_USER_PERMISSIONS = {
 | 
			
		||||
    "workspace": {
 | 
			
		||||
        "models": USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS,
 | 
			
		||||
        "knowledge": USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS,
 | 
			
		||||
        "prompts": USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS,
 | 
			
		||||
        "tools": USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS,
 | 
			
		||||
    },
 | 
			
		||||
    "chat": {
 | 
			
		||||
        "controls": USER_PERMISSIONS_CHAT_CONTROLS,
 | 
			
		||||
        "file_upload": USER_PERMISSIONS_CHAT_FILE_UPLOAD,
 | 
			
		||||
        "delete": USER_PERMISSIONS_CHAT_DELETE,
 | 
			
		||||
        "edit": USER_PERMISSIONS_CHAT_EDIT,
 | 
			
		||||
        "temporary": USER_PERMISSIONS_CHAT_TEMPORARY,
 | 
			
		||||
    },
 | 
			
		||||
    "features": {
 | 
			
		||||
        "web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
 | 
			
		||||
        "image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
USER_PERMISSIONS = PersistentConfig(
 | 
			
		||||
    "USER_PERMISSIONS",
 | 
			
		||||
    "user.permissions",
 | 
			
		||||
    {
 | 
			
		||||
        "workspace": {
 | 
			
		||||
            "models": USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS,
 | 
			
		||||
            "knowledge": USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS,
 | 
			
		||||
            "prompts": USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS,
 | 
			
		||||
            "tools": USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS,
 | 
			
		||||
        },
 | 
			
		||||
        "chat": {
 | 
			
		||||
            "file_upload": USER_PERMISSIONS_CHAT_FILE_UPLOAD,
 | 
			
		||||
            "delete": USER_PERMISSIONS_CHAT_DELETE,
 | 
			
		||||
            "edit": USER_PERMISSIONS_CHAT_EDIT,
 | 
			
		||||
            "temporary": USER_PERMISSIONS_CHAT_TEMPORARY,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    DEFAULT_USER_PERMISSIONS,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ENABLE_CHANNELS = PersistentConfig(
 | 
			
		||||
 | 
			
		||||
@ -62,27 +62,44 @@ async def get_user_permissisions(user=Depends(get_verified_user)):
 | 
			
		||||
# User Default Permissions
 | 
			
		||||
############################
 | 
			
		||||
class WorkspacePermissions(BaseModel):
 | 
			
		||||
    models: bool
 | 
			
		||||
    knowledge: bool
 | 
			
		||||
    prompts: bool
 | 
			
		||||
    tools: bool
 | 
			
		||||
    models: bool = False
 | 
			
		||||
    knowledge: bool = False
 | 
			
		||||
    prompts: bool = False
 | 
			
		||||
    tools: bool = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ChatPermissions(BaseModel):
 | 
			
		||||
    file_upload: bool
 | 
			
		||||
    delete: bool
 | 
			
		||||
    edit: bool
 | 
			
		||||
    temporary: bool
 | 
			
		||||
    controls: bool = True
 | 
			
		||||
    file_upload: bool = True
 | 
			
		||||
    delete: bool = True
 | 
			
		||||
    edit: bool = True
 | 
			
		||||
    temporary: bool = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FeaturesPermissions(BaseModel):
 | 
			
		||||
    web_search: bool = True
 | 
			
		||||
    image_generation: bool = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserPermissions(BaseModel):
 | 
			
		||||
    workspace: WorkspacePermissions
 | 
			
		||||
    chat: ChatPermissions
 | 
			
		||||
    features: FeaturesPermissions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.get("/default/permissions")
 | 
			
		||||
@router.get("/default/permissions", response_model=UserPermissions)
 | 
			
		||||
async def get_user_permissions(request: Request, user=Depends(get_admin_user)):
 | 
			
		||||
    return request.app.state.config.USER_PERMISSIONS
 | 
			
		||||
    return {
 | 
			
		||||
        "workspace": WorkspacePermissions(
 | 
			
		||||
            **request.app.state.config.USER_PERMISSIONS.get("workspace", {})
 | 
			
		||||
        ),
 | 
			
		||||
        "chat": ChatPermissions(
 | 
			
		||||
            **request.app.state.config.USER_PERMISSIONS.get("chat", {})
 | 
			
		||||
        ),
 | 
			
		||||
        "features": FeaturesPermissions(
 | 
			
		||||
            **request.app.state.config.USER_PERMISSIONS.get("features", {})
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.post("/default/permissions")
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,30 @@
 | 
			
		||||
from typing import Optional, Union, List, Dict, Any
 | 
			
		||||
from open_webui.models.users import Users, UserModel
 | 
			
		||||
from open_webui.models.groups import Groups
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from open_webui.config import DEFAULT_USER_PERMISSIONS
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fill_missing_permissions(
 | 
			
		||||
    permissions: Dict[str, Any], default_permissions: Dict[str, Any]
 | 
			
		||||
) -> Dict[str, Any]:
 | 
			
		||||
    """
 | 
			
		||||
    Recursively fills in missing properties in the permissions dictionary
 | 
			
		||||
    using the default permissions as a template.
 | 
			
		||||
    """
 | 
			
		||||
    for key, value in default_permissions.items():
 | 
			
		||||
        if key not in permissions:
 | 
			
		||||
            permissions[key] = value
 | 
			
		||||
        elif isinstance(value, dict) and isinstance(
 | 
			
		||||
            permissions[key], dict
 | 
			
		||||
        ):  # Both are nested dictionaries
 | 
			
		||||
            permissions[key] = fill_missing_permissions(permissions[key], value)
 | 
			
		||||
 | 
			
		||||
    return permissions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_permissions(
 | 
			
		||||
    user_id: str,
 | 
			
		||||
    default_permissions: Dict[str, Any],
 | 
			
		||||
@ -27,39 +48,45 @@ def get_permissions(
 | 
			
		||||
                if key not in permissions:
 | 
			
		||||
                    permissions[key] = value
 | 
			
		||||
                else:
 | 
			
		||||
                    permissions[key] = permissions[key] or value
 | 
			
		||||
                    permissions[key] = (
 | 
			
		||||
                        permissions[key] or value
 | 
			
		||||
                    )  # Use the most permissive value (True > False)
 | 
			
		||||
        return permissions
 | 
			
		||||
 | 
			
		||||
    user_groups = Groups.get_groups_by_member_id(user_id)
 | 
			
		||||
 | 
			
		||||
    # deep copy default permissions to avoid modifying the original dict
 | 
			
		||||
    # Deep copy default permissions to avoid modifying the original dict
 | 
			
		||||
    permissions = json.loads(json.dumps(default_permissions))
 | 
			
		||||
 | 
			
		||||
    # Combine permissions from all user groups
 | 
			
		||||
    for group in user_groups:
 | 
			
		||||
        group_permissions = group.permissions
 | 
			
		||||
        permissions = combine_permissions(permissions, group_permissions)
 | 
			
		||||
 | 
			
		||||
    # Ensure all fields from default_permissions are present and filled in
 | 
			
		||||
    permissions = fill_missing_permissions(permissions, default_permissions)
 | 
			
		||||
 | 
			
		||||
    return permissions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def has_permission(
 | 
			
		||||
    user_id: str,
 | 
			
		||||
    permission_key: str,
 | 
			
		||||
    default_permissions: Dict[str, bool] = {},
 | 
			
		||||
    default_permissions: Dict[str, Any] = {},
 | 
			
		||||
) -> bool:
 | 
			
		||||
    """
 | 
			
		||||
    Check if a user has a specific permission by checking the group permissions
 | 
			
		||||
    and falls back to default permissions if not found in any group.
 | 
			
		||||
    and fall back to default permissions if not found in any group.
 | 
			
		||||
 | 
			
		||||
    Permission keys can be hierarchical and separated by dots ('.').
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def get_permission(permissions: Dict[str, bool], keys: List[str]) -> bool:
 | 
			
		||||
    def get_permission(permissions: Dict[str, Any], keys: List[str]) -> bool:
 | 
			
		||||
        """Traverse permissions dict using a list of keys (from dot-split permission_key)."""
 | 
			
		||||
        for key in keys:
 | 
			
		||||
            if key not in permissions:
 | 
			
		||||
                return False  # If any part of the hierarchy is missing, deny access
 | 
			
		||||
            permissions = permissions[key]  # Go one level deeper
 | 
			
		||||
            permissions = permissions[key]  # Traverse one level deeper
 | 
			
		||||
 | 
			
		||||
        return bool(permissions)  # Return the boolean at the final level
 | 
			
		||||
 | 
			
		||||
@ -73,7 +100,10 @@ def has_permission(
 | 
			
		||||
        if get_permission(group_permissions, permission_hierarchy):
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    # Check default permissions afterwards if the group permissions don't allow it
 | 
			
		||||
    # Check default permissions afterward if the group permissions don't allow it
 | 
			
		||||
    default_permissions = fill_missing_permissions(
 | 
			
		||||
        default_permissions, DEFAULT_USER_PERMISSIONS
 | 
			
		||||
    )
 | 
			
		||||
    return get_permission(default_permissions, permission_hierarchy)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -53,10 +53,15 @@
 | 
			
		||||
			tools: false
 | 
			
		||||
		},
 | 
			
		||||
		chat: {
 | 
			
		||||
			controls: true,
 | 
			
		||||
			file_upload: true,
 | 
			
		||||
			delete: true,
 | 
			
		||||
			edit: true,
 | 
			
		||||
			temporary: true
 | 
			
		||||
		},
 | 
			
		||||
		features: {
 | 
			
		||||
			web_search: true,
 | 
			
		||||
			image_generation: true
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -37,10 +37,15 @@
 | 
			
		||||
			tools: false
 | 
			
		||||
		},
 | 
			
		||||
		chat: {
 | 
			
		||||
			controls: true,
 | 
			
		||||
			file_upload: true,
 | 
			
		||||
			delete: true,
 | 
			
		||||
			edit: true,
 | 
			
		||||
			temporary: true
 | 
			
		||||
		},
 | 
			
		||||
		features: {
 | 
			
		||||
			web_search: true,
 | 
			
		||||
			image_generation: true
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	export let userIds = [];
 | 
			
		||||
@ -65,20 +70,8 @@
 | 
			
		||||
		if (group) {
 | 
			
		||||
			name = group.name;
 | 
			
		||||
			description = group.description;
 | 
			
		||||
			permissions = group?.permissions ?? {
 | 
			
		||||
				workspace: {
 | 
			
		||||
					models: false,
 | 
			
		||||
					knowledge: false,
 | 
			
		||||
					prompts: false,
 | 
			
		||||
					tools: false
 | 
			
		||||
				},
 | 
			
		||||
				chat: {
 | 
			
		||||
					file_upload: true,
 | 
			
		||||
					delete: true,
 | 
			
		||||
					edit: true,
 | 
			
		||||
					temporary: true
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			permissions = group?.permissions ?? {};
 | 
			
		||||
 | 
			
		||||
			userIds = group?.user_ids ?? [];
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,12 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	import { getContext, onMount } from 'svelte';
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	import Switch from '$lib/components/common/Switch.svelte';
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
 | 
			
		||||
	export let permissions = {
 | 
			
		||||
	// Default values for permissions
 | 
			
		||||
	const defaultPermissions = {
 | 
			
		||||
		workspace: {
 | 
			
		||||
			models: false,
 | 
			
		||||
			knowledge: false,
 | 
			
		||||
@ -13,12 +14,38 @@
 | 
			
		||||
			tools: false
 | 
			
		||||
		},
 | 
			
		||||
		chat: {
 | 
			
		||||
			controls: true,
 | 
			
		||||
			delete: true,
 | 
			
		||||
			edit: true,
 | 
			
		||||
			temporary: true,
 | 
			
		||||
			file_upload: true
 | 
			
		||||
		},
 | 
			
		||||
		features: {
 | 
			
		||||
			web_search: true,
 | 
			
		||||
			image_generation: true
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export let permissions = {};
 | 
			
		||||
 | 
			
		||||
	// Reactive statement to ensure all fields are present in `permissions`
 | 
			
		||||
	$: {
 | 
			
		||||
		permissions = fillMissingProperties(permissions, defaultPermissions);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function fillMissingProperties(obj: any, defaults: any) {
 | 
			
		||||
		return {
 | 
			
		||||
			...defaults,
 | 
			
		||||
			...obj,
 | 
			
		||||
			workspace: { ...defaults.workspace, ...obj.workspace },
 | 
			
		||||
			chat: { ...defaults.chat, ...obj.chat },
 | 
			
		||||
			features: { ...defaults.features, ...obj.features }
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onMount(() => {
 | 
			
		||||
		permissions = fillMissingProperties(permissions, defaultPermissions);
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
@ -169,6 +196,14 @@
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" mb-2 text-sm font-medium">{$i18n.t('Chat Permissions')}</div>
 | 
			
		||||
 | 
			
		||||
		<div class="  flex w-full justify-between my-2 pr-2">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">
 | 
			
		||||
				{$i18n.t('Allow Chat Controls')}
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<Switch bind:state={permissions.chat.controls} />
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="  flex w-full justify-between my-2 pr-2">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">
 | 
			
		||||
				{$i18n.t('Allow File Upload')}
 | 
			
		||||
@ -201,4 +236,26 @@
 | 
			
		||||
			<Switch bind:state={permissions.chat.temporary} />
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<hr class=" border-gray-50 dark:border-gray-850 my-2" />
 | 
			
		||||
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" mb-2 text-sm font-medium">{$i18n.t('Features Permissions')}</div>
 | 
			
		||||
 | 
			
		||||
		<div class="  flex w-full justify-between my-2 pr-2">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">
 | 
			
		||||
				{$i18n.t('Web Search')}
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<Switch bind:state={permissions.features.web_search} />
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="  flex w-full justify-between my-2 pr-2">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">
 | 
			
		||||
				{$i18n.t('Image Generation')}
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<Switch bind:state={permissions.features.image_generation} />
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user