diff --git a/backend/ee/onyx/server/saml.py b/backend/ee/onyx/server/saml.py index 8d172c59d2..c1706c8cd4 100644 --- a/backend/ee/onyx/server/saml.py +++ b/backend/ee/onyx/server/saml.py @@ -1,5 +1,6 @@ import contextlib import secrets +import string from typing import Any from fastapi import APIRouter @@ -9,7 +10,6 @@ from fastapi import Request from fastapi import Response from fastapi import status from fastapi_users import exceptions -from fastapi_users.password import PasswordHelper from onelogin.saml2.auth import OneLogin_Saml2_Auth # type: ignore from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession @@ -38,6 +38,16 @@ router = APIRouter(prefix="/auth/saml") async def upsert_saml_user(email: str) -> User: + """ + Creates or updates a user account for SAML authentication. + + For new users or users with non-web-login roles: + 1. Generates a secure random password that meets validation criteria + 2. Creates the user with appropriate role and verified status + + SAML users never use this password directly as they authenticate via their + Identity Provider, but we need a valid password to satisfy system requirements. + """ logger.debug(f"Attempting to upsert SAML user with email: {email}") get_async_session_context = contextlib.asynccontextmanager( get_async_session @@ -60,15 +70,41 @@ async def upsert_saml_user(email: str) -> User: user_count = await get_user_count() role = UserRole.ADMIN if user_count == 0 else UserRole.BASIC - fastapi_users_pw_helper = PasswordHelper() - password = fastapi_users_pw_helper.generate() - hashed_pass = fastapi_users_pw_helper.hash(password) + # Generate a secure random password meeting validation requirements + # We use a secure random password since we never need to know what it is + # (SAML users authenticate via their IdP) + secure_random_password = "".join( + [ + # Ensure minimum requirements are met + secrets.choice( + string.ascii_uppercase + ), # at least one uppercase + secrets.choice( + string.ascii_lowercase + ), # at least one lowercase + secrets.choice(string.digits), # at least one digit + secrets.choice( + "!@#$%^&*()-_=+[]{}|;:,.<>?" + ), # at least one special + # Fill remaining length with random chars (mix of all types) + "".join( + secrets.choice( + string.ascii_letters + + string.digits + + "!@#$%^&*()-_=+[]{}|;:,.<>?" + ) + for _ in range(12) + ), + ] + ) + # Create the user with SAML-appropriate settings user = await user_manager.create( UserCreate( email=email, - password=hashed_pass, + password=secure_random_password, # Pass raw password, not hash role=role, + is_verified=True, # SAML users are pre-verified by their IdP ) )