o
    ưi                    @   sp  d Z ddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
mZmZmZmZmZmZmZmZ ddlmZmZmZmZmZ ddlmZ ddlZddlmZ ddlmZ dd	l m!Z! dd
l"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z) ddl*m+Z+ ddl,m-Z-m.Z.m/Z/ ddl0m1Z1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9m:Z:m;Z;m<Z< ddl=m>Z>m?Z? ddl@mAZA ddlBmCZC ddlDmEZE ddlFmGZGmHZH ddlImJZJ ddlKmLZL ddlMmNZN ddlOmPZP ddlQmRZRmSZS ddlTmUZUmVZV ddlWmXZXmYZYmZZZ ddl[m\Z\m]Z]m^Z^m_Z_ ddl`maZambZb ddlcmdZdmeZemfZfmgZgmhZhmiZi ddlcT ddljmkZk e
r*ddllmmZm ndd l	mZm e Znd!eeo d"eeo fd#d$Zpd%eeo d&d'd"ee3 fd(d)Zq	dd*eeo d+eeC d,eemerdf d&ed' d"df
d-d.Zsenjtd/d0gd1d2			dd3ed4eeo d5eeo d6eeo fd7d8Zu			dd9eCd+eeC d&ed' d:ed; d"eXf
d<d=Zvd>eod?eod"eeoeeo eoeoeoewf fd@dAZxd"ed; fdBdCZyd"ed' fdDdEZzd3ed9eCd+eeC d>eod?eod"eeemerf eer f fdFdGZ{dHdI Z|dJee7e2f dKeeo fdLdMZ}dNdO Z~dPeeo dQeeo dRe\dSe!dTe]d"ee2 fdUdVZ	dd,eeXemerf dRe\dSe!dTe]dQeeo dWee: dXeeo d"eee2e7f  fdYdZZd[eeo d"ewfd\d]Zd,eed^emerf  dQeeo dPeeo d"erfd_d`ZdJeee2e7f  dWee: d"ee: fdadbZdceodPeodRee\ fdddeZenjtdfd0gd1d2dd3edgeeo fdhdiZ			dd3ed5eeo d6eeo d,eeemerf  fdjdkZenjtdld0gd1d2ddmeodneeo fdodpZ	ddqeeemerf  dWee: d"e7fdrdsZenjtdtd0gd1eeEgdud3efdvdwZenjtdxd0geeEgdydzd{ ZG d|d} d}ZG d~d dZG dd dZenjtdd0gd1d2d3efddZenjtdd0gd1d2d3efddZdS )a^  
Has all /sso/* routes

/sso/key/generate - handles user signing in with SSO and redirects to /sso/callback
/sso/callback - returns JWT Redirect Response that redirects to LiteLLM UI

/sso/debug/login - handles user signing in with SSO and redirects to /sso/debug/callback
/sso/debug/callback - returns the OpenID object returned by the SSO provider
    N)deepcopy)	TYPE_CHECKINGAnyDictListLiteralOptionalTupleUnioncast)	APIRouterDependsHTTPExceptionRequeststatus)RedirectResponse)verbose_proxy_logger)uuid)	DualCache)LITELLM_UI_SESSION_DURATIONMAX_SPENDLOG_ROWS_TO_QUERY%MICROSOFT_USER_DISPLAY_NAME_ATTRIBUTEMICROSOFT_USER_EMAIL_ATTRIBUTE#MICROSOFT_USER_FIRST_NAME_ATTRIBUTEMICROSOFT_USER_ID_ATTRIBUTE"MICROSOFT_USER_LAST_NAME_ATTRIBUTE)get_nested_value)AsyncHTTPHandlerget_async_httpx_clienthttpxSpecialProvider)CommonProxyErrorsLiteLLM_UserTableLitellmUserRolesMemberNewTeamRequestNewUserRequestNewUserResponseProxyErrorTypesProxyExceptionSSOUserDefinedValuesTeamMemberAddRequestUserAPIKeyAuth)ExperimentalUIJWTTokenget_user_object)_has_user_setup_sso
JWTHandler)user_api_key_auth)admin_ui_disabledshow_missing_vars_in_env)jwt_display_template)	html_form)new_user)CustomMicrosoftSSO)check_is_admin_only_accesshas_admin_ui_access)new_teamteam_member_add)CustomOpenIDget_litellm_user_roleis_valid_litellm_user_role)PrismaClientProxyLoggingget_custom_urlget_server_root_path)get_secret_boolstr_to_bool)DefaultTeamSSOParams)MicrosoftGraphAPIUserGroupDirectoryObject"MicrosoftGraphAPIUserGroupResponseMicrosoftServicePrincipalTeamRoleMappingsTeamMappings)*)ParsedOpenIDResult)OpenID)r   emailreturnc                 C   s"   | du rdS t | tr|  S | S )a  
    Normalize email address to lowercase for consistent storage and comparison.

    Email addresses should be treated as case-insensitive for SSO purposes,
    even though RFC 5321 technically allows case-sensitive local parts.
    This prevents issues where SSO providers return emails with different casing
    than what's stored in the database.

    Args:
        email: Email address to normalize, can be None

    Returns:
        Lowercased email address, or None if input is None
    N)
isinstancestrlower)rN    rS   `/home/app/Keep/.python/lib/python3.10/site-packages/litellm/proxy/management_endpoints/ui_sso.pynormalize_emailj   s   rU   user_groupsrole_mappingsrI   c              	   C   s   |j s|jS tjtjtjtjg}t| trt	| nt	 }|D ]+}||j v rI|j | }t|trI|
t	|rItd|  d|j d|  |  S qtd|  d|j  |jS )a  
    Determine the highest privilege role for a user based on their groups.

    Role hierarchy (highest to lowest):
    - proxy_admin
    - proxy_admin_viewer
    - internal_user
    - internal_user_viewer

    Args:
        user_groups: List of group names from the SSO token
        role_mappings: RoleMappings configuration object

    Returns:
        The highest privilege role found, or default_role if no matches, or None
    zUser groups z matched role 'z' via groups: z6 did not match any role mappings, using default_role: )rolesdefault_roler"   PROXY_ADMINZPROXY_ADMIN_VIEW_ONLYINTERNAL_USERINTERNAL_USER_VIEW_ONLYrP   listsetintersectionr   debugvalue)rV   rW   Zrole_hierarchyZuser_groups_setroleZrole_groupsrS   rS   rT   determine_role_from_groups~   s.   

rc   access_token_strsso_jwt_handlerresultc                 C   s  | r|rddl }z|j| ddid}W n |jjy&   td Y dS w |rWt|tr@|dg }|s?|	|}||d< n|rHt
|dg ng }|sW|	|}t|d| t|tra|dnt
|dd}|du rd}	|dur|jr|j}
t||
}g }t|trd	d
 |D }nt|trdd
 |dD }n	|durt|g}|rt||}	td|	 d| d n|jr|j}	|	du rtdd}t||}|durt|}	td|	 d| d |	durt|tr|	|d< nt|d|	 td|	 d dS dS dS dS dS )a  
    Process SSO JWT access token and extract team IDs and user role if available.

    This function decodes the JWT access token and extracts team IDs and user
    role, then sets them on the result object. Role extraction from the access
    token is needed because some SSO providers (e.g., Keycloak) do not include
    role claims in the UserInfo endpoint response.

    Args:
        access_token_str: The JWT access token string
        sso_jwt_handler: SSO-specific JWT handler for team ID extraction
        result: The SSO result object to update with team IDs and role
        role_mappings: Optional role mappings configuration for group-based role determination
    r   Nverify_signatureFoptionszYAccess token is not a valid JWT (possibly an opaque token), skipping JWT-based extractionteam_ids	user_rolec                 S      g | ]}t |qS rS   rQ   .0grS   rS   rT   
<listcomp>       z0process_sso_jwt_access_token.<locals>.<listcomp>c                 S      g | ]
}|  r|  qS rS   striprn   rS   rS   rT   rq      s
    ,Determined role 'z' from access token groups '' using role_mappingsGENERIC_USER_ROLE_ATTRIBUTErb   zExtracted role 'z' from access token field ''zSet user_role='z' from JWT access token)jwtdecode
