mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-13 23:14:11 +00:00
back to format
This commit is contained in:
parent
11cebdf904
commit
f1199eb6ca
2 changed files with 104 additions and 89 deletions
|
|
@ -1,13 +1,14 @@
|
|||
import logging
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from open_webui.internal.db import Base, get_db_context
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
|
||||
from open_webui.models.users import User, UserModel, UserProfileImageResponse, Users
|
||||
from open_webui.utils.validate import validate_profile_image_url
|
||||
from pydantic import BaseModel, field_validator
|
||||
from sqlalchemy import Boolean, Column, String, Text
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ class Token(BaseModel):
|
|||
|
||||
|
||||
class ApiKey(BaseModel):
|
||||
api_key: str | None = None
|
||||
api_key: Optional[str] = None
|
||||
|
||||
|
||||
class SigninResponse(Token, UserProfileImageResponse):
|
||||
|
|
@ -73,18 +74,18 @@ class SignupForm(BaseModel):
|
|||
name: str
|
||||
email: str
|
||||
password: str
|
||||
profile_image_url: str | None = '/user.png'
|
||||
profile_image_url: Optional[str] = '/user.png'
|
||||
|
||||
@field_validator('profile_image_url')
|
||||
@classmethod
|
||||
def check_profile_image_url(cls, v: str | None) -> str | None:
|
||||
def check_profile_image_url(cls, v: Optional[str]) -> Optional[str]:
|
||||
if v is not None:
|
||||
return validate_profile_image_url(v)
|
||||
return v
|
||||
|
||||
|
||||
class AddUserForm(SignupForm):
|
||||
role: str | None = 'pending'
|
||||
role: Optional[str] = 'pending'
|
||||
|
||||
|
||||
class AuthsTable:
|
||||
|
|
@ -95,10 +96,10 @@ class AuthsTable:
|
|||
name: str,
|
||||
profile_image_url: str = '/user.png',
|
||||
role: str = 'pending',
|
||||
oauth: dict | None = None,
|
||||
db: Session | None = None,
|
||||
id: str | None = None,
|
||||
) -> UserModel | None:
|
||||
oauth: Optional[dict] = None,
|
||||
db: Optional[Session] = None,
|
||||
id: Optional[str] = None,
|
||||
) -> Optional[UserModel]:
|
||||
with get_db_context(db) as db:
|
||||
log.info('insert_new_auth')
|
||||
|
||||
|
|
@ -156,7 +157,9 @@ class AuthsTable:
|
|||
log.error(f'Failed to insert auth: {str(e)}')
|
||||
return None
|
||||
|
||||
def authenticate_user(self, email: str, verify_password: callable, db: Session | None = None) -> UserModel | None:
|
||||
def authenticate_user(
|
||||
self, email: str, verify_password: callable, db: Optional[Session] = None
|
||||
) -> Optional[UserModel]:
|
||||
log.info(f'authenticate_user: {email}')
|
||||
|
||||
user = Users.get_user_by_email(email, db=db)
|
||||
|
|
@ -176,8 +179,8 @@ class AuthsTable:
|
|||
except Exception:
|
||||
return None
|
||||
|
||||
def authenticate_user_by_api_key(self, api_key: str, db: Session | None = None) -> UserModel | None:
|
||||
log.info('authenticate_user_by_api_key')
|
||||
def authenticate_user_by_api_key(self, api_key: str, db: Optional[Session] = None) -> Optional[UserModel]:
|
||||
log.info(f'authenticate_user_by_api_key')
|
||||
# if no api_key, return None
|
||||
if not api_key:
|
||||
return None
|
||||
|
|
@ -188,7 +191,7 @@ class AuthsTable:
|
|||
except Exception:
|
||||
return False
|
||||
|
||||
def authenticate_user_by_email(self, email: str, db: Session | None = None) -> UserModel | None:
|
||||
def authenticate_user_by_email(self, email: str, db: Optional[Session] = None) -> Optional[UserModel]:
|
||||
log.info(f'authenticate_user_by_email: {email}')
|
||||
try:
|
||||
with get_db_context(db) as db:
|
||||
|
|
@ -206,7 +209,7 @@ class AuthsTable:
|
|||
except Exception:
|
||||
return None
|
||||
|
||||
def update_user_password_by_id(self, id: str, new_password: str, db: Session | None = None) -> bool:
|
||||
def update_user_password_by_id(self, id: str, new_password: str, db: Optional[Session] = None) -> bool:
|
||||
try:
|
||||
with get_db_context(db) as db:
|
||||
result = db.query(Auth).filter_by(id=id).update({'password': new_password})
|
||||
|
|
@ -215,7 +218,7 @@ class AuthsTable:
|
|||
except Exception:
|
||||
return False
|
||||
|
||||
def update_email_by_id(self, id: str, email: str, db: Session | None = None) -> bool:
|
||||
def update_email_by_id(self, id: str, email: str, db: Optional[Session] = None) -> bool:
|
||||
try:
|
||||
with get_db_context(db) as db:
|
||||
result = db.query(Auth).filter_by(id=id).update({'email': email})
|
||||
|
|
@ -227,7 +230,7 @@ class AuthsTable:
|
|||
except Exception:
|
||||
return False
|
||||
|
||||
def delete_auth_by_id(self, id: str, db: Session | None = None) -> bool:
|
||||
def delete_auth_by_id(self, id: str, db: Optional[Session] = None) -> bool:
|
||||
try:
|
||||
with get_db_context(db) as db:
|
||||
# Delete User
|
||||
|
|
@ -244,4 +247,4 @@ class AuthsTable:
|
|||
return False
|
||||
|
||||
|
||||
Auths = AuthsTable()
|
||||
Auths = AuthsTable()
|
||||
|
|
@ -1,79 +1,91 @@
|
|||
import asyncio
|
||||
import re
|
||||
import uuid
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import urllib
|
||||
import uuid
|
||||
from ssl import CERT_NONE, CERT_REQUIRED, PROTOCOL_TLS
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
from ldap3 import NONE, Connection, Server, Tls
|
||||
from ldap3.utils.conv import escape_filter_chars
|
||||
from open_webui.config import (
|
||||
ENABLE_PASSWORD_AUTH,
|
||||
OAUTH_MERGE_ACCOUNTS_BY_EMAIL,
|
||||
OAUTH_PROVIDERS,
|
||||
OPENID_END_SESSION_ENDPOINT,
|
||||
OPENID_PROVIDER_URL,
|
||||
)
|
||||
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
|
||||
from open_webui.env import (
|
||||
ENABLE_INITIAL_ADMIN_SIGNUP,
|
||||
ENABLE_OAUTH_TOKEN_EXCHANGE,
|
||||
WEBUI_AUTH,
|
||||
WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
WEBUI_AUTH_COOKIE_SECURE,
|
||||
WEBUI_AUTH_SIGNOUT_REDIRECT_URL,
|
||||
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
|
||||
WEBUI_AUTH_TRUSTED_GROUPS_HEADER,
|
||||
WEBUI_AUTH_TRUSTED_NAME_HEADER,
|
||||
WEBUI_AUTH_TRUSTED_ROLE_HEADER,
|
||||
)
|
||||
from open_webui.internal.db import get_session
|
||||
import urllib
|
||||
|
||||
|
||||
from open_webui.models.auths import (
|
||||
AddUserForm,
|
||||
ApiKey,
|
||||
Auths,
|
||||
Token,
|
||||
LdapForm,
|
||||
SigninForm,
|
||||
SigninResponse,
|
||||
SignupForm,
|
||||
Token,
|
||||
UpdatePasswordForm,
|
||||
)
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.oauth_sessions import OAuthSessions
|
||||
from open_webui.models.users import (
|
||||
UpdateProfileForm,
|
||||
UserModel,
|
||||
UserProfileImageResponse,
|
||||
Users,
|
||||
UpdateProfileForm,
|
||||
UserStatus,
|
||||
)
|
||||
from open_webui.utils.access_control import get_permissions, has_permission
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.oauth_sessions import OAuthSessions
|
||||
|
||||
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
|
||||
from open_webui.env import (
|
||||
WEBUI_AUTH,
|
||||
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
|
||||
WEBUI_AUTH_TRUSTED_NAME_HEADER,
|
||||
WEBUI_AUTH_TRUSTED_GROUPS_HEADER,
|
||||
WEBUI_AUTH_TRUSTED_ROLE_HEADER,
|
||||
WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
WEBUI_AUTH_COOKIE_SECURE,
|
||||
WEBUI_AUTH_SIGNOUT_REDIRECT_URL,
|
||||
ENABLE_INITIAL_ADMIN_SIGNUP,
|
||||
ENABLE_OAUTH_TOKEN_EXCHANGE,
|
||||
AIOHTTP_CLIENT_SESSION_SSL,
|
||||
)
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi.responses import RedirectResponse, Response, JSONResponse
|
||||
from open_webui.config import (
|
||||
OPENID_PROVIDER_URL,
|
||||
OPENID_END_SESSION_ENDPOINT,
|
||||
ENABLE_OAUTH_SIGNUP,
|
||||
ENABLE_LDAP,
|
||||
ENABLE_PASSWORD_AUTH,
|
||||
OAUTH_PROVIDERS,
|
||||
OAUTH_MERGE_ACCOUNTS_BY_EMAIL,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
|
||||
from open_webui.utils.misc import parse_duration, validate_email_format
|
||||
from open_webui.utils.auth import (
|
||||
create_api_key,
|
||||
create_token,
|
||||
decode_token,
|
||||
get_admin_user,
|
||||
get_current_user,
|
||||
get_http_authorization_cred,
|
||||
get_password_hash,
|
||||
get_verified_user,
|
||||
invalidate_token,
|
||||
validate_password,
|
||||
verify_password,
|
||||
decode_token,
|
||||
invalidate_token,
|
||||
create_api_key,
|
||||
create_token,
|
||||
get_admin_user,
|
||||
get_verified_user,
|
||||
get_current_user,
|
||||
get_password_hash,
|
||||
get_http_authorization_cred,
|
||||
)
|
||||
from open_webui.utils.groups import apply_default_group_assignment
|
||||
from open_webui.utils.misc import parse_duration, validate_email_format
|
||||
from open_webui.utils.rate_limit import RateLimiter
|
||||
from open_webui.utils.redis import get_redis_client
|
||||
from open_webui.utils.webhook import post_webhook
|
||||
from pydantic import BaseModel
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.utils.webhook import post_webhook
|
||||
from open_webui.utils.access_control import get_permissions, has_permission
|
||||
from open_webui.utils.groups import apply_default_group_assignment
|
||||
|
||||
from open_webui.utils.redis import get_redis_client
|
||||
from open_webui.utils.rate_limit import RateLimiter
|
||||
|
||||
|
||||
from typing import Optional, List
|
||||
|
||||
from ssl import CERT_NONE, CERT_REQUIRED, PROTOCOL_TLS
|
||||
|
||||
from ldap3 import Server, Connection, NONE, Tls
|
||||
from ldap3.utils.conv import escape_filter_chars
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
@ -107,7 +119,7 @@ def create_session_response(request: Request, user, db, response: Response = Non
|
|||
)
|
||||
|
||||
if set_cookie and response:
|
||||
datetime_expires_at = datetime.datetime.fromtimestamp(expires_at, datetime.UTC) if expires_at else None
|
||||
datetime_expires_at = datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc) if expires_at else None
|
||||
max_age = int(expires_delta.total_seconds()) if expires_delta else None
|
||||
response.set_cookie(
|
||||
key='token',
|
||||
|
|
@ -140,14 +152,14 @@ def create_session_response(request: Request, user, db, response: Response = Non
|
|||
|
||||
|
||||
class SessionUserResponse(Token, UserProfileImageResponse):
|
||||
expires_at: int | None = None
|
||||
permissions: dict | None = None
|
||||
expires_at: Optional[int] = None
|
||||
permissions: Optional[dict] = None
|
||||
|
||||
|
||||
class SessionUserInfoResponse(SessionUserResponse, UserStatus):
|
||||
bio: str | None = None
|
||||
gender: str | None = None
|
||||
date_of_birth: datetime.date | None = None
|
||||
bio: Optional[str] = None
|
||||
gender: Optional[str] = None
|
||||
date_of_birth: Optional[datetime.date] = None
|
||||
|
||||
|
||||
@router.get('/', response_model=SessionUserInfoResponse)
|
||||
|
|
@ -178,7 +190,7 @@ async def get_session_user(
|
|||
response.set_cookie(
|
||||
key='token',
|
||||
value=token,
|
||||
expires=(datetime.datetime.fromtimestamp(expires_at, datetime.UTC) if expires_at else None),
|
||||
expires=(datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc) if expires_at else None),
|
||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||
|
|
@ -617,7 +629,7 @@ async def signin(
|
|||
name = request.headers.get(WEBUI_AUTH_TRUSTED_NAME_HEADER, email)
|
||||
try:
|
||||
name = urllib.parse.unquote(name, encoding='utf-8')
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if not Users.get_user_by_email(email.lower(), db=db):
|
||||
|
|
@ -1024,7 +1036,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
|
|||
|
||||
class AdminConfig(BaseModel):
|
||||
SHOW_ADMIN_DETAILS: bool
|
||||
ADMIN_EMAIL: str | None = None
|
||||
ADMIN_EMAIL: Optional[str] = None
|
||||
WEBUI_URL: str
|
||||
ENABLE_SIGNUP: bool
|
||||
ENABLE_API_KEYS: bool
|
||||
|
|
@ -1036,15 +1048,15 @@ class AdminConfig(BaseModel):
|
|||
ENABLE_COMMUNITY_SHARING: bool
|
||||
ENABLE_MESSAGE_RATING: bool
|
||||
ENABLE_FOLDERS: bool
|
||||
FOLDER_MAX_FILE_COUNT: int | str | None = None
|
||||
FOLDER_MAX_FILE_COUNT: Optional[int | str] = None
|
||||
ENABLE_CHANNELS: bool
|
||||
ENABLE_MEMORIES: bool
|
||||
ENABLE_NOTES: bool
|
||||
ENABLE_USER_WEBHOOKS: bool
|
||||
ENABLE_USER_STATUS: bool
|
||||
PENDING_USER_OVERLAY_TITLE: str | None = None
|
||||
PENDING_USER_OVERLAY_CONTENT: str | None = None
|
||||
RESPONSE_WATERMARK: str | None = None
|
||||
PENDING_USER_OVERLAY_TITLE: Optional[str] = None
|
||||
PENDING_USER_OVERLAY_CONTENT: Optional[str] = None
|
||||
RESPONSE_WATERMARK: Optional[str] = None
|
||||
|
||||
|
||||
@router.post('/admin/config')
|
||||
|
|
@ -1117,7 +1129,7 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep
|
|||
class LdapServerConfig(BaseModel):
|
||||
label: str
|
||||
host: str
|
||||
port: int | None = None
|
||||
port: Optional[int] = None
|
||||
attribute_for_mail: str = 'mail'
|
||||
attribute_for_username: str = 'uid'
|
||||
app_dn: str
|
||||
|
|
@ -1125,9 +1137,9 @@ class LdapServerConfig(BaseModel):
|
|||
search_base: str
|
||||
search_filters: str = ''
|
||||
use_tls: bool = True
|
||||
certificate_path: str | None = None
|
||||
certificate_path: Optional[str] = None
|
||||
validate_cert: bool = True
|
||||
ciphers: str | None = 'ALL'
|
||||
ciphers: Optional[str] = 'ALL'
|
||||
|
||||
|
||||
@router.get('/admin/config/ldap/server', response_model=LdapServerConfig)
|
||||
|
|
@ -1200,7 +1212,7 @@ async def get_ldap_config(request: Request, user=Depends(get_admin_user)):
|
|||
|
||||
|
||||
class LdapConfigForm(BaseModel):
|
||||
enable_ldap: bool | None = None
|
||||
enable_ldap: Optional[bool] = None
|
||||
|
||||
|
||||
@router.post('/admin/config/ldap')
|
||||
|
|
@ -1323,7 +1335,7 @@ async def token_exchange(
|
|||
# Get sub claim
|
||||
sub = user_data.get(request.app.state.config.OAUTH_SUB_CLAIM or OAUTH_PROVIDERS[provider].get('sub_claim', 'sub'))
|
||||
if not sub:
|
||||
log.warning('Token exchange failed: sub claim missing from user data')
|
||||
log.warning(f'Token exchange failed: sub claim missing from user data')
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Token missing required 'sub' claim",
|
||||
|
|
@ -1331,7 +1343,7 @@ async def token_exchange(
|
|||
|
||||
email = user_data.get(email_claim, '')
|
||||
if not email:
|
||||
log.warning('Token exchange failed: email claim missing from user data')
|
||||
log.warning(f'Token exchange failed: email claim missing from user data')
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail='Token missing required email claim',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue