diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index ed6906edcf..f99034a3c5 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -287,25 +287,30 @@ class AppConfig: # WEBUI_AUTH (Required for security) #################################### -ENABLE_API_KEY = PersistentConfig( - "ENABLE_API_KEY", - "auth.api_key.enable", - os.environ.get("ENABLE_API_KEY", "True").lower() == "true", +ENABLE_API_KEYS = PersistentConfig( + "ENABLE_API_KEYS", + "auth.enable_api_keys", + os.environ.get("ENABLE_API_KEYS", "False").lower() == "true", ) -ENABLE_API_KEY_ENDPOINT_RESTRICTIONS = PersistentConfig( - "ENABLE_API_KEY_ENDPOINT_RESTRICTIONS", +ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = PersistentConfig( + "ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS", "auth.api_key.endpoint_restrictions", - os.environ.get("ENABLE_API_KEY_ENDPOINT_RESTRICTIONS", "False").lower() == "true", + os.environ.get( + "ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS", + os.environ.get("ENABLE_API_KEY_ENDPOINT_RESTRICTIONS", "False"), + ).lower() + == "true", ) -API_KEY_ALLOWED_ENDPOINTS = PersistentConfig( - "API_KEY_ALLOWED_ENDPOINTS", +API_KEYS_ALLOWED_ENDPOINTS = PersistentConfig( + "API_KEYS_ALLOWED_ENDPOINTS", "auth.api_key.allowed_endpoints", - os.environ.get("API_KEY_ALLOWED_ENDPOINTS", ""), + os.environ.get( + "API_KEYS_ALLOWED_ENDPOINTS", os.environ.get("API_KEY_ALLOWED_ENDPOINTS", "") + ), ) - JWT_EXPIRES_IN = PersistentConfig( "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "4w") ) @@ -1395,6 +1400,10 @@ USER_PERMISSIONS_FEATURES_NOTES = ( os.environ.get("USER_PERMISSIONS_FEATURES_NOTES", "True").lower() == "true" ) +USER_PERMISSIONS_FEATURES_API_KEYS = ( + os.environ.get("USER_PERMISSIONS_FEATURES_API_KEYS", "False").lower() == "true" +) + DEFAULT_USER_PERMISSIONS = { "workspace": { @@ -1438,6 +1447,7 @@ DEFAULT_USER_PERMISSIONS = { "temporary_enforced": USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED, }, "features": { + "api_keys": USER_PERMISSIONS_FEATURES_API_KEYS, "direct_tool_servers": USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS, "web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH, "image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION, diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 4c39a649a0..cd54dd25fc 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -357,9 +357,9 @@ from open_webui.config import ( JWT_EXPIRES_IN, ENABLE_SIGNUP, ENABLE_LOGIN_FORM, - ENABLE_API_KEY, - ENABLE_API_KEY_ENDPOINT_RESTRICTIONS, - API_KEY_ALLOWED_ENDPOINTS, + ENABLE_API_KEYS, + ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS, + API_KEYS_ALLOWED_ENDPOINTS, ENABLE_CHANNELS, ENABLE_NOTES, ENABLE_COMMUNITY_SHARING, @@ -741,11 +741,11 @@ app.state.config.WEBUI_URL = WEBUI_URL app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP app.state.config.ENABLE_LOGIN_FORM = ENABLE_LOGIN_FORM -app.state.config.ENABLE_API_KEY = ENABLE_API_KEY -app.state.config.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS = ( - ENABLE_API_KEY_ENDPOINT_RESTRICTIONS +app.state.config.ENABLE_API_KEYS = ENABLE_API_KEYS +app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = ( + ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS ) -app.state.config.API_KEY_ALLOWED_ENDPOINTS = API_KEY_ALLOWED_ENDPOINTS +app.state.config.API_KEYS_ALLOWED_ENDPOINTS = API_KEYS_ALLOWED_ENDPOINTS app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN @@ -1286,11 +1286,11 @@ class APIKeyRestrictionMiddleware(BaseHTTPMiddleware): # Only apply restrictions if an sk- API key is used if token and token.startswith("sk-"): # Check if restrictions are enabled - if request.app.state.config.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS: + if request.app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS: allowed_paths = [ path.strip() for path in str( - request.app.state.config.API_KEY_ALLOWED_ENDPOINTS + request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS ).split(",") if path.strip() ] @@ -1333,7 +1333,7 @@ async def check_url(request: Request, call_next): request.headers.get("Authorization") ) - request.state.enable_api_key = app.state.config.ENABLE_API_KEY + request.state.enable_api_keys = app.state.config.ENABLE_API_KEYS response = await call_next(request) process_time = int(time.time()) - start_time response.headers["X-Process-Time"] = str(process_time) @@ -1839,7 +1839,7 @@ async def get_app_config(request: Request): "auth_trusted_header": bool(app.state.AUTH_TRUSTED_EMAIL_HEADER), "enable_signup_password_confirmation": ENABLE_SIGNUP_PASSWORD_CONFIRMATION, "enable_ldap": app.state.config.ENABLE_LDAP, - "enable_api_key": app.state.config.ENABLE_API_KEY, + "enable_api_keys": app.state.config.ENABLE_API_KEYS, "enable_signup": app.state.config.ENABLE_SIGNUP, "enable_login_form": app.state.config.ENABLE_LOGIN_FORM, "enable_websocket": ENABLE_WEBSOCKET_SUPPORT, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 72a4c3f2a0..b214441a49 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -55,7 +55,7 @@ from open_webui.utils.auth import ( get_http_authorization_cred, ) from open_webui.utils.webhook import post_webhook -from open_webui.utils.access_control import get_permissions +from open_webui.utils.access_control import get_permissions, has_permission from typing import Optional, List @@ -853,9 +853,9 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)): "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS, "WEBUI_URL": request.app.state.config.WEBUI_URL, "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP, - "ENABLE_API_KEY": request.app.state.config.ENABLE_API_KEY, - "ENABLE_API_KEY_ENDPOINT_RESTRICTIONS": request.app.state.config.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS, - "API_KEY_ALLOWED_ENDPOINTS": request.app.state.config.API_KEY_ALLOWED_ENDPOINTS, + "ENABLE_API_KEYS": request.app.state.config.ENABLE_API_KEYS, + "ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS": request.app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS, + "API_KEYS_ALLOWED_ENDPOINTS": request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS, "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE, "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, @@ -873,9 +873,9 @@ class AdminConfig(BaseModel): SHOW_ADMIN_DETAILS: bool WEBUI_URL: str ENABLE_SIGNUP: bool - ENABLE_API_KEY: bool - ENABLE_API_KEY_ENDPOINT_RESTRICTIONS: bool - API_KEY_ALLOWED_ENDPOINTS: str + ENABLE_API_KEYS: bool + ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS: bool + API_KEYS_ALLOWED_ENDPOINTS: str DEFAULT_USER_ROLE: str JWT_EXPIRES_IN: str ENABLE_COMMUNITY_SHARING: bool @@ -896,12 +896,12 @@ async def update_admin_config( request.app.state.config.WEBUI_URL = form_data.WEBUI_URL request.app.state.config.ENABLE_SIGNUP = form_data.ENABLE_SIGNUP - request.app.state.config.ENABLE_API_KEY = form_data.ENABLE_API_KEY - request.app.state.config.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS = ( - form_data.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS + request.app.state.config.ENABLE_API_KEYS = form_data.ENABLE_API_KEYS + request.app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = ( + form_data.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS ) - request.app.state.config.API_KEY_ALLOWED_ENDPOINTS = ( - form_data.API_KEY_ALLOWED_ENDPOINTS + request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS = ( + form_data.API_KEYS_ALLOWED_ENDPOINTS ) request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS @@ -936,9 +936,9 @@ async def update_admin_config( "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS, "WEBUI_URL": request.app.state.config.WEBUI_URL, "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP, - "ENABLE_API_KEY": request.app.state.config.ENABLE_API_KEY, - "ENABLE_API_KEY_ENDPOINT_RESTRICTIONS": request.app.state.config.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS, - "API_KEY_ALLOWED_ENDPOINTS": request.app.state.config.API_KEY_ALLOWED_ENDPOINTS, + "ENABLE_API_KEYS": request.app.state.config.ENABLE_API_KEYS, + "ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS": request.app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS, + "API_KEYS_ALLOWED_ENDPOINTS": request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS, "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE, "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, @@ -1063,9 +1063,11 @@ async def update_ldap_config( # create api key @router.post("/api_key", response_model=ApiKey) async def generate_api_key(request: Request, user=Depends(get_current_user)): - if not request.app.state.config.ENABLE_API_KEY: + if not request.app.state.config.ENABLE_API_KEYS or not has_permission( + user.id, "features.api_keys", request.app.state.config.USER_PERMISSIONS + ): raise HTTPException( - status.HTTP_403_FORBIDDEN, + status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_CREATION_NOT_ALLOWED, ) diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index 15eced4306..d615a28634 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -208,6 +208,7 @@ class ChatPermissions(BaseModel): class FeaturesPermissions(BaseModel): + api_keys: bool = False direct_tool_servers: bool = False web_search: bool = True image_generation: bool = True diff --git a/backend/open_webui/utils/auth.py b/backend/open_webui/utils/auth.py index b7c49de442..aa027321da 100644 --- a/backend/open_webui/utils/auth.py +++ b/backend/open_webui/utils/auth.py @@ -21,6 +21,8 @@ from typing import Optional, Union, List, Dict from opentelemetry import trace + +from open_webui.utils.access_control import has_permission from open_webui.models.users import Users from open_webui.constants import ERROR_MESSAGES @@ -228,13 +230,17 @@ def get_current_user( # auth by api key if token.startswith("sk-"): - if not request.state.enable_api_key: + user = get_current_user_by_api_key(token) + + if not request.state.enable_api_keys or not has_permission( + user.id, + "features.api_keys", + request.app.state.config.USER_PERMISSIONS, + ): raise HTTPException( status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_NOT_ALLOWED ) - user = get_current_user_by_api_key(token) - # Add user info to current span current_span = trace.get_current_span() if current_span: diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index 71c5e06a67..44ce446d71 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -338,21 +338,21 @@