exceptionsDecodeErrorr   r`   rP   dictgetget_team_ids_from_jwtgetattrsetattrrX   group_claimr   r]   rQ   splitrc   rY   osgetenvr=   )rd   re   rf   rW   r{   Zaccess_token_payloadZresult_team_idsrj   Zexisting_rolerk   r   user_groups_rawrV    generic_user_role_attribute_nameZuser_role_from_tokenrS   rS   rT   process_sso_jwt_access_token   s   











$*r   z/sso/key/generateZexperimentalF)tagsinclude_in_schemarequestsourcekeyexisting_keyc                    s  ddl m}m}m} tdd}tdd}tdd}	td}
|
dur0t|
d}|r0t S |dus<|dus<|	durj|d	urj|dur^|jj	
 I dH }|r]|d
kr]tdtjdtjdnttjjtjdtjdt }|durs|S td}tj| d|d}tj|||d}|durzddlm} |j| dI dH W S  ty   tdw tj|||	dd	u rtd|  tj||||	|dI dH S |durddl m!} |t"ddS ddl m!} |t"ddS )
    Create Proxy API Keys using Google Workspace SSO. Requires setting PROXY_BASE_URL in .env
    PROXY_BASE_URL should be the your deployed proxy endpoint, e.g. PROXY_BASE_URL="https://litellm-production-7002.up.railway.app/"
    Example:
    r   )premium_userprisma_client"user_custom_ui_sso_sign_in_handlerMICROSOFT_CLIENT_IDNGOOGLE_CLIENT_IDGENERIC_CLIENT_IDZDISABLE_ADMIN_UI)ra   T   a  You must be a LiteLLM Enterprise user to use SSO for more than 5 users. If you have a license please set `LITELLM_LICENSE` in your env. If you want to obtain a license meet with us here: https://calendly.com/d/cx9p-5yf-2nm/litellm-introductions You are seeing this error message because You set one of `MICROSOFT_CLIENT_ID`, `GOOGLE_CLIENT_ID`, or `GENERIC_CLIENT_ID` in your env. Please unset thisr   messagetypeparamcodeZUI_USERNAMEsso/callback)r   sso_callback_router   )r   r   r   )EnterpriseCustomSSOHandler)r   zYEnterprise features are not available. Custom UI SSO sign-in requires LiteLLM Enterprise.microsoft_client_idgoogle_client_idgeneric_client_idzRedirecting to SSO login for )redirect_urlr   r   r   stateHTMLResponse   contentstatus_code)#litellm.proxy.proxy_serverr   r   r   r   r   rD   r2   dblitellm_usertablecountr(   r'   
auth_errorr   HTTP_403_FORBIDDENr    db_not_connected_errorra   r3   SSOAuthenticationHandlerget_redirect_url_for_sso_get_cli_stateZ0litellm_enterprise.proxy.auth.custom_sso_handlerr   Zhandle_custom_ui_sso_sign_inImportError
ValueErrorshould_use_sso_handlerr   infoget_sso_login_redirectfastapi.responsesr   r5   )r   r   r   r   r   r   r   r   r   r   Z_disable_ui_flagZis_disabledZtotal_usersZmissing_env_varsZui_usernamer   Z	cli_stater   r   rS   rS   rT   google_login   s   


r   jwt_handlerteam_mappingsrJ   c                 C   s  t dd}t dd}t dd}t dd}t d	d
}	t dd}
t dd}t dd }td| d|  g }|d urN|tt| }|| |d urv|jd urvt	tt| |jg d}|ru|| td|j d|  n|tt| }|| d }|d ur|j
 dv r|j}t	| |}g }t|trdd |D }nt|trdd |dD }n	|d urt|g}|rt||}td|r|jnd  d| d n|j}td| d|j  |d u rt	| |}|d urt|}|d ur|}td|j d | d! d }|r*i }|dD ]}| }t	| |||< qtt	| |t	| |tt	| |t	| |t	| |	t	| |
|||d"	S )#NZGENERIC_USER_ID_ATTRIBUTEZpreferred_usernameZ#GENERIC_USER_DISPLAY_NAME_ATTRIBUTEsubZGENERIC_USER_EMAIL_ATTRIBUTErN   Z!GENERIC_USER_FIRST_NAME_ATTRIBUTE
first_nameZ GENERIC_USER_LAST_NAME_ATTRIBUTE	last_nameZGENERIC_USER_PROVIDER_ATTRIBUTEproviderry   rb   ZGENERIC_USER_EXTRA_ATTRIBUTESz! generic_user_id_attribute_name: z%
 generic_user_email_attribute_name: )dataZkey_pathdefaultz:Loaded team_ids from DB team_mappings.team_ids_jwt_field='z': )genericZoktac                 S   rl   rS   rm   rn   rS   rS   rT   rq     rr   z.generic_response_convertor.<locals>.<listcomp>c                 S   rs   rS   rt   rn   rS   rS   rT   rq     s    rv   rw   z' from groups 'rx   zNo groups found in 'z', using default_role: Found valid LitellmUserRoles 'z' from SSO attribute 'rz   )	iddisplay_namerN   r   r   r   rj   rk   extra_fields)r   r   r   r`   r   r   r   extendteam_ids_jwt_fieldr   r   rR   r   rP   r]   rQ   r   rc   ra   rY   r=   ru   r<   rU   )responser   re   rW   r   Zgeneric_user_id_attribute_nameZ(generic_user_display_name_attribute_nameZ!generic_user_email_attribute_nameZ&generic_user_first_name_attribute_nameZ%generic_user_last_name_attribute_nameZgeneric_provider_attribute_namer   Zgeneric_user_extra_attributesZ	all_teamsrj   Zteam_ids_from_db_mappingrk   r   r   rV   Zuser_role_from_ssorb   r   	attr_namerS   rS   rT   generic_response_convertor  s   










r   r   r   c                 C   s
  t dd}t ddd}t dd}t dd}t dd}t d	d
 dk}|du r9tdtjdtjd|du rGtdtjdtjd|du rUtdtjdtjd|du rctdtjdtjdt	
d| d| d|  t	
d| d|  d ||||||fS )z5Setup and validate Generic SSO environment variables.GENERIC_CLIENT_SECRETNGENERIC_SCOPEopenid email profile GENERIC_AUTHORIZATION_ENDPOINTGENERIC_TOKEN_ENDPOINTGENERIC_USERINFO_ENDPOINTZGENERIC_INCLUDE_CLIENT_IDfalsetrue2GENERIC_CLIENT_SECRET not set. Set it in .env filer   ;GENERIC_AUTHORIZATION_ENDPOINT not set. Set it in .env file3GENERIC_TOKEN_ENDPOINT not set. Set it in .env file6GENERIC_USERINFO_ENDPOINT not set. Set it in .env fileauthorization_endpoint: 
token_endpoint: 
userinfo_endpoint: GENERIC_REDIRECT_URI: 
GENERIC_CLIENT_ID: 
)r   r   r   rR   r(   r'   r   r   HTTP_500_INTERNAL_SERVER_ERRORr   r`   )r   r   generic_client_secretgeneric_scopegeneric_authorization_endpointgeneric_token_endpointgeneric_userinfo_endpointgeneric_include_client_idrS   rS   rT   _setup_generic_sso_env_vars  s`   r   c               
      s  d} zcddl m} |d}|jjjddidI dH }|rX|jr[t|j}|d}|r^dd	lm	} t
|tr?|di |} nt
||rF|} | ra| jrdtd
| j d W | S W | S W | S W | S W | S W | S  ty } ztd| d W Y d}~| S d}~ww )z/Setup team mappings from SSO database settings.Nr   get_prisma_client_or_throw7Prisma client is None, connect a database to your proxyr   
sso_configwherer   )rJ   z/Loaded team_mappings with team_ids_jwt_field: 'rz   z,Could not load team_mappings from database: z,. Continuing with config-based team mapping.rS   )litellm.proxy.utilsr   r   litellm_ssoconfigfind_uniquesso_settingsr   r   /litellm.types.proxy.management_endpoints.ui_ssorJ   rP   r   r   r`   	Exception)r   r   r   sso_db_recordsso_settings_dictZteam_mappings_datarJ   erS   rS   rT   _setup_team_mappingsS  sR   





		
r   c               
      s  d} zPddl m} |d}|jjjddidI dH }|rR|jrRt|j}|d}|rRdd	lm	} t
