updated decryption patch to 0.6.18

This commit is contained in:
priten 2025-07-29 08:39:26 -05:00
parent b8912aa671
commit 5788293d23
6 changed files with 191 additions and 1 deletions

View file

@ -2250,6 +2250,28 @@ RAG_ALLOWED_FILE_EXTENSIONS = PersistentConfig(
],
)
# File decryption settings
ENABLE_FILE_DECRYPTION = PersistentConfig(
"ENABLE_FILE_DECRYPTION",
"file_decryption.enable",
os.environ.get("ENABLE_FILE_DECRYPTION", "False").lower() == "true",
)
FILE_DECRYPTION_ENDPOINT = PersistentConfig(
"FILE_DECRYPTION_ENDPOINT",
"file_decryption.endpoint",
os.environ.get("FILE_DECRYPTION_ENDPOINT", ""),
)
FILE_DECRYPTION_API_KEY = PersistentConfig(
"FILE_DECRYPTION_API_KEY",
"file_decryption.api_key",
os.environ.get("FILE_DECRYPTION_API_KEY", ""),
)
FILE_DECRYPTION_TIMEOUT = PersistentConfig(
"FILE_DECRYPTION_TIMEOUT",
"file_decryption.timeout",
int(os.environ.get("FILE_DECRYPTION_TIMEOUT", "30")),
)
RAG_EMBEDDING_ENGINE = PersistentConfig(
"RAG_EMBEDDING_ENGINE",
"rag.embedding_engine",

View file

@ -223,6 +223,10 @@ from open_webui.config import (
RAG_AZURE_OPENAI_API_VERSION,
RAG_OLLAMA_BASE_URL,
RAG_OLLAMA_API_KEY,
ENABLE_FILE_DECRYPTION,
FILE_DECRYPTION_ENDPOINT,
FILE_DECRYPTION_API_KEY,
FILE_DECRYPTION_TIMEOUT,
CHUNK_OVERLAP,
CHUNK_SIZE,
CONTENT_EXTRACTION_ENGINE,
@ -818,6 +822,11 @@ app.state.config.RAG_AZURE_OPENAI_API_VERSION = RAG_AZURE_OPENAI_API_VERSION
app.state.config.RAG_OLLAMA_BASE_URL = RAG_OLLAMA_BASE_URL
app.state.config.RAG_OLLAMA_API_KEY = RAG_OLLAMA_API_KEY
app.state.config.ENABLE_FILE_DECRYPTION = ENABLE_FILE_DECRYPTION
app.state.config.FILE_DECRYPTION_ENDPOINT = FILE_DECRYPTION_ENDPOINT
app.state.config.FILE_DECRYPTION_API_KEY = FILE_DECRYPTION_API_KEY
app.state.config.FILE_DECRYPTION_TIMEOUT = FILE_DECRYPTION_TIMEOUT
app.state.config.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES
app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
@ -1616,6 +1625,7 @@ async def get_app_config(request: Request):
"width": app.state.config.FILE_IMAGE_COMPRESSION_WIDTH,
"height": app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT,
},
"decryption_enabled": app.state.config.ENABLE_FILE_DECRYPTION,
},
"permissions": {**app.state.config.USER_PERMISSIONS},
"google_drive": {

View file

@ -397,6 +397,11 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
"TOP_K_RERANKER": request.app.state.config.TOP_K_RERANKER,
"RELEVANCE_THRESHOLD": request.app.state.config.RELEVANCE_THRESHOLD,
"HYBRID_BM25_WEIGHT": request.app.state.config.HYBRID_BM25_WEIGHT,
# File decryption settings
"ENABLE_FILE_DECRYPTION": request.app.state.config.ENABLE_FILE_DECRYPTION,
"FILE_DECRYPTION_ENDPOINT": request.app.state.config.FILE_DECRYPTION_ENDPOINT,
"FILE_DECRYPTION_API_KEY": request.app.state.config.FILE_DECRYPTION_API_KEY,
"FILE_DECRYPTION_TIMEOUT": request.app.state.config.FILE_DECRYPTION_TIMEOUT,
# Content extraction settings
"CONTENT_EXTRACTION_ENGINE": request.app.state.config.CONTENT_EXTRACTION_ENGINE,
"PDF_EXTRACT_IMAGES": request.app.state.config.PDF_EXTRACT_IMAGES,
@ -611,6 +616,12 @@ class ConfigForm(BaseModel):
ENABLE_GOOGLE_DRIVE_INTEGRATION: Optional[bool] = None
ENABLE_ONEDRIVE_INTEGRATION: Optional[bool] = None
# File decryption settings
ENABLE_FILE_DECRYPTION: Optional[bool] = None
FILE_DECRYPTION_ENDPOINT: Optional[str] = None
FILE_DECRYPTION_API_KEY: Optional[str] = None
FILE_DECRYPTION_TIMEOUT: Optional[int] = None
# Web search settings
web: Optional[WebConfig] = None
@ -889,6 +900,28 @@ async def update_rag_config(
else request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION
)
# File decryption settings
request.app.state.config.ENABLE_FILE_DECRYPTION = (
form_data.ENABLE_FILE_DECRYPTION
if form_data.ENABLE_FILE_DECRYPTION is not None
else request.app.state.config.ENABLE_FILE_DECRYPTION
)
request.app.state.config.FILE_DECRYPTION_ENDPOINT = (
form_data.FILE_DECRYPTION_ENDPOINT
if form_data.FILE_DECRYPTION_ENDPOINT is not None
else request.app.state.config.FILE_DECRYPTION_ENDPOINT
)
request.app.state.config.FILE_DECRYPTION_API_KEY = (
form_data.FILE_DECRYPTION_API_KEY
if form_data.FILE_DECRYPTION_API_KEY is not None
else request.app.state.config.FILE_DECRYPTION_API_KEY
)
request.app.state.config.FILE_DECRYPTION_TIMEOUT = (
form_data.FILE_DECRYPTION_TIMEOUT
if form_data.FILE_DECRYPTION_TIMEOUT is not None
else request.app.state.config.FILE_DECRYPTION_TIMEOUT
)
if form_data.web is not None:
# Web search settings
request.app.state.config.ENABLE_WEB_SEARCH = form_data.web.ENABLE_WEB_SEARCH
@ -1045,6 +1078,11 @@ async def update_rag_config(
# Integration settings
"ENABLE_GOOGLE_DRIVE_INTEGRATION": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
"ENABLE_ONEDRIVE_INTEGRATION": request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
# File decryption settings
"ENABLE_FILE_DECRYPTION": request.app.state.config.ENABLE_FILE_DECRYPTION,
"FILE_DECRYPTION_ENDPOINT": request.app.state.config.FILE_DECRYPTION_ENDPOINT,
"FILE_DECRYPTION_API_KEY": request.app.state.config.FILE_DECRYPTION_API_KEY,
"FILE_DECRYPTION_TIMEOUT": request.app.state.config.FILE_DECRYPTION_TIMEOUT,
# Web search settings
"web": {
"ENABLE_WEB_SEARCH": request.app.state.config.ENABLE_WEB_SEARCH,

View file

@ -26,6 +26,10 @@ from open_webui.config import (
AZURE_STORAGE_KEY,
STORAGE_PROVIDER,
UPLOAD_DIR,
ENABLE_FILE_DECRYPTION,
FILE_DECRYPTION_ENDPOINT,
FILE_DECRYPTION_API_KEY,
FILE_DECRYPTION_TIMEOUT,
)
from google.cloud import storage
from google.cloud.exceptions import GoogleCloudError, NotFound
@ -35,11 +39,36 @@ from azure.storage.blob import BlobServiceClient
from azure.core.exceptions import ResourceNotFoundError
from open_webui.env import SRC_LOG_LEVELS
from open_webui.utils.decryption import (
decrypt_file_via_azure,
DecryptionError,
)
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MAIN"])
def _decrypt_content_if_enabled(filename: str, contents: bytes) -> bytes:
"""Checks config and decrypts file contents if enabled."""
if ENABLE_FILE_DECRYPTION and FILE_DECRYPTION_ENDPOINT and FILE_DECRYPTION_API_KEY:
try:
decrypted_contents = decrypt_file_via_azure(
filename,
contents,
FILE_DECRYPTION_ENDPOINT,
FILE_DECRYPTION_API_KEY,
FILE_DECRYPTION_TIMEOUT,
)
log.info(f"File {filename} decrypted successfully.")
return decrypted_contents
except Exception as e:
log.error(f"File decryption failed: {e}")
# Raise a specific error to be caught by the API layer
raise DecryptionError(f"File decryption failed: {e}")
return contents
class StorageProvider(ABC):
@abstractmethod
def get_file(self, file_path: str) -> str:
@ -68,6 +97,10 @@ class LocalStorageProvider(StorageProvider):
contents = file.read()
if not contents:
raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
# Decrypt contents if decryption is enabled in the config
contents = _decrypt_content_if_enabled(filename, contents)
file_path = f"{UPLOAD_DIR}/{filename}"
with open(file_path, "wb") as f:
f.write(contents)

View file

@ -136,6 +136,14 @@
};
const submitHandler = async () => {
if (
RAGConfig.ENABLE_FILE_DECRYPTION &&
(!RAGConfig.FILE_DECRYPTION_ENDPOINT || !RAGConfig.FILE_DECRYPTION_API_KEY)
) {
toast.error($i18n.t('Endpoint URL and API Key are required when file decryption is enabled.'));
return;
}
if (
RAGConfig.CONTENT_EXTRACTION_ENGINE === 'external' &&
RAGConfig.EXTERNAL_DOCUMENT_LOADER_URL === ''
@ -192,6 +200,10 @@
const res = await updateRAGConfig(localStorage.token, {
...RAGConfig,
ENABLE_FILE_DECRYPTION: RAGConfig.ENABLE_FILE_DECRYPTION ?? false,
FILE_DECRYPTION_ENDPOINT: RAGConfig.FILE_DECRYPTION_ENDPOINT ?? '',
FILE_DECRYPTION_API_KEY: RAGConfig.FILE_DECRYPTION_API_KEY ?? '',
FILE_DECRYPTION_TIMEOUT: RAGConfig.FILE_DECRYPTION_TIMEOUT ?? 30,
ALLOWED_FILE_EXTENSIONS: RAGConfig.ALLOWED_FILE_EXTENSIONS.split(',')
.map((ext) => ext.trim())
.filter((ext) => ext !== ''),
@ -1058,6 +1070,62 @@
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class="mb-2.5 flex w-full justify-between">
<div class="self-center text-xs font-medium">{$i18n.t('File Decryption')}</div>
<div class="flex items-center relative">
<Switch bind:state={RAGConfig.ENABLE_FILE_DECRYPTION} />
</div>
</div>
{#if RAGConfig.ENABLE_FILE_DECRYPTION}
<div
class="space-y-2.5 pl-6 border-l-2 border-gray-100 dark:border-gray-800 ml-1"
>
<div class="flex w-full justify-between items-center">
<div class="self-center text-xs font-medium">
{$i18n.t('Decryption Endpoint URL')}
</div>
<input
class="flex-1 w-full text-sm bg-transparent outline-hidden text-right"
type="text"
placeholder={$i18n.t('Enter Azure Function Endpoint')}
bind:value={RAGConfig.FILE_DECRYPTION_ENDPOINT}
autocomplete="off"
required
/>
</div>
<div class="flex w-full justify-between items-center">
<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div>
<div class="max-w-[180px]">
<SensitiveInput
placeholder={$i18n.t('Enter API Key')}
bind:value={RAGConfig.FILE_DECRYPTION_API_KEY}
required
/>
</div>
</div>
<div class="flex w-full justify-between items-center">
<div class="self-center text-xs font-medium">{$i18n.t('Timeout (seconds)')}</div>
<input
class="w-auto text-sm bg-transparent outline-hidden text-right"
type="number"
placeholder="30"
bind:value={RAGConfig.FILE_DECRYPTION_TIMEOUT}
autocomplete="off"
min="1"
/>
</div>
{#if !RAGConfig.FILE_DECRYPTION_ENDPOINT || !RAGConfig.FILE_DECRYPTION_API_KEY}
<div class="text-xs text-red-500 text-right">
{$i18n.t('Endpoint URL and API Key are required when file decryption is enabled.')}
</div>
{/if}
</div>
{/if}
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Allowed File Extensions')}</div>
<div class="flex items-center relative">

View file

@ -533,7 +533,15 @@
files = [...files, fileItem];
if (!$temporaryChatEnabled) {
let loadingToastId = null;
try {
// Show loading toast for decryption if enabled in config
if ($config?.file?.decryption_enabled ?? false) {
loadingToastId = toast.loading($i18n.t('Decrypting and uploading file...'));
} else {
loadingToastId = toast.loading($i18n.t('Uploading file...'));
}
// If the file is an audio file, provide the language for STT.
let metadata = null;
if (
@ -568,11 +576,22 @@
fileItem.url = `${WEBUI_API_BASE_URL}/files/${uploadedFile.id}`;
files = files;
toast.success($i18n.t('File uploaded successfully.'), { id: loadingToastId });
} else {
files = files.filter((item) => item?.itemId !== tempItemId);
toast.dismiss(loadingToastId);
}
} catch (e) {
toast.error(`${e}`);
toast.dismiss(loadingToastId);
const errMsg = typeof e === 'string' ? e : (e?.message || `${e}`);
if (
errMsg.toLowerCase().includes('decryption') ||
errMsg.toLowerCase().includes('azure function error')
) {
toast.error($i18n.t('File decryption failed: {{error}}', { error: errMsg }));
} else {
toast.error(`${errMsg}`);
}
files = files.filter((item) => item?.itemId !== tempItemId);
}
} else {