fix: SCIM-provisioned users cannot sign in with SSO

Since v0.12.0 every sign-in needs a stored login record for the account, but users created through SCIM never got one, so SSO sign-in for them failed with "Invalid authentication request." SCIM now creates that record the same way SSO sign-up does, with a random password nobody knows, updates its email when the identity provider changes the user's email over SCIM, and removes it when SCIM deletes the user. On upgrade, a migration adds the record for every existing account that lacks one, without a usable password, so users SCIM already created can sign in with SSO right away and still cannot sign in with a password.

Fixes #32347
This commit is contained in:
Classic298 2026-10-13 16:10:29 +02:00
parent be6f0a6656
commit 7238fa6beb
2 changed files with 46 additions and 8 deletions

View file

@ -0,0 +1,34 @@
"""add missing auth rows
Revision ID: 0bd252b206fb
Revises: c92da83b017f
"""
import sqlalchemy as sa
from alembic import op
revision = '0bd252b206fb'
down_revision = 'c92da83b017f'
branch_labels = None
depends_on = None
_user = sa.table('user', sa.column('id', sa.Text), sa.column('email', sa.Text))
_auth = sa.table(
'auth',
sa.column('id', sa.Text),
sa.column('email', sa.Text),
sa.column('password', sa.Text),
sa.column('active', sa.Boolean),
)
def upgrade() -> None:
# An empty password never verifies, so these accounts stay SSO-only.
users_without_auth = sa.select(_user.c.id, _user.c.email, sa.literal_column("''"), sa.true()).where(
~sa.exists().where(_auth.c.id == _user.c.id)
)
op.execute(_auth.insert().from_select(['id', 'email', 'password', 'active'], users_without_auth))
def downgrade() -> None:
pass

View file

@ -20,12 +20,14 @@ from open_webui.constants import ERROR_MESSAGES
from open_webui.events import EVENTS, publish_event
from open_webui.env import SCIM_AUTH_PROVIDER
from open_webui.internal.db import get_async_session
from open_webui.models.auths import Auths
from open_webui.models.groups import GroupModel, Groups, GroupHierarchyError
from open_webui.models.users import UserModel, Users
from open_webui.utils.auth import (
decode_token,
get_admin_user,
get_current_user,
get_password_hash,
get_verified_user,
)
from pydantic import BaseModel, ConfigDict, Field
@ -620,9 +622,6 @@ async def create_user(
detail=f'User with email {email} already exists',
)
# Create user
user_id = str(uuid.uuid4())
# Parse name if provided
name = user_data.displayName
if user_data.name:
@ -636,10 +635,11 @@ async def create_user(
if user_data.photos and len(user_data.photos) > 0:
profile_image = user_data.photos[0].value
new_user = await Users.insert_new_user(
id=user_id,
name=name,
# Create user
new_user = await Auths.insert_new_auth(
email=email,
password=await get_password_hash(str(uuid.uuid4())),
name=name,
profile_image_url=profile_image,
role='user' if user_data.active else 'pending',
db=db,
@ -651,7 +651,7 @@ async def create_user(
detail='Failed to create user',
)
new_user = await Users.update_user_scim_by_id(user_id, get_scim_provider(), user_data.externalId, db=db)
new_user = await Users.update_user_scim_by_id(new_user.id, get_scim_provider(), user_data.externalId, db=db)
if not new_user:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@ -721,6 +721,8 @@ async def update_user(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Failed to update user',
)
if 'email' in update_data:
await Auths.update_email_by_id(user_id, update_data['email'], db=db)
# Update externalId in the scim field
if user_data.externalId:
@ -830,6 +832,8 @@ async def patch_user(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Failed to update user',
)
if 'email' in update_data:
await Auths.update_email_by_id(user_id, update_data['email'], db=db)
else:
updated_user = user
@ -871,7 +875,7 @@ async def delete_user(
detail=f'User {user_id} not found',
)
success = await Users.delete_user_by_id(user_id, db=db)
success = await Auths.delete_auth_by_id(user_id, db=db)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,