|tr?|di |} nt
||rF|} | rRtd
| j d W n tyn } ztd| d W Y d}~nd}~ww tdd}tdd}	tdd}
|durtd ddl}z.||}t
|trdd	lm	} d|	|
|d}|di |} td| j d | W S W | S  ty } ztd| d W Y d}~| S d}~ww | S )z/Setup role mappings from SSO database settings.Nr   r   r   r   r   r   rW   )rI   z#Loaded role_mappings for provider 'rz   z,Could not load role_mappings from database: z&. Continuing with existing role logic.ZGENERIC_ROLE_MAPPINGS_ROLESZ!GENERIC_ROLE_MAPPINGS_GROUP_CLAIMZ"GENERIC_ROLE_MAPPINGS_DEFAULT_ROLEzAFound role_mappings for generic provider in environment variablesr   )r   r   rY   rX   z5Loaded role_mappings from environments for provider 'z'.z9Error decoding role mappings from environment variables: rS   )r   r   r   r   r   r   r   r   r   rI   rP   r   r`   r   r   r   r   astliteral_eval	TypeErrorwarning)rW   r   r   r   r   Zrole_mappings_datarI   r   Zgeneric_role_mappingsZ!generic_role_mappings_group_claimZ#generic_role_mappoings_default_roler   Zgeneric_user_role_mappings_datarS   rS   rT   _setup_role_mappingsy  s   







r   c              
      sr  ddl m} ddlm} d t||\}}}	}
}}||	|
|d}t I d H t I d H  fdd}|d||d}||||d	|d
}td t	
dd }i }|d urs|d}|D ]}| }|rr|d\}}|||< q_z |j| tj| |dI d H |dI d H }|j}t||d W n ty } ztd| d|  |d }~ww td| |pi fS )Nr   DiscoveryDocumentcreate_providerZauthorization_endpointZtoken_endpointZuserinfo_endpointc                    s   | t |  dS )N)r   r   re   rW   r   )r   )r   clientr   received_responserW   re   r   rS   rT   response_convertor  s   z4get_generic_sso_response.<locals>.response_convertoroidc)namediscovery_documentr  T	client_idclient_secretredirect_uriallow_insecure_httpscopez&calling generic_sso.verify_and_processZGENERIC_SSO_HEADERSrv   =)r   r   )paramsheaders)rW   z,Error verifying and processing generic SSO: z. Passed in headers: zgeneric result: %s)fastapi_sso.sso.baser  fastapi_sso.sso.genericr  r   r   r   r   r`   r   r   r   ru   verify_and_processr   !prepare_token_exchange_parametersaccess_tokenr   r   	exception)r   r   re   r   r   r  r  r   r   r   r   r   r   	discoveryr  SSOProvidergeneric_ssoZadditional_generic_sso_headersZ#additional_generic_sso_headers_dictZ$additional_generic_sso_headers_splitheaderr   ra   rf   rd   r   rS   r  rT   get_generic_sso_response  s   




	
r  c              
      sp   zt |jdd}t|| d}t|ttjddI dH W S  ty7 } zt	d|  W Y d}~dS d}~ww )z,Create a task for adding a member to a team.user)user_idrb   )memberteam_idrk   r   user_api_key_dictN3[Non-Blocking] Error trying to add sso user to db: )
r#   r!  r*   r;   r+   r"   rZ   r   r   r`   )r#  	user_infor"  Zteam_member_add_requestr   rS   rS   rT   create_team_member_add_task!  s"   
r)  r(  	sso_teamsc              
      s    j dur	 j ng }t|t| }t|}g } fdd|D }ztj| I dH  W dS  tyH } ztd|  W Y d}~dS d}~ww )zq
    - Get missing teams (diff b/w user_info.team_ids and sso_teams)
    - Add missing user to missing teams
    Nc                    s   g | ]}t | qS rS   )r)  )ro   r#  r(  rS   rT   rq   ?  s    z+add_missing_team_member.<locals>.<listcomp>r'  )teamsr^   r]   asynciogatherr   r   r`   )r(  r*  
user_teamsZmissing_teamsZmissing_teams_listtasksr   rS   r+  rT   add_missing_team_member3  s    
r1  c                  C   s:   t j} | d u r	dS | dpi }|dpg }td|v S )NFpersonal_key_generationallowed_user_rolesZproxy_admin)litellmkey_generation_settingsr   bool)r5  r2  r3  rS   rS   rT   ,get_disabled_non_admin_personal_key_creationL  s   r7  r!  
user_emailr   user_api_key_cacheproxy_logging_objc              
      sb   zt | |||dd || dI d H }W |S  ty0 } ztd|  d }W Y d }~|S d }~ww )NF)r!  r8  r   r9  Zuser_id_upsertZparent_otel_spanr:  sso_user_idzError getting user object: )r-   r   r   r`   )r!  r8  r   r9  r:  r(  r   rS   rS   rT   get_existing_user_info_from_dbW  s&   r<  user_defined_valuesalternate_user_idc              
      sP  zg }|d ur| | t| ts't| dd }|d ur&t|tr&| | n| dd }|d ur;t|tr;| | tt| tsGt| dd n| dd }d }	|D ]}
t|
||||dI d H }	|	d urf nqRt	d|	 dt
j  tj| |	|||dI d H }	tj| |	dI d H  |	W S  ty } ztd|  W Y d }~d S d }~ww )	Nr   rN   )r!  r8  r   r9  r:  zuser_info: z(; litellm.default_internal_user_params: )rf   r(  r8  r=  r   )rf   r(  r'  )appendrP   r   r   rQ   r   rU   r<  r   r`   r4  default_internal_user_paramsr   upsert_sso_user#add_user_to_teams_from_sso_responser   r  )rf   r   r9  r:  r8  r=  r>  Zpotential_user_idsZ_idr(  r!  r   rS   rS   rT   get_user_info_from_dbp  sh   	




rC  sso_rolec                 C   s.   | du rdS t | std|  d dS dS )zLreturns true if SSO upsert should use the 'role' defined on the SSO responseNFz
SSO role 'zo' is not a valid LiteLLM user role. Ignoring role from SSO response. See LitellmUserRoles enum for valid roles.T)r>   r   r`   )rD  rS   rS   rT   "_should_use_role_from_sso_response  s   
rE  r<   c                 C   s^   dt |i}t| dd}|dur-t|tr|jn|}t|r-||d< td| d|  |S )aI  
    Build the update data dictionary for SSO user upsert.

    Args:
        result: The SSO response containing user information
        user_email: The user's email from SSO
        user_id: The user's ID for logging purposes

    Returns:
        dict: Update data containing user_email and optionally user_role if valid
    r8  rk   NzUpdating user z role from SSO: )rU   r   rP   r"   ra   rE  r   r   )rf   r8  r!  update_datarD  Zsso_role_strrS   rS   rT   _build_sso_user_update_data  s   rG  c                 C   s   |d u rd S | d ur| j d ur| j |d< |d}| r| jnd }t|r1td| d| d n#| d u s:| jd u rFtjj|d< t	d n| j|d< t	d| j  | d uret
| dre| jre| j|d< |S )	Nr!  rk   zUsing SSO role: z (DB role was: )z?No SSO or DB role found, using default: INTERNAL_USER_VIEW_ONLYzUsing DB role: models)r!  r   rk   rE  r   r   r"   r\   ra   r`   hasattrrI  )r(  r=  rD  Zdb_rolerS   rS   rT   1apply_user_info_values_to_sso_user_defined_values  s*   



rK  rk   c                    sd   t d}|dur0||kr0| r| tjjkr| S |r,|jjjd|idtjjidI dH  tjj} | S )z`
    - Check if user role in DB is admin
    - If not, update user role in DB to admin role
    ZPROXY_ADMIN_IDNr!  rk   r   r   )r   r   r"   rZ   ra   r   r   update)rk   r!  r   Zproxy_admin_idrS   rS   rT   "check_and_update_if_proxy_admin_id  s   

rN  z/sso/callbackr   c                    s2  t d|  ddlm} ddlm} ddlm} ddlm	}m
}m}m}m}	 |du r5tdtjjd	d}
|d
d}|dur]t|tr]| }
|
j||	||d
i ddddd tdd}tdd}tdd}d}|du rtdtjdtjdtj| dd}t d|  d}|durtj | ||dI dH }n"|durt!j"| ||dI dH }n|durt#| ||||
dI dH \}}|du rtddd	|r|$| dr|%dd}t&|dkr|d nd}t&|dkr|d nd}t d| d |  t'| |||d!I dH S tj(|| |||d"I dH S )#zVerify loginz"Starting SSO callback with state: r   ) LITELLM_CLI_SESSION_TOKEN_PREFIXLiteLLM_JWTAuthr/   )general_settingsr   
master_keyr   r9  N  r   detailui_access_modesso_group_jwt_fieldr   r   r9  Zlitellm_jwtauthZleewayr   r   r   zMaster Key not set for Proxy. Please set Master Key to use Admin UI. Set `LITELLM_MASTER_KEY` in .env or set general_settings:master_key in config.yaml.  https://docs.litellm.ai/docs/proxy/virtual_keys. If set, use `--detailed_debug` to debug issue.rS  r   r   r   r   Redirecting to )r   r   r   )r   r   r   r   r   r   r   re     z$Result not returned by SSO provider.:      z#CLI SSO callback detected for key: , existing_key: )r   r   r   rf   )rf   r   r  r   rW  ))r   r   litellm.constantsrO  litellm.proxy._typesrQ  litellm.proxy.auth.handle_jwtr0   r   rR  r   rS  r   r9  r   r    r   ra   r   rP   r   update_environmentr   r   r(   r'   r   r   r   r   r   GoogleSSOHandlerget_google_callback_responseMicrosoftSSOHandlerget_microsoft_callback_responser  
startswithr   lencli_sso_callback!get_redirect_response_from_openid)r   r   rO  rQ  r0   rR  r   rS  r   r9  re   rW  r   r   r   r  r   rf   Zstate_partskey_idr   rS   rS   rT   auth_callback$  s   rp  c              
      sz  t d| d|  ddlm}m}m} |r|ds#tddd|d	u r/td
tj	j
d|d	u r9td
ddttttf |}tj|d}t d|  zt|||||dd	|ddI d	H }	|	d	u rotd
ddddlm}
 g }t|	dr|	jrt|	jtr|	jng }g }z*|r|jjjdd|iidI d	H }|D ]}| }||d|dd qW n ty } zt  d|  W Y d	}~nd	}~ww |	j!|	j"t|	dr|	j#ng |d||d}|
 d| }|j$||dd t d|	j! d | d!t%|  dd"l&m'} dd#l(m)} | }||d$d%W S  ty< } zt  d&|  td
d't*| dd	}~ww )(zDCLI SSO callback - stores session info for JWT generation on pollingzCLI SSO callback for key: rb  r   )r   r:  r9  sk-  zAInvalid key parameter. Must be a valid key ID starting with 'sk-'rU  NrT  z<SSO authentication failed - no result returned from provider)rf   zparsed_openid_result: r8  r!  rf   r   r9  r:  r8  r=  r>  z,Failed to retrieve user information from SSO CLI_SSO_SESSION_CACHE_KEY_PREFIXr,  r#  inr   
team_aliasr#  rw  z1Error fetching team details for CLI SSO session: rI  )r!  rk   rI  r8  r,  team_detailsr_  X  r   ra   ttlz!Stored CLI SSO session for user: z	, teams: z, num_teams: r   )render_cli_sso_success_pager   r   zError with CLI SSO callback: zFailed to process CLI SSO: )+r   r   r   r   r:  r9  rk  r   r    r   ra   r   r
   rM   r   r   "_get_user_email_and_id_from_resultr`   rC  r   rc  ru  rJ  r,  rP   r]   r   litellm_teamtableZ	find_many
model_dumpr?  r   errorr!  rk   rI  Z	set_cacherl  r   r   Z5litellm.proxy.common_utils.html_forms.cli_sso_successr}  rQ   )r   r   r   rf   r   r:  r9  Zresult_non_noneparsed_openid_resultr(  ru  r,  ry  Zprisma_teamsZteam_rowZ	team_dictr   session_data	cache_keyr   r}  html_contentrS   rS   rT   rm    s   


rm  z/sso/cli/poll/{key_id}ro  r#  c                    s  ddl m} ddlm} ddlm} | dstdddz| d	|  }|j|d
}|r|	dg }|	d}|d }	t
d|	 d| d| dt|  |du rt|dkrt
d|	 d|  d}
t|trq|rq|}
n	|rzdd |D }
d|	||
ddW S |dur||vrtdd| d| dnt|dkr|d nd}t|	|d |	dg tjd }|j||d!}|j|d
 t
d"|	 d#|  d||	|||d$W S d%d&iW S  ty } zt
d'|  td(d)t| dd}~ww )*a  
    CLI polling endpoint - retrieves session from cache and generates JWT.

    Flow:
    1. First poll (no team_id): Returns teams list without generating JWT
    2. Second poll (with team_id): Generates JWT with selected team and deletes session

    Args:
        key_id: The session key ID
        team_id: Optional team ID to assign to the JWT. If provided, must be one of user's teams.
    r   rt  )r,   )r9  rq  rr  zInvalid key ID formatrU  r_  r   r,  ry  r!  zCLI poll: user=z
, team_id=z, user_teams=z, num_teams=Nra  zReturning teams list for user z to select from: c                 S   s   g | ]}|d dqS )Nrx  rS   )ro   trS   rS   rT   rq   <  s    
z cli_poll_key.<locals>.<listcomp>readyT)r   r!  r,  ry  Zrequires_team_selectioni  zUser does not belong to team: z. Available teams: rk   rI  r!  rk   rI  
max_budget)r(  r#  zCLI JWT generated for user: z, team: )r   r   r!  r#  r,  ry  r   pendingzError polling for CLI JWT: rT  zError checking session status: )rc  ru  litellm.proxy.auth.auth_checksr,   r   r9  rk  r   Z	get_cacher   r   r   rl  rP   r]   r!   r4  max_ui_session_budgetZget_cli_jwt_auth_tokenZdelete_cacher   r  rQ   )ro  r#  ru  r,   r9  r  r  r/  Zuser_team_detailsr!  Zteam_details_responser(  	jwt_tokenr   rS   rS   rT   cli_poll_key  s   

	
	
r  result_openidc              	      s`  t d|  | du rtdt| trtdi | } |du r%tdtjrM|d}t	|rG|}|
tj ||d< t d| d n|
tj |dtjjkrn|ddu rbtj|d< |d	du rntj|d	< |d du rytj|d< t|d
 t|d |d |d |d	 |d
 dd}| rt| drdt| di|_t|ttjddI dH }|S )ar  
    Helper function to create a New User in LiteLLM DB after a successful SSO login

    Args:
        result_openid (OpenID): User information in OpenID format if the login was successful.
        user_defined_values (Optional[SSOUserDefinedValues], optional): LiteLLM SSOValues / fields that were read

    Returns:
        Tuple[str, str]: User ID and User Role
    z)Inserting SSO user into DB. User values: Nzresult_openid is Nonezuser_defined_values is Nonerk   zPreserved SSO-extracted role 'rz   r  budget_durationr!  r8  F)r!  r8  rk   r  r  r;  Zauto_create_keyr   Zauth_providerr$  r%  rS   )r   r`   r   rP   r   rM   r4  r@  r   rE  rM  r"   r[   ra   max_internal_user_budgetinternal_user_budget_durationr\   r%   rU   rJ  r   metadatar6   r+   rZ   )r  r=  rD  Zpreserved_roleZnew_user_requestr   rS   rS   rT   insert_sso_userz  sZ   








r  z/sso/get/ui_settings)r   r   dependenciesc           	         s   ddl m}m} tdd }tdd }tdd }t }|dtk}|dd}d	tj	v r;tj	d	 
 d
kr;d}||||||d|dS )Nr   )rR  proxy_statePROXY_BASE_URLPROXY_LOGOUT_URLLITELLM_UI_API_DOC_BASE_URLZspend_logs_row_countdefault_team_disabledFZPROXY_DEFAULT_TEAM_DISABLEDr   T)r  r  r  ZDEFAULT_TEAM_DISABLEDZSSO_ENABLEDZNUM_SPEND_LOGS_ROWSZDISABLE_EXPENSIVE_DB_QUERIES)r   rR  r  r   r   r.   Zget_proxy_state_variabler   r   environrR   )	r   rR  r  Z_proxy_base_urlZ_logout_urlZ_api_doc_base_urlZ_is_sso_enabledZdisable_expensive_db_queriesr  rS   rS   rT   get_ui_settings  s0   
r  z/sso/readiness)r   r  c                     s  t dd} t dd}t dd}d}|durd}n| dur#d}n|dur)d}|du r3dd	d
dS g }|dkrIt dd}|du rH|d nc|dkrlt dd}t dd}|du rb|d |du rk|d n@|dkrt dd}t dd}	t dd}
t dd}|du r|d |	du r|d |
du r|d |du r|d t|dkrdd||  ddS tddd|||  dd| dd)z
    Health endpoint for checking SSO readiness.
    Checks if the configured SSO provider has all required environment variables set in memory.
    r   Nr   r   Zgoogle	microsoftr   ZhealthyFzNo SSO provider configured)r   sso_configuredr   GOOGLE_CLIENT_SECRETMICROSOFT_CLIENT_SECRETMICROSOFT_TENANTr   r   r   r   r   Tz SSO is properly configured)r   r  r   r   i  Z	unhealthyz? SSO is configured but missing required environment variables: z, )r   r  r   Zmissing_environment_variablesr   rU  )r   r   r?  rl  
capitalizer   join)r   r   r   Zconfigured_providerZmissing_varsgoogle_client_secretmicrosoft_client_secretmicrosoft_tenantr   r   r   r   rS   rS   rT   sso_readiness  sx   







r  c                   @   s  e Zd ZdZe				d<dedee dee dee dee dee fd	d
Ze		d=de	dee dee dee fddZ
e		d=dee dee deeee f fddZe			d>dee dee dee defddZe	d?dededee defddZedeeeeef  deeeef  dee dee def
ddZedeeeeef  deeeef  fdd Zed!edeeeeef  d"ee ded# fd$d%Ze	d?d&ed'ee fd(d)Ze	d?d*eeef d+e d&ed'ee de f
d,d-Z!e	d?d.ee d/ee dee dee fd0d1Z"e	d?deeeef  dee de#fd2d3Z$e			d>deeeef ded"ee dee d4ee defd5d6Z%eded7edefd8d9Z&edeeef fd:d;Z'dS )@r   zA
    Handler for SSO Authentication across all SSO providers
    Nr   r   r   r   r   rO   c                    s  |durPddl m} tdd}|du rtdtjdtjd|||| d}t	
d|  d	|  | |j|d
I dH W  d   S 1 sGw   Y  td(|durtdd}tdd}	|du rntdtjdtjdt|||	| dd}
|
 |
j|d
I dH W  d   S 1 sw   Y  td(|dur<ddlm} ddlm} tdd}tddd}tdd}tdd}tdd}|du rtdtjdtjd|du rtdtjdtjd|du rtdtjdtjd|du rtdtjdtjdt	d| d| d|  t	d |  d!| d" ||||d#}|d$|d%}|||| d|d&}tj|||d'I dH S td())aT  
        Step 1. Call Get Login Redirect for the SSO provider. Send the redirect response to `redirect_url`

        Args:
            redirect_url (str): The URL to redirect the user to after login
            google_client_id (Optional[str], optional): The Google Client ID. Defaults to None.
            microsoft_client_id (Optional[str], optional): The Microsoft Client ID. Defaults to None.
            generic_client_id (Optional[str], optional): The Generic Client ID. Defaults to None.

        Returns:
            RedirectResponse: The redirect response from the SSO provider.
        Nr   	GoogleSSOr  1GOOGLE_CLIENT_SECRET not set. Set it in .env filer   )r  r  r  z5In /google-login/key/generate, 
GOOGLE_REDIRECT_URI: z
GOOGLE_CLIENT_ID: )r   r  r  4MICROSOFT_CLIENT_SECRET not set. Set it in .env fileTr  r  Ztenantr  r  r   r  r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r	  )r
  r  r  )r  r   r   zfUnknown SSO provider. Please setup SSO with client IDs https://docs.litellm.ai/docs/proxy/admin_ui_sso)fastapi_sso.sso.googler  r   r   r(   r'   r   r   r   r   r   get_login_redirectr7   r  r  r  r  r   r`   r   !get_generic_sso_redirect_responser   )r   r   r   r   r   r  r  
google_ssor  r  microsoft_ssor  r  r   r   r   r   r   r  r  r  rS   rS   rT   r   E  s    W C

z/SSOAuthenticationHandler.get_sso_login_redirectr  r   c                    s|  ddl m}m}m}m} ddlm}m} |  tj	||d\}	}
i }i }|	
 D ]\}}|dv r6|||< q)|||< q)| jdi |I dH }|
rd|	v rd|	d  }|durc|j||
d	d
I dH  n|j||
d	d
I dH  |r|t|jd }||j}|
 D ]	\}}|g||< q||dd}||j|j|j|j||jf}||jd< td |W  d   S 1 sw   Y  dS )z;
        Get the redirect response for Generic SSO
        r   )parse_qs	urlencodeurlparse
urlunparseredis_usage_cacher9  )r   r   )code_challengecode_challenge_methodNr   pkce_verifier:rz  r{  locationT)doseqz*PKCE parameters added to authorization URLrS   )urllib.parser  r  r  r  r   r  r9  r    _get_generic_sso_redirect_paramsitemsr  Zasync_set_cacherQ   r  queryschemenetlocpathr  fragmentr   r`   )r  r   r   r  r  r  r  r  r9  redirect_paramscode_verifierZpkce_paramsZstate_only_paramsr   ra   redirect_responser  
parsed_urlquery_paramsZ	new_querynew_urlrS   rS   rT   r    sh   	



$z:SSOAuthenticationHandler.get_generic_sso_redirect_responsec                 C   s   i }d}| r| |d< nt dd}|r||d< nt j|d< t dd dk}|r>t \}}||d< d|d	< t	d
 ||fS )a  
        Get redirect parameters for Generic SSO with proper state priority handling.
        Optionally generates PKCE parameters if GENERIC_CLIENT_USE_PKCE is enabled.

        Priority order:
        1. CLI state (if provided)
        2. GENERIC_CLIENT_STATE environment variable
        3. Generated UUID (required by Okta and most OAuth providers)


        Args:
            state: Optional state parameter (e.g., CLI state)
            generic_authorization_endpoint: Authorization endpoint URL

        Returns:
            Tuple[dict, Optional[str]]:
                - Redirect parameters for SSO login (may include PKCE params)
                - code_verifier (if PKCE is enabled, None otherwise)
        Nr   ZGENERIC_CLIENT_STATEZGENERIC_CLIENT_USE_PKCEr   r   r  ZS256r  z<PKCE enabled - code_challenge added to authorization request)
r   r   r   uuid4hexrR   r   generate_pkce_paramsr   r`   )r   r   r  r  Zgeneric_client_stateZuse_pkcer  rS   rS   rT   r    s(   

z9SSOAuthenticationHandler._get_generic_sso_redirect_paramsc                 C   s    | d us|d us|d urdS dS )NTFrS   )r   r   r   rS   rS   rT   r   S  s
   z/SSOAuthenticationHandler.should_use_sso_handlerr   r   r   c                 C   sB   ddl m} |t| jd}|dr||7 }|S |d| 7 }|S )z
        Get the redirect URL for SSO

        Note: existing_key is not added to the URL to avoid changing the callback URL.
        It should be passed via the state parameter instead.
        r   )rA   )request_base_url/)r   rA   rQ   base_urlendswith)r   r   r   rA   r   rS   rS   rT   r   a  s   
