o
    ưins                     @   s8  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	 d dl
mZmZmZ d dlmZmZ d dlmZ d dlmZ d dlT d d	lmZ 	
dYdedee dee fddZ	
dYdeee  dedee de	eee f fddZdedefddZdededefddZdedededefddZdedededee def
d d!Z ded"edee dedef
d#d$Z!ded%ed&efd'd(Z"d)efd*d+Z#dedefd,d-Z$ed.d/d&edefd0d1Z%dedefd2d3Z&d4edefd5d6Z'd7e(fd8d9Z)d:e*dee+ee(f  fd;d<Z,d:e*dee+ee(f  fd=d>Z-d:e*d?e.d@ dAe.dB dee+ee(f  fdCdDZ/d:e*dee+ee(f  fdEdFZ0d:e*dee+ee(f  fdGdHZ1d&edefdIdJZ2dKdL Z3dee fdMdNZ4dOee dee fdPdQZ5	dZdedOee dee fdRdSZ6d%ed&edee7eee f  fdTdUZ8dVedefdWdXZ9dS )[    N)	lru_cache)AnyListOptionalTuple)HTTPExceptionRequeststatus)Routerprovider_list)verbose_proxy_logger)STANDARD_CUSTOMER_ID_HEADERS)*)#CONFIGURABLE_CLIENTSIDE_AUTH_PARAMSFrequestuse_x_forwarded_forreturnc                 C   sB   d }|du rd| j v r| j d }|S | jd ur| jj}|S d}|S )NTzx-forwarded-for )headersclienthost)r   r   	client_ip r   T/home/app/Keep/.python/lib/python3.10/site-packages/litellm/proxy/auth/auth_utils.py_get_request_ip_address   s   

r   allowed_ipsc                 C   s0   | du rdS t ||d}|| vrd|fS d|fS )z)
    Returns if ip is allowed or not
    N)TN)r   r   FT)r   )r   r   r   r   r   r   r   _check_valid_ip   s   r   request_bodyc                 C   sN   d}|  d}|du rdS d|v sd|v sd|v sd|v rdS d| v r%d	S dS )
zh
    if 'api_base' in request body. Check if complete credentials given. Prevent malicious attacks.
    NmodelFZ	sagemakerZbedrockZ	vertex_aiZvertex_ai_betaapi_keyT)get)r   Zgiven_modelr   r   r   check_complete_credentials5   s   
r!   request_body_value	regex_strc                 C   s   t || s
|| krdS dS )zP
    Check if request_body_value matches the regex_str or is equal to param
    TF)rematchr"   r#   r   r   r   check_regex_or_str_matchN   s   r'   param#configurable_clientside_auth_paramsc                 C   sZ   |du rdS |D ]"}t |tr| |kr dS t |tr*| dkr*t||d dr* dS qdS )zd
    Check if param is a str or dict and if request_body_value is in the list of allowed values
    NFTapi_baser&   )
isinstancestrDictr'   )r(   r"   r)   itemr   r   r   _is_param_allowedW   s   

