This commit is contained in:
Timothy Jaeryang Baek
2026-04-01 01:21:21 -05:00
parent 0d3d824274
commit 5a2ff8b2e5
7 changed files with 63 additions and 2 deletions
+5
View File
@@ -1435,6 +1435,10 @@ USER_PERMISSIONS_FEATURES_API_KEYS = os.environ.get('USER_PERMISSIONS_FEATURES_A
USER_PERMISSIONS_FEATURES_MEMORIES = os.environ.get('USER_PERMISSIONS_FEATURES_MEMORIES', 'True').lower() == 'true' USER_PERMISSIONS_FEATURES_MEMORIES = os.environ.get('USER_PERMISSIONS_FEATURES_MEMORIES', 'True').lower() == 'true'
USER_PERMISSIONS_FEATURES_AUTOMATIONS = (
os.environ.get('USER_PERMISSIONS_FEATURES_AUTOMATIONS', 'False').lower() == 'true'
)
USER_PERMISSIONS_SETTINGS_INTERFACE = os.environ.get('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true' USER_PERMISSIONS_SETTINGS_INTERFACE = os.environ.get('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true'
@@ -1504,6 +1508,7 @@ DEFAULT_USER_PERMISSIONS = {
'image_generation': USER_PERMISSIONS_FEATURES_IMAGE_GENERATION, 'image_generation': USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
'code_interpreter': USER_PERMISSIONS_FEATURES_CODE_INTERPRETER, 'code_interpreter': USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
'memories': USER_PERMISSIONS_FEATURES_MEMORIES, 'memories': USER_PERMISSIONS_FEATURES_MEMORIES,
'automations': USER_PERMISSIONS_FEATURES_AUTOMATIONS,
}, },
'settings': { 'settings': {
'interface': USER_PERMISSIONS_SETTINGS_INTERFACE, 'interface': USER_PERMISSIONS_SETTINGS_INTERFACE,
+20
View File
@@ -21,6 +21,7 @@ from open_webui.utils.automations import (
execute_automation, execute_automation,
) )
from open_webui.utils.auth import get_verified_user, get_admin_user from open_webui.utils.auth import get_verified_user, get_admin_user
from open_webui.utils.access_control import has_permission
from open_webui.internal.db import get_session from open_webui.internal.db import get_session
from open_webui.constants import ERROR_MESSAGES from open_webui.constants import ERROR_MESSAGES
@@ -36,6 +37,16 @@ PAGE_ITEM_COUNT = 30
############################ ############################
def check_automations_permission(request, user):
if user.role != 'admin' and not has_permission(
user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
def check_automation_access(automation, user): def check_automation_access(automation, user):
if not automation: if not automation:
raise HTTPException( raise HTTPException(
@@ -71,6 +82,7 @@ async def get_automations(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
automations = Automations.get_by_user(user.id, db=db) automations = Automations.get_by_user(user.id, db=db)
return [enrich_automation(automation, db, tz=user.timezone) for automation in automations] return [enrich_automation(automation, db, tz=user.timezone) for automation in automations]
@@ -89,6 +101,7 @@ async def get_automation_items(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
limit = PAGE_ITEM_COUNT limit = PAGE_ITEM_COUNT
page = max(1, page) page = max(1, page)
skip = (page - 1) * limit skip = (page - 1) * limit
@@ -123,6 +136,7 @@ async def create_new_automation(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
try: try:
validate_rrule(form_data.data.rrule) validate_rrule(form_data.data.rrule)
except ValueError as e: except ValueError as e:
@@ -159,6 +173,7 @@ async def get_automation_by_id(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db) automation = Automations.get_by_id(id, db=db)
check_automation_access(automation, user) check_automation_access(automation, user)
return enrich_automation(automation, db, tz=user.timezone) return enrich_automation(automation, db, tz=user.timezone)
@@ -177,6 +192,7 @@ async def update_automation_by_id(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db) automation = Automations.get_by_id(id, db=db)
check_automation_access(automation, user) check_automation_access(automation, user)
@@ -216,6 +232,7 @@ async def toggle_automation_by_id(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db) automation = Automations.get_by_id(id, db=db)
check_automation_access(automation, user) check_automation_access(automation, user)
toggled = Automations.toggle( toggled = Automations.toggle(
@@ -236,6 +253,7 @@ async def run_automation_by_id(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db) automation = Automations.get_by_id(id, db=db)
check_automation_access(automation, user) check_automation_access(automation, user)
asyncio.create_task(execute_automation(request.app, automation)) asyncio.create_task(execute_automation(request.app, automation))
@@ -254,6 +272,7 @@ async def delete_automation_by_id(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db) automation = Automations.get_by_id(id, db=db)
check_automation_access(automation, user) check_automation_access(automation, user)
AutomationRuns.delete_by_automation(id, db=db) AutomationRuns.delete_by_automation(id, db=db)
@@ -274,6 +293,7 @@ async def get_automation_runs(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db) automation = Automations.get_by_id(id, db=db)
check_automation_access(automation, user) check_automation_access(automation, user)
return AutomationRuns.get_by_automation(id, skip=skip, limit=limit, db=db) return AutomationRuns.get_by_automation(id, skip=skip, limit=limit, db=db)
+1
View File
@@ -232,6 +232,7 @@ class FeaturesPermissions(BaseModel):
image_generation: bool = True image_generation: bool = True
code_interpreter: bool = True code_interpreter: bool = True
memories: bool = True memories: bool = True
automations: bool = False
class SettingsPermissions(BaseModel): class SettingsPermissions(BaseModel):
@@ -894,6 +894,28 @@
</div> </div>
{/if} {/if}
</div> </div>
<div class="flex flex-col w-full">
<Tooltip
className="flex w-full justify-between my-1"
content={$i18n.t(
'Warning: Enabling this will allow users to run scheduled prompts automatically.'
)}
placement="top-start"
>
<div class=" self-center text-xs font-medium">
{$i18n.t('Automations')}
</div>
<Switch bind:state={permissions.features.automations} />
</Tooltip>
{#if defaultPermissions?.features?.automations && !permissions.features.automations}
<div>
<div class="text-xs text-gray-500">
{$i18n.t('This is a default user permission and will remain enabled.')}
</div>
</div>
{/if}
</div>
</div> </div>
<hr class=" border-gray-100/30 dark:border-gray-850/30" /> <hr class=" border-gray-100/30 dark:border-gray-850/30" />
@@ -9,6 +9,7 @@
import { showSettings, mobile, showSidebar, showShortcuts, user, config } from '$lib/stores'; import { showSettings, mobile, showSidebar, showShortcuts, user, config } from '$lib/stores';
import { WEBUI_API_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL } from '$lib/constants';
import Dropdown from '$lib/components/common/Dropdown.svelte'; import Dropdown from '$lib/components/common/Dropdown.svelte';
@@ -214,6 +215,7 @@
<div class=" self-center truncate">{$i18n.t('Settings')}</div> <div class=" self-center truncate">{$i18n.t('Settings')}</div>
</button> </button>
{#if $user?.role === 'admin' || $user?.permissions?.features?.automations}
<a <a
href="/automations" href="/automations"
draggable="false" draggable="false"
@@ -247,6 +249,7 @@
</div> </div>
<div class="self-center truncate">{$i18n.t('Automations')}</div> <div class="self-center truncate">{$i18n.t('Automations')}</div>
</a> </a>
{/if}
<button <button
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none" class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
+2 -1
View File
@@ -60,7 +60,8 @@ export const DEFAULT_PERMISSIONS = {
web_search: true, web_search: true,
image_generation: true, image_generation: true,
code_interpreter: true, code_interpreter: true,
memories: true memories: true,
automations: false
}, },
settings: { settings: {
interface: true interface: true
+10 -1
View File
@@ -2,7 +2,8 @@
import { onMount, onDestroy, getContext } from 'svelte'; import { onMount, onDestroy, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { WEBUI_NAME, mobile, showSidebar } from '$lib/stores'; import { goto } from '$app/navigation';
import { WEBUI_NAME, mobile, showSidebar, user, config } from '$lib/stores';
import { import {
getAutomationItems, getAutomationItems,
@@ -165,6 +166,14 @@
}; };
onMount(async () => { onMount(async () => {
if (
$user?.role !== 'admin' &&
!($user?.permissions?.features?.automations ?? false)
) {
goto('/');
return;
}
loaded = true; loaded = true;
return () => { return () => {