mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-14 04:08:46 +00:00
refac
This commit is contained in:
parent
6229405b4d
commit
2d47128242
2 changed files with 86 additions and 39 deletions
|
|
@ -4,6 +4,7 @@ import time
|
|||
import uuid
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import jwt
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request, status
|
||||
from open_webui.internal.db import get_async_session
|
||||
from open_webui.models.access_grants import AccessGrant, AccessGrants
|
||||
|
|
@ -81,12 +82,15 @@ async def preview_app_manifest(form_data: AppManifestForm, user=Depends(get_admi
|
|||
try:
|
||||
status_code, raw = await send_app_request(form_data.url)
|
||||
if status_code != 200:
|
||||
raise ValueError()
|
||||
raise HTTPException(502, f'App manifest endpoint returned HTTP {status_code}. Check the URL or enter details manually.')
|
||||
manifest = Manifest.model_validate(raw)
|
||||
return manifest.model_dump()
|
||||
except Exception:
|
||||
except HTTPException:
|
||||
raise
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid or unreachable app manifest'
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail='App returned an invalid manifest. Check its app details or enter them manually.',
|
||||
) from None
|
||||
|
||||
|
||||
|
|
@ -391,22 +395,32 @@ async def connect_app_by_id(
|
|||
)
|
||||
await db.commit()
|
||||
challenge = str(uuid.uuid4())
|
||||
status_code, value = await send_app_event(
|
||||
app,
|
||||
{'id': str(uuid.uuid4()), 'type': 'connect', 'data': {'challenge': challenge}},
|
||||
timeout=20,
|
||||
)
|
||||
if status_code != 200:
|
||||
detail = {
|
||||
401: 'App rejected the API key. Enter the API key configured on the app server.',
|
||||
403: 'App refused the connection. Check the app server access rules and logs.',
|
||||
400: 'App rejected the connection request. Check the WebUI origin and app server configuration.',
|
||||
404: 'App event endpoint was not found. Check the URL in app settings.',
|
||||
405: 'App endpoint does not accept POST requests. Use its event endpoint URL.',
|
||||
503: 'App is busy or shutting down. Connect again when it is ready.',
|
||||
504: 'App connection request timed out. Check the app server logs.',
|
||||
}.get(status_code, f'App returned HTTP {status_code}. Check the app server logs.')
|
||||
raise HTTPException(504 if status_code == 504 else 502, detail)
|
||||
if not isinstance(value, dict) or not isinstance(value.get('proof'), str):
|
||||
raise HTTPException(502, 'App did not return an identity proof. Check its event endpoint and SDK version.')
|
||||
try:
|
||||
status_code, value = await send_app_event(
|
||||
app,
|
||||
{'id': str(uuid.uuid4()), 'type': 'connect', 'data': {'challenge': challenge}},
|
||||
timeout=20,
|
||||
)
|
||||
if status_code != 200 or not isinstance(value, dict):
|
||||
raise ValueError()
|
||||
claims = verify_app_token(app, value['proof'], 'webui-app-connect+jwt')
|
||||
if claims.get('challenge') != challenge:
|
||||
raise ValueError()
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail='Could not connect. Check the app API key and installed public key.',
|
||||
) from None
|
||||
except jwt.InvalidSignatureError:
|
||||
raise HTTPException(502, 'App public key does not match the installed key. Review the app public key in settings.') from None
|
||||
except (jwt.InvalidTokenError, ValueError, TypeError, KeyError):
|
||||
raise HTTPException(502, 'App identity proof is invalid or expired. Check the installed public key and server clocks.') from None
|
||||
result = await db.execute(
|
||||
update(App)
|
||||
.where(App.id == app_id, App.updated_at == checked_at, App.api_key == api_key)
|
||||
|
|
|
|||
|
|
@ -92,27 +92,47 @@ async def send_app_request(
|
|||
timeout: float = 10,
|
||||
max_response_size: int = 262144,
|
||||
) -> tuple[int, Any]:
|
||||
allow_private = validate_app_url(url)
|
||||
async with aiohttp.ClientSession(
|
||||
connector=AppTCPConnector(allow_private=allow_private),
|
||||
timeout=aiohttp.ClientTimeout(total=timeout),
|
||||
trust_env=False,
|
||||
) as session:
|
||||
async with session.request(
|
||||
'POST' if body is not None else 'GET',
|
||||
url,
|
||||
data=body,
|
||||
headers=headers,
|
||||
allow_redirects=False,
|
||||
) as response:
|
||||
response_body = bytearray()
|
||||
async for chunk in response.content.iter_chunked(16384):
|
||||
response_body.extend(chunk)
|
||||
if len(response_body) > max_response_size:
|
||||
raise ValueError('App response is too large')
|
||||
if response.status != 200:
|
||||
return response.status, None
|
||||
return response.status, json.loads(response_body) if response_body else None
|
||||
try:
|
||||
allow_private = validate_app_url(url)
|
||||
async with aiohttp.ClientSession(
|
||||
connector=AppTCPConnector(allow_private=allow_private),
|
||||
timeout=aiohttp.ClientTimeout(total=timeout),
|
||||
trust_env=False,
|
||||
) as session:
|
||||
async with session.request(
|
||||
'POST' if body is not None else 'GET',
|
||||
url,
|
||||
data=body,
|
||||
headers=headers,
|
||||
allow_redirects=False,
|
||||
) as response:
|
||||
response_body = bytearray()
|
||||
async for chunk in response.content.iter_chunked(16384):
|
||||
response_body.extend(chunk)
|
||||
if len(response_body) > max_response_size:
|
||||
raise ValueError('App response is too large')
|
||||
if response.status != 200:
|
||||
return response.status, None
|
||||
return response.status, json.loads(response_body) if response_body else None
|
||||
except TimeoutError:
|
||||
raise HTTPException(504, 'App request timed out. Check that the app server is running and reachable.') from None
|
||||
except aiohttp.ClientSSLError:
|
||||
raise HTTPException(502, 'App TLS verification failed. Check its certificate and hostname.') from None
|
||||
except aiohttp.ClientConnectorDNSError:
|
||||
raise HTTPException(502, 'App hostname could not be resolved. Check DNS from the WebUI server.') from None
|
||||
except aiohttp.ClientConnectorError:
|
||||
raise HTTPException(502, 'Could not reach the app server. Check its address, port and network access.') from None
|
||||
except aiohttp.ClientError:
|
||||
raise HTTPException(502, 'The app connection failed before a complete response was received.') from None
|
||||
except ValueError as exc:
|
||||
# Only local, fixed diagnostics may be shown; never echo arbitrary app responses.
|
||||
details = {
|
||||
'Invalid service URL': 'Invalid app URL. Use an HTTP(S) URL without credentials or a fragment.',
|
||||
'App endpoints require HTTPS': 'App endpoints require HTTPS. For local HTTP, configure APP_ALLOWED_PRIVATE_ORIGINS.',
|
||||
'App endpoint resolves to a prohibited address': 'App resolves to a private or reserved address. For local development, configure APP_ALLOWED_PRIVATE_ORIGINS.',
|
||||
'App response is too large': 'App response exceeds the allowed size.',
|
||||
}
|
||||
raise HTTPException(502, details.get(str(exc), 'App returned an invalid response. Expected JSON.')) from None
|
||||
|
||||
|
||||
def get_app_encryption_cipher() -> Fernet:
|
||||
|
|
@ -490,10 +510,23 @@ async def generate_app_channel_reply(
|
|||
await db.commit()
|
||||
try:
|
||||
status, raw = await send_app_event(app, envelope)
|
||||
except Exception:
|
||||
raise HTTPException(504, 'App response timed out; execution outcome may be unknown') from None
|
||||
except HTTPException as exc:
|
||||
if exc.status_code == 504:
|
||||
raise HTTPException(504, 'App response timed out; execution outcome may be unknown') from None
|
||||
raise
|
||||
except ValueError:
|
||||
raise HTTPException(502, 'App event could not be delivered. Check the WebUI server logs.') from None
|
||||
if status != 200:
|
||||
raise HTTPException(502, 'App rejected the interaction')
|
||||
detail = {
|
||||
401: 'App rejected the API key. Update the key in app settings.',
|
||||
403: 'App rejected event authorization. Check the app server logs for the verification failure.',
|
||||
404: 'App event endpoint was not found. Check its URL in app settings.',
|
||||
405: 'App endpoint does not accept POST requests. Check its event URL.',
|
||||
413: 'App rejected the event because it is too large.',
|
||||
503: 'App is busy or shutting down. The event was not accepted.',
|
||||
504: 'App processing timed out; execution outcome may be unknown.',
|
||||
}.get(status, f'App returned HTTP {status}. Check the app server logs.')
|
||||
raise HTTPException(504 if status == 504 else 502, detail)
|
||||
app = await get_app_by_id(app.id, require_active=True, db=db)
|
||||
if (app.api_key, app.data['manifest']['public_key']) != checked_key:
|
||||
raise HTTPException(403, 'App credentials changed')
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue