chore: format
This commit is contained in:
@@ -20,7 +20,6 @@ from pytz import UTC
|
||||
from typing import Optional, Union, List, Dict
|
||||
|
||||
|
||||
|
||||
from open_webui.utils.access_control import has_permission
|
||||
from open_webui.models.users import Users
|
||||
from open_webui.models.auths import Auths
|
||||
@@ -238,9 +237,7 @@ async def is_valid_token(request, decoded) -> bool:
|
||||
# Per-user revocation (OIDC back-channel logout)
|
||||
user_id = decoded.get('id')
|
||||
if user_id:
|
||||
revoked_at = await request.app.state.redis.get(
|
||||
f'{REDIS_KEY_PREFIX}:auth:user:{user_id}:revoked_at'
|
||||
)
|
||||
revoked_at = await request.app.state.redis.get(f'{REDIS_KEY_PREFIX}:auth:user:{user_id}:revoked_at')
|
||||
if revoked_at:
|
||||
try:
|
||||
revoked_at_ts = int(revoked_at)
|
||||
@@ -385,6 +382,7 @@ async def get_current_user(
|
||||
# Refresh the user's last active timestamp
|
||||
# Fire-and-forget via asyncio.create_task to avoid blocking
|
||||
import asyncio
|
||||
|
||||
asyncio.create_task(Users.update_last_active_by_id(user.id))
|
||||
return user
|
||||
else:
|
||||
@@ -432,15 +430,10 @@ async def get_current_user_by_api_key(request, api_key: str):
|
||||
# (Authorization header, cookie, x-api-key header, etc.).
|
||||
if request.app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS:
|
||||
allowed_paths = [
|
||||
path.strip()
|
||||
for path in str(request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS).split(',')
|
||||
if path.strip()
|
||||
path.strip() for path in str(request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS).split(',') if path.strip()
|
||||
]
|
||||
request_path = request.url.path
|
||||
is_allowed = any(
|
||||
request_path == allowed or request_path.startswith(allowed + '/')
|
||||
for allowed in allowed_paths
|
||||
)
|
||||
is_allowed = any(request_path == allowed or request_path.startswith(allowed + '/') for allowed in allowed_paths)
|
||||
if not is_allowed:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
|
||||
@@ -88,7 +88,7 @@ async def convert_markdown_base64_images(request, content: str, metadata, user):
|
||||
last_end = 0
|
||||
|
||||
for match in MARKDOWN_IMAGE_URL_PATTERN.finditer(content):
|
||||
result_parts.append(content[last_end:match.start()])
|
||||
result_parts.append(content[last_end : match.start()])
|
||||
base64_string = match.group(2)
|
||||
if len(base64_string) > MIN_REPLACEMENT_URL_LENGTH:
|
||||
url = await get_image_url_from_base64(request, base64_string, metadata, user)
|
||||
|
||||
@@ -1785,7 +1785,10 @@ class OAuthManager:
|
||||
log.warning(f'Back-channel logout: no configured provider matches issuer {token_issuer}')
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={'error': 'invalid_request', 'error_description': 'No configured provider matches token issuer'},
|
||||
content={
|
||||
'error': 'invalid_request',
|
||||
'error_description': 'No configured provider matches token issuer',
|
||||
},
|
||||
)
|
||||
|
||||
# 4. Validate the logout_token signature and claims
|
||||
@@ -1886,5 +1889,7 @@ class OAuthManager:
|
||||
f'(email={user.email}, provider={matched_provider}, sessions_deleted={len(sessions)})'
|
||||
)
|
||||
|
||||
log.info(f'Back-channel logout: completed for {len(users_to_logout)} user(s), {revoked_count} revocation(s) set')
|
||||
log.info(
|
||||
f'Back-channel logout: completed for {len(users_to_logout)} user(s), {revoked_count} revocation(s) set'
|
||||
)
|
||||
return JSONResponse(status_code=200, content={})
|
||||
|
||||
@@ -194,20 +194,12 @@ def get_redis_connection(
|
||||
connection = None
|
||||
|
||||
connect_timeout_kwargs = (
|
||||
{'socket_connect_timeout': REDIS_SOCKET_CONNECT_TIMEOUT}
|
||||
if REDIS_SOCKET_CONNECT_TIMEOUT is not None
|
||||
else {}
|
||||
{'socket_connect_timeout': REDIS_SOCKET_CONNECT_TIMEOUT} if REDIS_SOCKET_CONNECT_TIMEOUT is not None else {}
|
||||
)
|
||||
|
||||
keepalive_kwargs = (
|
||||
{'socket_keepalive': True} if REDIS_SOCKET_KEEPALIVE else {}
|
||||
)
|
||||
keepalive_kwargs = {'socket_keepalive': True} if REDIS_SOCKET_KEEPALIVE else {}
|
||||
|
||||
health_check_kwargs = (
|
||||
{'health_check_interval': REDIS_HEALTH_CHECK_INTERVAL}
|
||||
if REDIS_HEALTH_CHECK_INTERVAL
|
||||
else {}
|
||||
)
|
||||
health_check_kwargs = {'health_check_interval': REDIS_HEALTH_CHECK_INTERVAL} if REDIS_HEALTH_CHECK_INTERVAL else {}
|
||||
|
||||
if async_mode:
|
||||
import redis.asyncio as redis
|
||||
|
||||
@@ -64,8 +64,7 @@ async def get_session() -> aiohttp.ClientSession:
|
||||
trust_env=True,
|
||||
)
|
||||
log.info(
|
||||
'Created shared aiohttp session pool '
|
||||
'(limit=%s, per_host=%s, dns_ttl=%d)',
|
||||
'Created shared aiohttp session pool (limit=%s, per_host=%s, dns_ttl=%d)',
|
||||
AIOHTTP_POOL_CONNECTIONS or 'unlimited',
|
||||
AIOHTTP_POOL_CONNECTIONS_PER_HOST or 'unlimited',
|
||||
AIOHTTP_POOL_DNS_TTL,
|
||||
|
||||
@@ -101,7 +101,9 @@ log = logging.getLogger(__name__)
|
||||
|
||||
# Let no function be called without need, and let what
|
||||
# it yields justify the cost of running it.
|
||||
async def get_async_tool_function_and_apply_extra_params(function: Callable, extra_params: dict) -> Callable[..., Awaitable]:
|
||||
async def get_async_tool_function_and_apply_extra_params(
|
||||
function: Callable, extra_params: dict
|
||||
) -> Callable[..., Awaitable]:
|
||||
sig = inspect.signature(function)
|
||||
extra_params = {k: v for k, v in extra_params.items() if k in sig.parameters}
|
||||
partial_func = partial(function, **extra_params)
|
||||
@@ -544,7 +546,9 @@ async def get_builtin_tools(
|
||||
|
||||
# Automation tools - create and manage scheduled automations from chat
|
||||
if is_builtin_tool_enabled('automations') and await has_user_permission('automations'):
|
||||
builtin_functions.extend([create_automation, update_automation, list_automations, toggle_automation, delete_automation])
|
||||
builtin_functions.extend(
|
||||
[create_automation, update_automation, list_automations, toggle_automation, delete_automation]
|
||||
)
|
||||
|
||||
for func in builtin_functions:
|
||||
callable = await get_async_tool_function_and_apply_extra_params(
|
||||
|
||||
@@ -13,19 +13,19 @@ _USER_PROFILE_IMAGE_RE = re.compile(r'^/api/v1/users/[^/?#]+/profile/image$')
|
||||
# regex across megabytes of data on every Pydantic instantiation for zero
|
||||
# security benefit (corrupt base64 simply renders a broken image, same as
|
||||
# a 404 URL). SVG is intentionally excluded: it can carry embedded scripts.
|
||||
_SAFE_DATA_URI_RE = re.compile(
|
||||
r'^data:image/(png|jpeg|gif|webp);base64,', re.IGNORECASE
|
||||
)
|
||||
_SAFE_DATA_URI_RE = re.compile(r'^data:image/(png|jpeg|gif|webp);base64,', re.IGNORECASE)
|
||||
|
||||
# Exact relative paths accepted as profile images. These are the only
|
||||
# static-asset paths OWUI itself assigns; no prefix/wildcard matching is
|
||||
# used so that arbitrary relative paths cannot trigger authenticated GETs
|
||||
# against internal endpoints when rendered as ``<img>`` sources.
|
||||
_SAFE_STATIC_PATHS = frozenset({
|
||||
'/user.png',
|
||||
'/favicon.png',
|
||||
'/static/favicon.png',
|
||||
})
|
||||
_SAFE_STATIC_PATHS = frozenset(
|
||||
{
|
||||
'/user.png',
|
||||
'/favicon.png',
|
||||
'/static/favicon.png',
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def validate_profile_image_url(url: str) -> str:
|
||||
@@ -67,9 +67,7 @@ def validate_profile_image_url(url: str) -> str:
|
||||
# for a URL like http://:80/path with no actual host).
|
||||
if parsed.scheme in ('http', 'https'):
|
||||
if not parsed.hostname:
|
||||
raise ValueError(
|
||||
'Invalid profile image URL: HTTP(S) URLs must include a host.'
|
||||
)
|
||||
raise ValueError('Invalid profile image URL: HTTP(S) URLs must include a host.')
|
||||
return url
|
||||
|
||||
# Base64-encoded raster images uploaded via the frontend.
|
||||
|
||||
Reference in New Issue
Block a user