z1SSOAuthenticationHandler.get_redirect_url_for_ssorf   r(  r8  r=  r   c              
      s   z0|dur!|j }t| ||d}|jjjd|i|dI dH  |W S td t| |dI dH }|W S  tyM } zt	d|  |W  Y d}~S d}~ww )a  
        Connects the SSO Users to the User Table in LiteLLM DB

        - If user on LiteLLM DB, update the user_email and user_role (if SSO provides valid role) with the SSO values
        - If user not on LiteLLM DB, insert the user into LiteLLM DB
        N)rf   r8  r!  r!  rL  z.user not in DB, inserting user into LiteLLM DB)r  r=  z*Error upserting SSO user into LiteLLM DB: )
r!  rG  r   r   Zupdate_manyr   r   r  r   r  )rf   r(  r8  r=  r   r!  rF  r   rS   rS   rT   rA  w  s8   z(SSOAuthenticationHandler.upsert_sso_userc                    s:   |du rt d dS t| dg }t||dI dH  dS )z
        Adds the user as a team member to the teams specified in the SSO responses `team_ids` field


        The `team_ids` field is populated by litellm after processing the SSO response
        Nz;User not found in LiteLLM DB, skipping team member additionrj   )r(  r*  )r   r`   r   r1  )rf   r(  r*  rS   rS   rT   rB    s   z<SSOAuthenticationHandler.add_user_to_teams_from_sso_responserR  r  Tc                 C   s   t ttttf  | d}|du rdS t|trdS t|dg }|ddkrD|d}||vrDtd| d| d	| t	j
dtjd
dS )a  
        when ui_access_mode.type == "restricted_sso_group":

        - result.team_ids should contain the restricted_sso_group
        - if not, raise a ProxyException
        - if so, return True
        - if result.team_ids is None, return False
        - if result.team_ids is an empty list, return False
        - if result.team_ids is a list, return True if the restricted_sso_group is in the list, otherwise return False
        rW  NTrj   r   restricted_sso_groupz)User is not in the restricted SSO group: z. User groups: z. Received SSO response: r   )r   r   r
   r   rQ   r   rP   r   r(   r'   r   r   r   )rR  rf   r  rW  rj   r  rS   rS   rT   #verify_user_in_restricted_sso_group  s$   

z<SSOAuthenticationHandler.verify_user_in_restricted_sso_grouplitellm_team_idlitellm_team_namec              
      s  ddl m} |du rtdtjdtjdzS|jjj	d| idI dH }t
d	|  |r;t
d
|  d|  W dS t| |d}tjrNtjtj| ||d}t|tddddtddtj ddI dH  W dS  ty } zt
d|  W Y d}~dS d}~ww )aj  
        Creates a Litellm Team from a SSO Group ID

        Your SSO provider might have groups that should be created on LiteLLM

        Use this helper to create a Litellm Team from a SSO Group ID

        Args:
            litellm_team_id (str): The ID of the Litellm Team
            litellm_team_name (Optional[str]): The name of the Litellm Team
        r   )r   Nz;Prisma client not found. Set it in the proxy_server.py filer   r   r#  r   zTeam object: zTeam already exists: z - rx  )default_team_paramsr  r  team_requesthttpPOST)r   method)r   zlitellm.)tokenZ	key_alias)r   http_requestr&  zError creating Litellm Team: )r   r   r(   r'   r   r   r   r   r  Z
find_firstr   r`   r$   r4  r  r   ._cast_and_deepcopy_litellm_default_team_paramsr:   r   r+   ri  __name__r   r  )r  r  r   Zteam_objr  r   rS   rS   rT   "create_litellm_team_from_sso_group  sT   
	z;SSOAuthenticationHandler.create_litellm_team_from_sso_groupr  r  c                 C   sn   t | trt| }||d< ||d< tdi |}|S t tjtr5ttj}| }|| tdi |}|S )ac  
        Casts and deepcopies the litellm.default_team_params to a NewTeamRequest object

        - Ensures we create a new DefaultTeamSSOParams object
        - Handle the case where litellm.default_team_params is a dict or a DefaultTeamSSOParams object
        - Adds the litellm_team_id and litellm_team_name to the DefaultTeamSSOParams object
        r#  rw  NrS   )	rP   r   r   r$   r4  r  rE   r  rM  )r  r  r  r  Z_team_requestZ_default_team_paramsZ_new_team_requestrS   rS   rT   r    s   


zGSSOAuthenticationHandler._cast_and_deepcopy_litellm_default_team_paramsr   r   c                 C   sF   ddl m}m} | |kr!|r!|r| d| d| S | d| S dS )a  
        Checks the request 'source' if a cli state token was passed in

        This is used to authenticate through the CLI login flow.

        The state parameter format is: {PREFIX}:{key}:{existing_key}
        - If existing_key is provided, it's included in the state
        - The state parameter is used to pass data through the OAuth flow without changing the callback URL
        r   )rO  LITELLM_CLI_SOURCE_IDENTIFIERr_  N)rc  rO  r  )r   r   r   rO  r  rS   rS   rT   r   2  s   z'SSOAuthenticationHandler._get_cli_statec                 C   s  t t| dd}| durt| ddnd}d}|dur@tddur@|dd }tdd}||vr@tdd	d
||id| dur`t| dd}|dur`t|trV|j	n|}t
d|  |dur| durtdd}t| dd}t t| dd}|du rt| |d}	|	durt|	tr|	j	n|	}|du r| durt| ddpd}
t| ddpd}|
| }|dur|du st|dkr|}t|||dS )ze
        Gets the user email and id from the OpenID result after validating the email domain
        rN   Nr   ZALLOWED_EMAIL_DOMAINS@ra  rv   r^  r   zZThe email domain={}, is not an allowed email domain={}. Contact your admin to change this.rU  rk   z%Extracted user_role from SSO result: ry   rb   r   r  r   r   )r8  r!  rk   )rU   r   r   r   r   r   formatrP   r"   ra   r   r`   rl  rL   )rf   r   r8  r!  rk   Zemail_domainallowed_domainsZ
_user_roler   Z_role_from_attrZ_first_nameZ
_last_namerS   rS   rT   r~  L  sb   
z;SSOAuthenticationHandler._get_user_email_and_id_from_resultrW  c           $         s8  dd l }ddlm}m}m}m}	m}
m}m} ddl	m
} ddlm} |d}tj| |d}|d}|d}|d	}td
|   d }g }tj}tj}ttji i ddd}d }|d urpt|rl|| I d H }ntd|d ur~t||||||d}tj|| |d t| |||
|||dI d H }t||d}|d u rtdtd|  || d|d< |d1i |ddiI d H }|d }|d }|d	 pt j!j"}|rt#|t$rt%|||dI d H }t&d| d|  t'|pi }|rt(|pd}|st)ddd| d| id t* }t+t$|j,d!d"}t-d#rNd } |d ur<|d d ur<t.|d |d	 p6|g tjd$} | d u rIt)ddd%id t/0| }|t1t$||||p[t j!j"d&|	|d'd(|t2 d)	}!|j3t1t4|!|prdd*d+}"|d urt#|t$r|d,7 }td-|  t5|d.d/}#|#j6d|"d0 |#S )2Nr   )rR  generate_key_helper_fnrS  r   r:  r9  user_custom_ssor   )ReturnedUITokenObjectr   )rf   r   r8  r!  rk   zSSO callback result: zlitellm-dashboard)durationZkey_max_budgetaliasesconfigZspendr#  z,user_custom_sso must be a coroutine function)rI  r!  r8  r  rk   r  )rR  rf   r  rs  )r(  r=  zUnable to map user identity to known values. 'user_defined_values' is None. File an issue - https://github.com/BerriAI/litellm/issuesz)user_defined_values for creating ui key: r   Zrequest_typeZ
table_namer  )rk   r!  r   zuser_role: z; ui_access_mode: r  r^  r  z,User not allowed to access proxy. User role=z, proxy mode=rU  zui/)r  ZrouteZEXPERIMENTAL_UI_LOGINr  z6User Information is required for experimental UI loginZssoZlitellm_key_header_nameAuthorization)	r!  r   r8  rk   Zlogin_methodr   Zauth_header_name(disabled_non_admin_personal_key_creationZserver_root_pathZHS256)	algorithmz?login=successr\  i/  )urlr   )r   ra   rS   )7r{   r   rR  r  rS  r   r:  r9  r  r   r   litellm.types.proxy.ui_ssor  r   r~  r   r   r   r4  r  r  r   r  inspectiscoroutinefunctionr   r)   r  rC  rK  r   rM  r"   r\   ra   rP   rQ   rN  r`   r8   r9   r   r7  rA   r  rC   r!   r,   Z(get_experimental_ui_login_jwt_auth_tokenr   rB   encoder   r   
set_cookie)$rf   r   r  r   rW  r{   rR  r  rS  r   r:  r9  r  r   r  r   r  r8  r!  rk   r(  Zuser_id_modelsr  r  Zdefault_ui_key_valuesr=  r   r   Zis_admin_only_accessZ
has_accessr  Zlitellm_dashboard_uiZ
_user_infoZreturned_ui_token_objectr  r  rS   rS   rT   rn    s  $	










z:SSOAuthenticationHandler.get_redirect_response_from_openidr   c           	         s   d|i}t | j}|d}|raddlm}m} d| }|dur,|j|dI dH }n	|j|dI dH }|rat|tr>|nt||d< t	
d	 |durX|j|dI dH  |S |j|dI dH  |S )
z
        Prepare token exchange parameters for Generic SSO.

        Args:
            request: Request object
            generic_include_client_id: Generic OAuth Client ID

        Returns:
            dict: Token exchange parameters
        Zinclude_client_idr   r   r  r  Nr  r  zCPKCE code_verifier retrieved and will be included in token exchange)r   r  r   r   r  r9  Zasync_get_cacherP   rQ   r   r`   Zasync_delete_cache)	r   r   Ztoken_paramsr  r   r  r9  r  r  rS   rS   rT   r  R	  s.   


z:SSOAuthenticationHandler.prepare_token_exchange_parametersc                  C   sN   t tddd} t| d	 }t |dd}| |fS )a  
        Generate PKCE (Proof Key for Code Exchange) parameters for OAuth 2.0.

        Returns:
            Tuple[str, str]: (code_verifier, code_challenge)
            - code_verifier: Random 43-128 character string (we use 43 for efficiency)
            - code_challenge: Base64-URL-encoded SHA256 hash of the code_verifier

        Reference: https://datatracker.ietf.org/doc/html/rfc7636
            zutf-8r  )
base64urlsafe_b64encodesecretsZtoken_bytesr|   rstriphashlibsha256r  digest)r  Zcode_challenge_bytesr  rS   rS   rT   r  	  s   z-SSOAuthenticationHandler.generate_pkce_params)NNNN)NNNNNN)(r  
__module____qualname____doc__staticmethodrQ   r   r   r   r   r  r	   r   r  r6  r   r   r   r
   r<   rM   r&   r!   r)   r?   rA  rB  r   r   r  r  rE   r$   r  r   rL   r~  rn  r  r  rS   rS   rS   rT   r   @  sF    T5)%=
G >0r   c                   @   sl  e Zd ZdZdZe dZ	 dZdZe	d)de	de
d	e
d
edeeeef f
ddZedee dee
 dee defddZedee
 dee
 fddZe	d*dee
 dee
 fddZede
dededeee
 ee
 f fddZededee
 fdd Zededefd!d"Ze	d*d#e
dedee
 deee
 ee f fd$d%Zed&ee fd'd(ZdS )+ri  zS
    Handles Microsoft SSO callback response and returns a CustomOpenID object
     https://graph.microsoft.com/v1.0z/me/memberOfr   Zgraph_api_user_groupsFr   r   r   return_raw_sso_responserO   c                    s.  t dd}t dd}|du rtdtjdtjd|du r)tdtjdtjdt||||dd}|j| d	d
I dH p=i }t	j
|jdI dH }t	j|jd}	td|	  d}
|	rv|	D ]}t|}|duru|}
td|j d  nq]td|  |r||t	j< |	|d< |pi S t	j|||
d}|S )z
        Get the Microsoft SSO callback response

        Args:
            return_raw_sso_response: If True, return the raw SSO response
        r  Nr  r  r   z-MICROSOFT_TENANT not set. Set it in .env fileTr  Fr   Zconvert_response)r  )id_tokenz#Extracted app roles from id_token: r   z' in app_rolesz(Combined team_ids (groups + app roles): 	app_roles)r   rj   rk   )r   r   r(   r'   r   r   r   r7   r  ri  get_user_groups_from_graph_apir  get_app_roles_from_id_tokenr  r   r`   r=   ra   GRAPH_API_RESPONSE_KEYopenid_from_response)r   r   r   r  r  r  r  Zoriginal_msft_resultZuser_team_idsr  rk   Zrole_strrb   rf   rS   rS   rT   rj  	  s|   z3MicrosoftSSOHandler.get_microsoft_callback_responser   rj   rk   c              
   C   sp   | pi } t d|   tt| tp| d| td| t| t| t	||d}t d|  |S )Nz!Microsoft SSO Callback Response: mailr  )rN   r   r   r   r   r   rj   rk   zMicrosoft SSO OpenID Response: )
r   r`   r<   rU   r   r   r   r   r   r   )r   rj   rk   Zopenid_responserS   rS   rT   r  
  s    z(MicrosoftSSOHandler.openid_from_responser  c              
   C   s   | s	t d g S z8ddl}|j| ddid}|dg p"|dg }|r:t|tr:t d	t| d
|  |W S t d g W S  ty] } zt 	d|  g W  Y d}~S d}~ww )a  
        Extract app roles from the Microsoft Entra ID (Azure AD) id_token JWT.

        App roles are assigned in the Azure AD Enterprise Application and appear
        in the 'app_roles' claim of the id_token.

        Args:
            id_token (Optional[str]): The JWT id_token from Microsoft SSO

        Returns:
            List[str]: List of app role names assigned to the user
        z,No id_token provided for app role extractionr   Nrg   Frh   r  rX   zFound z app role(s) in id_token: z;No app roles found in id_token or roles claim is not a listz*Error extracting app roles from id_token: )
r   r`   r{   r|   r   rP   r]   rl  r   r  )r  r{   Zdecoded_tokenrX   r   rS   rS   rT   r	  
  s*   
z/MicrosoftSSOHandler.get_app_roles_from_id_tokenNr  c           
   
      sb  zt tjd}tdd}g  g }|r8tj||| dI dH \ }td   t	 dkr8tj