r/   r   
llm_routerc                 C   s~   |du rdS |j | d}|du r&| ddd tv r&|j | ddd d}|du r,dS |du s5|jdu r7dS t|||jdS )z
    Check if model is allowed to use configurable client-side params
    - get matching model
    - check if 'clientside_configurable_parameters' is set for model
    -
    NF)Zmodel_group/   r   )r(   r"   r)   )Zget_model_group_infosplitr   r)   r/   )r   r(   r"   r0   Z
model_infor   r   r   5_allow_model_level_clientside_configurable_parameterso   s"   	r4   general_settingsc                 C   sj   ddg}|D ],}|| v r2t | ds2|ddu r dS t||| | |ddu r* dS td| dqdS )	u  
    Check if the request body is safe.

    A malicious user can set the ﻿api_base to their own domain and invoke POST /chat/completions to intercept and steal the OpenAI API key.
    Relevant issue: https://huntr.com/bounties/4001e1a2-7b7a-4776-a3ae-e6692ec3d997
    r*   base_url)r   Zallow_client_side_credentialsT)r   r(   r"   r0   zRejected Request: z is not allowed in request body. Enable with `general_settings::allow_client_side_credentials` on proxy config.yaml. Relevant Issue: https://huntr.com/bounties/4001e1a2-7b7a-4776-a3ae-e6692ec3d997)r!   r    r4   
ValueError)r   r5   r0   r   Zbanned_paramsr(   r   r   r   is_request_body_safe   s.   	
r8   request_dataroutec           	         s   ddl m}m}m} t| dI dH  t||||ddd t|dd|d	d
| d\}}|s=tt	j
d| ddd|v rm|d }|durStdtjj  ||vrotd| d|  tt	j
d| dddS dS )ax  
    1. Checks if request size is under max_request_size_mb (if set)
    2. Check if request body is safe (example user has not set api_base in request body)
    3. Check if IP address is allowed (if set)
    4. Check if request route is an allowed route on the proxy (if set)

    Returns:
    - True

    Raises:
    - HTTPException if request fails initial auth checks
    r   )r5   r0   premium_user)r   Nr   r   )r   r5   r0   r   r   r   F)r   r   r   zAccess forbidden: IP address z not allowed.)status_codedetailZallowed_routesTz=Trying to set allowed_routes. This is an Enterprise feature. zRoute z not in allowed_routes=zAccess forbidden: Route z not allowed)litellm.proxy.proxy_serverr5   r0   r;   check_if_request_size_is_safer8   r    r   r   r	   ZHTTP_403_FORBIDDENr   errorCommonProxyErrorsnot_premium_uservalue)	r   r9   r:   r5   r0   r;   Zis_valid_ipZpassed_in_ipZ_allowed_routesr   r   r   pre_db_read_auth_checks   sJ   





rD   current_routec              
   C   s   ddl m} ddlm}m} z.|durW dS |du rW dS |dg }| |v r*W dS |D ]}|j| |dr9 W dS q,W dS  tyY } zt	d	t
|  W Y d}~dS d}~ww )
ay  
    Helper to check if the user defined public_routes on config.yaml

    Parameters:
    - current_route: str - the route the user is trying to call

    Returns:
    - bool - True if the route is defined in public_routes
    - bool - False if the route is not defined in public_routes

    Supports wildcard patterns (e.g., "/api/*" matches "/api/users", "/api/users/123")

    In order to use this the litellm config.yaml should have the following in general_settings:

    ```yaml
    general_settings:
        master_key: sk-1234
        public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate", "/api/*"]
    ```
    r   )RouteChecksr5   r;   TFNZpublic_routes)r:   patternz"route_in_additonal_public_routes: )Zlitellm.proxy.auth.route_checksrF   r>   r5   r;   r    Z_route_matches_wildcard_pattern	Exceptionr   r@   r,   )rE   rF   r5   r;   Zroutes_definedZroute_patterner   r   r    route_in_additonal_public_routes   s,   rK   c              
   C   s   z"t | dr| jj| jjr| jjt| jjd d W S | jjW S  tyG } ztdt	| d| jj  | jjW  Y d}~S d}~ww )z
    Helper to get the route from the request

    remove base url from path if set e.g. `/genai/chat/completions` -> `/chat/completions
    r6   r2   Nzerror on get_request_route: z!, defaulting to request.url.path=)
hasattrurlpath
startswithr6   lenrI   r   debugr,   )r   rJ   r   r   r   get_request_route&  s   
rR      )maxsizec                 C   s8   g d}|D ]\}}t ||| }|| kr|  S q| S )a  
    Normalize request routes by replacing dynamic path parameters with placeholders.

    This prevents high cardinality in Prometheus metrics by collapsing routes like:
    - /v1/responses/1234567890 -> /v1/responses/{response_id}
    - /v1/threads/thread_123 -> /v1/threads/{thread_id}
    
    Args:
        route: The request route path
        
    Returns:
        Normalized route with dynamic parameters replaced by placeholders
        
    Examples:
        >>> normalize_request_route("/v1/responses/abc123")
        '/v1/responses/{response_id}'
        >>> normalize_request_route("/v1/responses/abc123/cancel")
        '/v1/responses/{response_id}/cancel'
        >>> normalize_request_route("/chat/completions")
        '/chat/completions'
    ))z3^(/(?:openai/)?v1/responses)/([^/]+)(/input_items)$\1/{response_id}\3)z.^(/(?:openai/)?v1/responses)/([^/]+)(/cancel)$rU   )z%^(/(?:openai/)?v1/responses)/([^/]+)$\1/{response_id})z$^(/responses)/([^/]+)(/input_items)$rU   )z^(/responses)/([^/]+)(/cancel)$rU   )z^(/responses)/([^/]+)$rV   )zB^(/(?:openai/)?v1/threads)/([^/]+)(/runs)/([^/]+)(/steps)/([^/]+)$z%\1/{thread_id}\3/{run_id}\5/{step_id})z:^(/(?:openai/)?v1/threads)/([^/]+)(/runs)/([^/]+)(/steps)$\1/{thread_id}\3/{run_id}\5)z;^(/(?:openai/)?v1/threads)/([^/]+)(/runs)/([^/]+)(/cancel)$rW   )zH^(/(?:openai/)?v1/threads)/([^/]+)(/runs)/([^/]+)(/submit_tool_outputs)$rW   )z2^(/(?:openai/)?v1/threads)/([^/]+)(/runs)/([^/]+)$z\1/{thread_id}\3/{run_id})z*^(/(?:openai/)?v1/threads)/([^/]+)(/runs)$\1/{thread_id}\3)z6^(/(?:openai/)?v1/threads)/([^/]+)(/messages)/([^/]+)$z\1/{thread_id}\3/{message_id})z.^(/(?:openai/)?v1/threads)/([^/]+)(/messages)$rX   )z#^(/(?:openai/)?v1/threads)/([^/]+)$z\1/{thread_id})z9^(/(?:openai/)?v1/vector_stores)/([^/]+)(/files)/([^/]+)$z \1/{vector_store_id}\3/{file_id})z1^(/(?:openai/)?v1/vector_stores)/([^/]+)(/files)$\1/{vector_store_id}\3)z@^(/(?:openai/)?v1/vector_stores)/([^/]+)(/file_batches)/([^/]+)$z!\1/{vector_store_id}\3/{batch_id})z8^(/(?:openai/)?v1/vector_stores)/([^/]+)(/file_batches)$rY   )z)^(/(?:openai/)?v1/vector_stores)/([^/]+)$z\1/{vector_store_id})z&^(/(?:openai/)?v1/assistants)/([^/]+)$z\1/{assistant_id})z+^(/(?:openai/)?v1/files)/([^/]+)(/content)$z\1/{file_id}\3)z!^(/(?:openai/)?v1/files)/([^/]+)$z\1/{file_id})z,^(/(?:openai/)?v1/batches)/([^/]+)(/cancel)$z\1/{batch_id}\3)z#^(/(?:openai/)?v1/batches)/([^/]+)$z\1/{batch_id})z5^(/(?:openai/)?v1/fine_tuning/jobs)/([^/]+)(/events)$\1/{fine_tuning_job_id}\3)z5^(/(?:openai/)?v1/fine_tuning/jobs)/([^/]+)(/cancel)$rZ   )z:^(/(?:openai/)?v1/fine_tuning/jobs)/([^/]+)(/checkpoints)$rZ   )z,^(/(?:openai/)?v1/fine_tuning/jobs)/([^/]+)$z\1/{fine_tuning_job_id})z"^(/(?:openai/)?v1/models)/([^/]+)$z
\1/{model})r$   sub)r:   patternsrH   replacement
normalizedr   r   r   normalize_request_route;  s   1r_   c           
         s  ddl m}m} |dd}|dur|dur#tdtjj  dS | j	d}|rSt
|}t|d}td	|  ||krQtd
| d| dtjjddddS |  I dH }t|}t|d}	td|	  |	|krtd
|	 d| dtjjddddS )a  
    Enterprise Only:
        - Checks if the request size is within the limit

    Args:
        request (Request): The incoming request.

    Returns:
        bool: True if the request size is within the limit

    Raises:
        ProxyException: If the request size is too large

    r   rG   max_request_size_mbNTzPusing max_request_size_mb - not checking -  this is an enterprise only feature. content-lengthbytes_valuez"content_length request size in MB=z+Request size is too large. Request size is  MB. Max size is  MB  messagetypecoder(   z request body request size in MB=)r>   r5   r;   r    r   warningrA   rB   rC   r   intbytes_to_mbrQ   ProxyExceptionProxyErrorTypesbad_request_errorbodyrP   )
r   r5   r;   r`   content_lengthheader_sizeZheader_size_mbrq   Z	body_sizeZrequest_size_mbr   r   r   r?     sL   

r?   responsec                    s   ddl m}m} |dd}|durH|dur#tdtjj  dS t	t
| d}td|  ||krHtd	| d
| dtjjddddS )a   
    Enterprise Only:
        - Checks if the response size is within the limit

    Args:
        response (Any): The response to check.

    Returns:
        bool: True if the response size is within the limit

    Raises:
        ProxyException: If the response size is too large

    r   rG   max_response_size_mbNTzQusing max_response_size_mb - not checking -  this is an enterprise only feature. rb   zresponse size in MB=z-Response size is too large. Response size is rd   re   rf   ra   rg   )r>   r5   r;   r    r   rk   rA   rB   rC   rm   sys	getsizeofrQ   rn   ro   rp   )rt   r5   r;   ru   Zresponse_size_mbr   r   r   check_response_size_is_safe  s&   rx   rc   c                 C   s   | d S )z'
    Helper to convert bytes to MB
    i   r   rb   r   r   r   rm     s   rm   user_api_key_dictc                 C   z   | j r| j d}|r|S | jr2i }| j D ]\}}t|tr-|ddur-|d ||< q|r2|S | jr;| jdS dS )z
    Get the model rpm limit for a given api key.

    Priority order (returns first found):
    1. Key metadata (model_rpm_limit)
    2. Key model_max_budget (rpm_limit per model)
    3. Team metadata (model_rpm_limit)
    model_rpm_limitZ	rpm_limitNmetadatar    Zmodel_max_budgetitemsr+   dictteam_metadata)ry   resultr{   r   budgetr   r   r   get_key_model_rpm_limit     r   c                 C   rz   )z
    Get the model tpm limit for a given api key.

    Priority order (returns first found):
    1. Key metadata (model_tpm_limit)
    2. Key model_max_budget (tpm_limit per model)
    3. Team metadata (model_tpm_limit)
    model_tpm_limitZ	tpm_limitNr|   )ry   r   r   r   r   r   r   r   get_key_model_tpm_limit  r   r   metadata_accessor_key)r   Zorganization_metadatarate_limit_key)r{   r   c                 C   s   t | |rt | ||S d S N)getattrr    )ry   r   r   r   r   r   "get_model_rate_limit_from_metadata@  s   
r   c                 C      | j r	| j dS d S )Nr{   r   r    ry   r   r   r   get_team_model_rpm_limitJ     r   c                 C   r   )Nr   r   r   r   r   r   get_team_model_tpm_limitR  r   r   c                 C   s"   dg}|D ]	}|| v r dS qdS )Nz	vertex-aiTFr   )r:   Z%PROVIDER_SPECIFIC_PASS_THROUGH_ROUTESprefixr   r   r   is_pass_through_provider_routeZ  s   r   c                  C   s@   t dd} t dd}t dd}| dup|dup|du}|S )z
    Check if the user has set up single sign-on (SSO) by verifying the presence of Microsoft client ID, Google client ID or generic client ID and UI username environment variables.
    Returns a boolean indicating whether SSO has been set up.
    ZMICROSOFT_CLIENT_IDNZGOOGLE_CLIENT_IDZGENERIC_CLIENT_ID)osgetenv)Zmicrosoft_client_idZgoogle_client_idZgeneric_client_idZ	sso_setupr   r   r   _has_user_setup_ssog  s   r   c                 C   sz   | sdS t | tr| n| g}|D ]*}t |tsq|d}|d}|du s(|s)qt| ttj kr:|  S qdS )zDReturn the header_name mapped to CUSTOMER role, if any (dict-based).NZlitellm_user_roleheader_name)r+   listr   r    r,   lowerZLitellmUserRolesZCUSTOMER)user_id_mappingr~   r.   Zroler   r   r   r   %get_customer_user_header_from_mappingy  s   


r   request_headersc                 C   sd   | du rdS t D ]'}|  D ] \}}| | kr.|dur"t|nd}| r.|    S qqdS )av  
    Check standard customer ID headers for a customer/end-user ID.

    This enables tools like Claude Code to pass customer IDs via ANTHROPIC_CUSTOM_HEADERS.
    No configuration required - these headers are always checked.

    Args:
        request_headers: The request headers dict

    Returns:
        The customer ID if found in standard headers, None otherwise
    Nr   )r   r~   r   r,   strip)r   Zstandard_headerr   header_valueuser_id_strr   r   r   &_get_customer_id_from_standard_headers  s   r   c                 C   sh  ddl m} t|d}|d ur|S |d urcd }|dd }|r#t|}|s9d}||}t|tr9| dkr9|}t|trc| D ] \}}	|	 |	 krb|	}
|
d urXt|
nd}| rb|  S qBd| v ru| d d uru| d }t|S | d}t|t
r|d}|d urt|S | d	}t|t
r|d
}|d urt|S | dd ur| d }t|S d S )Nr   )r5   )r   Zuser_header_mappingsZuser_header_namer   userlitellm_metadatar}   Zuser_idZsafety_identifier)r>   r5   r   r    r   r+   r,   r   r~   r   r   )r   r   r5   Zcustomer_idZcustom_header_name_to_checkr   Zuser_id_header_config_keyrC   r   r   Zuser_id_from_headerr   Zuser_from_body_user_fieldr   Zuser_from_litellm_metadatametadata_dictZuser_id_from_metadata_fieldr   r   r   !get_end_user_id_from_request_body  sX   








r   c                 C   s  |  dp	|  d}|d ur'|d}t|dkr |d  }ndd |D }|d u r8td|}|r8|d}|d u rP| d	sPt	d
|}|rP|d}|d u rh| d	sht	d|}|rh|d}|d u r| d	rt	d|}|r|d}|S )Nr   Ztarget_model_names,r2   r   c                 S   s   g | ]}|  qS r   )r   ).0mr   r   r   
<listcomp>  s    z*get_model_from_request.<locals>.<listcomp>z/openai/deployments/([^/]+)z/vertexz /(?:v1beta|beta)/models/([^:]+):z^/models/([^:]+):z/models/([^:]+))
r    r3   rP   r   r$   r%   groupr   rO   search)r9   r:   r   Zmodel_namesr%   Zgoogle_matchZvertex_matchr   r   r   get_model_from_request  s.   




r   r   c                 C   s   d| dd   S )Nzsk-...r   )r   r   r   r   abbreviate_api_key   s   r   )Fr   ):r   r$   rv   	functoolsr   typingr   r   r   r   Zfastapir   r   r	   Zlitellmr
   r   Zlitellm._loggingr   Zlitellm.constantsr   Zlitellm.proxy._typesZlitellm.types.routerr   boolr,   r   r   r   r!   r'   r/   r4   r8   rD   rK   rR   r_   r?   rx   rl   rm   ZUserAPIKeyAuthr-   r   r   Literalr   r   r   r   r   r   r   r   Unionr   r   r   r   r   r   <module>   s   


	

!
'
>1R@(
!
!





M
/