|dI dH  g }tj}dd	|  i}d}|durn|tjk rntj|||d
I dH \}}|| |d7 }|durn|tjk sO|dur|tjkrtdtj d  rt	 dkr fdd|D }|W S  ty }	 ztd|	  g W  Y d}	~	S d}	~	ww )a  
        Returns a list of `team_ids` the user belongs to from the Microsoft Graph API

        Args:
            access_token (Optional[str]): Microsoft Graph API access token

        Returns:
            List[str]: List of group IDs the user belongs to
        )Zllm_providerZMICROSOFT_SERVICE_PRINCIPAL_IDN)service_principal_idasync_clientr  zService principal group IDs: r   )service_principal_teamsr  Bearer )r  r  r  ra  zReached maximum page limit of z". Some groups may not be included.c                    s   g | ]}| v r|qS rS   rS   )ro   Zgroup_idZservice_principal_group_idsrS   rT   rq   
  s
    zFMicrosoftSSOHandler.get_user_groups_from_graph_api.<locals>.<listcomp>z4Error getting user groups from Microsoft Graph API: )r   r   ZSSO_HANDLERr   r   ri  $get_group_ids_from_service_principalr   r`   rl  4create_litellm_teams_from_service_principal_team_idsgraph_api_user_groups_endpointMAX_GRAPH_API_PAGESfetch_and_parse_groupsr   r   r   r  )
r  r  r  r  Zall_group_idsZ	next_linkZauth_headersZ
page_count	group_idsr   rS   r  rT   r  I
  sn   



	

z2MicrosoftSSOHandler.get_user_groups_from_graph_apir  r  r  c                    sJ   |j | |dI dH }| }tj|dI dH }tj|d}|| dfS )z8Helper function to fetch and parse group data from a URLr  N)r   odata_nextLink)r   jsonri  _cast_graph_api_response_dict&_get_group_ids_from_graph_api_response)r  r  r  r   response_jsonZresponse_typedr  rS   rS   rT   r  
  s   z*MicrosoftSSOHandler.fetch_and_parse_groupsc                 C   s:   g }|  dg p	g D ]}| d}|d ur|| q
|S )Nra   r   )r   r?  )r   r  _objectZ	_group_idrS   rS   rT   r  
  s   

z:MicrosoftSSOHandler._get_group_ids_from_graph_api_responsec                    sp   g }|  dg D ]!}|t| d| d| d| d| d| dd q	t|  d	|  d
|dS )Nra   z@odata.typer   deletedDateTimedescriptiondisplayNameroleTemplateId)Z
odata_typer   r  r   r!  r"  z@odata.contextz@odata.nextLink)Zodata_contextr  ra   )r   r?  rF   rG   )r   Zdirectory_objectsr  rS   rS   rT   r  
  s$   
z1MicrosoftSSOHandler._cast_graph_api_response_dictr  c                    s   d}d|  d}|| }d| dd}|j ||dI dH }| }td	|  g }	g }
| d
g D ] }| ddkrU|	| d |
t| d| dd q5|	|
fS )z
        Gets the groups belonging to the Service Principal Application

        Service Principal Id is an `Enterprise Application` in Azure AD

        Users use Enterprise Applications to manage Groups and Users on Microsoft Entra ID
        r  z/servicePrincipals/z/appRoleAssignedTor  zapplication/json)r  zContent-Typer  Nz6Response from service principal app role assigned to: ra   ZprincipalTypeGroupprincipalIdprincipalDisplayName)r%  r$  )r   r  r   r`   r?  rH   )r  r  r  r  Zendpointr  r  r   r  r  r  r  rS   rS   rT   r  
  s2   z8MicrosoftSSOHandler.get_group_ids_from_service_principalr  c                    s`   t d|   | D ]"}|d}|d}|s#t d| d qtj||dI dH  qdS )z
        Creates Litellm Teams from the Service Principal Group IDs

        When a user sets a `SERVICE_PRINCIPAL_ID` in the env, litellm will fetch groups under that service principal and create Litellm Teams from them
        z5Creating Litellm Teams from Service Principal Teams: r$  r%  zSkipping team creation for z because it has no principalId)r  r  N)r   r`   r   r   r  )r  Zservice_principal_teamr  r  rS   rS   rT   r  
  s&   	

zHMicrosoftSSOHandler.create_litellm_teams_from_service_principal_team_idsFr  ) r  r  r   r  Zgraph_api_base_urlr  r  r
  r  r   rQ   r6  r
   r<   rM   r   rj  r   r   r"   r  r	  r  r   r	   r  rG   r  r  rH   r  r  rS   rS   rS   rT   ri  	  s    
U+P
,ri  c                   @   s>   e Zd ZdZe	ddededededee	e
f f
dd	Zd
S )rg  zP
    Handles Google SSO callback response and returns a CustomOpenID object
    Fr   r   r   r  rO   c                    sx   ddl m} tdd}|du rtdtjdtjd||||d}|r0|j	| dd	I dH p/i S |	| I dH }|p;i S )
z
        Get the Google SSO callback response

        Args:
            return_raw_sso_response: If True, return the raw SSO response
        r   r  r  Nr  r   )r  r  r  Fr  )
r  r  r   r   r(   r'   r   r   r   r  )r   r   r   r  r  r  r  rf   rS   rS   rT   rh    s0   z-GoogleSSOHandler.get_google_callback_responseNr&  )r  r  r   r  r  r   rQ   r6  r
   rM   r   rh  rS   rS   rS   rT   rg    s    
rg  z/sso/debug/loginc                    s   ddl m} tdd}tdd}tdd}|dus%|dus%|dur3|dur3tdtjd	tjd
t	j
| dd}t	j|||ddu rPt	j||||dI dH S dS )r   r   )r   r   Nr   r   Tax  You must be a LiteLLM Enterprise user to use SSO. If you have a license please set `LITELLM_LICENSE` in your env. If you want to obtain a license meet with us here: https://calendly.com/d/cx9p-5yf-2nm/litellm-introductions You are seeing this error message because You set one of `MICROSOFT_CLIENT_ID`, `GOOGLE_CLIENT_ID`, or `GENERIC_CLIENT_ID` in your env. Please unset thisr   r   sso/debug/callbackr[  r   )r   r   r   r   )r   r   r   r   r(   r'   r   r   r   r   r   r   r   )r   r   r   r   r   r   rS   rS   rT   debug_sso_loginD  sB   r(  /sso/debug/callbackc                    sD  ddl }ddlm} ddlm} ddlm} ddlm}m	}m
}m} d}	|dd}
|
durKt|
trK| }	|	j||||di ddd	dd
 tdd}tdd}tdd}tdt| j}|drp|d7 }n|d7 }d}|durtj| ||ddI dH }n#|durtj| ||ddI dH }n|durt| ||||	dI dH \}}|du r|dddS t|dr|j}nt|}i }| D ]F\}}|dur|dst|tttt fs|du r|||< qzt|||< W q t!y } zdt| ||< W Y d}~qd}~ww qt"#dd|j$|dd d }||d!S )"z@
    Returns the OpenID object returned by the SSO provider
    r   Nr   rP  r/   )rR  r   r   r9  rW  rX  rY  rZ  r   r   r   r  r  r'  r)  T)r   r   r   r  )r   r   r   r  r]  zT<h1>SSO Authentication Failed</h1><p>No data was returned from the SSO provider.</p>rr  r   __dict___z!Complex value (not displayable): zconst userData = SSO_DATA;zconst userData = r`  )indent;)r   )%r  r   r   rd  rQ  re  r0   r   rR  r   r   r9  r   rP   r   rf  r   r   rQ   r  r  rg  rh  ri  rj  r  rJ  r*  r  rk  intfloatr6  r   r4   replacedumps)r   r  r   rQ  r0   rR  r   r   r9  re   rW  r   r   r   r   rf   r+  Zresult_dictZfiltered_resultr   ra   r   r  rS   rS   rT   debug_sso_callbackv  s   

	


r2  r  r  )r  r-  r  r  r  r   r  copyr   typingr   r   r   r   r   r   r	   r
   r   Zfastapir   r   r   r   r   r   r   r4  Zlitellm._loggingr   Zlitellm._uuidr   Zlitellm.cachingr   rc  r   r   r   r   r   r   r   Z0litellm.litellm_core_utils.dot_notation_indexingr   Z&litellm.llms.custom_httpx.http_handlerr   r   r   rd  r    r!   r"   r#   r$   r%   r&   r'   r(   r)   r*   r+   r  r,   r-   Zlitellm.proxy.auth.auth_utilsr.   re  r0   Z$litellm.proxy.auth.user_api_key_authr1   Z)litellm.proxy.common_utils.admin_ui_utilsr2   r3   Z:litellm.proxy.common_utils.html_forms.jwt_display_templater4   Z.litellm.proxy.common_utils.html_forms.ui_loginr5   Z:litellm.proxy.management_endpoints.internal_user_endpointsr6   Z&litellm.proxy.management_endpoints.ssor7   Z3litellm.proxy.management_endpoints.sso_helper_utilsr8   r9   Z1litellm.proxy.management_endpoints.team_endpointsr:   r;   Z(litellm.proxy.management_endpoints.typesr<   r=   r>   r   r?   r@   rA   rB   Zlitellm.secret_managers.mainrC   rD   r   rE   rF   rG   rH   rI   rJ   r  rL   r  rM   ZrouterrQ   rU   rc   r   r   r   r   r   r6  r   r   r   r  r)  r1  r7  r<  rC  rE  rG  rK  rN  rp  rm  r  r  r  r  r   ri  rg  r(  r2  rS   rS   rS   rT   <module>   s   
,$	8 
:
lx
 
<&J
^


 
D
$
'
k
~o
N
P      f  v01