o
    ?7if)                     @   sX  U d Z ddlmZ ddlmZmZ ddlmZmZ ddlm	Z	 ddl
Z
ddlZddlmZ edgd	d
Zdae	ej ed< dejfddZdededefddZdedefddZdRdede	e defddZdede	e fddZeZdefddZdedeeef fdd Zded!eddfd"d#Zdedefd$d%Z defd&d'Z!defd(d)Z"defd*d+Z#defd,d-Z$defd.d/Z%defd0d1Z&defd2d3Z'defd4d5Z(defd6d7Z)defd8d9Z*d:e	e defd;d<Z+dRd=ed>e	e defd?d@Z,dRdAe	e defdBdCZ-dDdE Z.dRdSdHdIZ/de0fdJdKZ1dLe0defdMdNZ2dOedefdPdQZ3dS )TzM
Security utilities
JWT token handling, password hashing, and authentication
    )CryptContext)JWTErrorjwt)datetime	timedelta)OptionalN)settingsbcryptauto)schemes
deprecated_redis_clientreturnc                   C   sB   t du rtjrtjtjdda t S tjtjtjtjtj	dda t S )z/Get or create Redis client for token managementNT)decode_responses)hostportdbpasswordr   )
r   r   	REDIS_URLredisfrom_urlRedis
REDIS_HOST
REDIS_PORTREDIS_DBREDIS_PASSWORD r   r   9/lsinfo/ai/hellotax_ai/base_platform/app/core/security.pyget_redis_client   s   r   plain_passwordhashed_passwordc                 C   s2   t | tr| ddd jddd} t| |S )z*
    Verify a password against a hash
    zutf-8NH   ignore)errors)
isinstancestrencodedecodepwd_contextverify)r   r    r   r   r   verify_password   s   
r*   r   c                 C   s
   t | S )z&
    Hash a password using bcrypt
    )r(   hashr   r   r   r   get_password_hash)   s   
r-   dataexpires_deltac                 C   sT   |   }|rt | }n
t ttjd }|d|i tj|tj	tj
d}|S )z
    Create a JWT access token

    Args:
        data: Dictionary containing token payload
        expires_delta: Optional expiration time delta

    Returns:
        Encoded JWT token string
    )minutesexp)	algorithm)copyr   utcnowr   r   ACCESS_TOKEN_EXPIRE_MINUTESupdater   r&   
SECRET_KEY	ALGORITHM)r.   r/   	to_encodeexpireencoded_jwtr   r   r   create_access_token0   s   r<   tokenc                 C   s2   zt j| tjtjgd}|W S  ty   Y dS w )z
    Decode and verify a JWT access token

    Args:
        token: JWT token string

    Returns:
        Token payload dict if valid, None otherwise
    )
algorithmsN)r   r'   r   r7   r8   r   )r=   payloadr   r   r   decode_access_tokenF   s   
r@   c                   C   s
   t dS )zx
    Generate a random verification token for email verification

    Returns:
        URL-safe random token string
        )secretstoken_urlsafer   r   r   r   generate_verification_token[   s   
rD   c                    st   t | dk rdS tdd | D sdS tdd | D sdS tdd | D s)d	S d
 t fdd| D s8dS dS )a  
    Validate password strength

    Requirements:
    - At least 8 characters
    - Contains uppercase letter
    - Contains lowercase letter
    - Contains digit
    - Contains special character

    Returns:
        Tuple of (is_valid, error_message)
       )Fu   密码至少需要8个字符c                 s       | ]}|  V  qd S N)isupper.0cr   r   r   	<genexpr>v       z-validate_password_strength.<locals>.<genexpr>)Fu   密码必须包含大写字母c                 s   rF   rG   )islowerrI   r   r   r   rL   y   rM   )Fu   密码必须包含小写字母c                 s   rF   rG   )isdigitrI   r   r   r   rL   |   rM   )Fu   密码必须包含数字z!@#$%^&*()_+-=[]{}|;:,.<>?c                 3       | ]}| v V  qd S rG   r   rI   special_charsr   r   rL      rM   )Fu   密码必须包含特殊字符)T )lenanyr,   r   rQ   r   validate_password_strengthe   s   rV   exp_timestampc                 C   s>   t  }|tt   }|dkr|d|  |d dS dS )z
    Revoke a JWT token by adding it to Redis blacklist

    Args:
        token: JWT token string
        exp_timestamp: Token expiration timestamp
    r   revoked_token:1N)r   intr   r4   	timestampsetex)r=   rW   clientttlr   r   r   revoke_token   s
   r_   c              
   C   sf   zt  }|d|  dkW S  ty2 } zddl}|t}|d|  W Y d}~dS d}~ww )z
    Check if a token has been revoked

    Args:
        token: JWT token string

    Returns:
        True if token is revoked
    rX   r   Nz.Redis unavailable for token revocation check: T)r   exists	Exceptionlogging	getLogger__name__error)r=   r]   erb   loggerr   r   r   is_token_revoked   s   

rh   c                 C   sF   t | dr| jr| jD ]}|js|jjs|jj  S q| jr!| jS dS )z
    Get user's primary role (backward compatible)

    Priority:
    1. user_roles relationship (first non-deleted role)
    2. Fallback to user.role field

    Args:
        user: User object

    Returns:
        Role code string
    
user_rolescustomer_user)hasattrri   
is_deletedrolecode)userurr   r   r   get_primary_role   s   
rq   c                 C      t | dkS )z'Check if user is platform administratorplatform_adminrq   ro   r   r   r   is_platform_admin      rv   c                 C   rr   )z*Check if user is platform user (read-only)platform_userrt   ru   r   r   r   is_platform_user   rw   ry   c                 C   rr   )z'Check if user is customer administratorcustomer_adminrt   ru   r   r   r   is_customer_admin   rw   r{   c                 C   rr   )z&Check if user is customer regular userrj   rt   ru   r   r   r   is_customer_user   rw   r|   c                 C      t | dv S )z'Check if user has platform-level accessrs   rx   rt   ru   r   r   r   has_platform_access   rw   r   c                 C   r}   )z5Check if user has admin access (platform or customer)rs   rz   rt   ru   r   r   r   has_admin_access   rw   r   c                 C   r}   )z$Check if user can manage other usersr   rt   ru   r   r   r   can_manage_users   rw   r   c                 C   r}   )z"Check if user can delete resourcesr   rt   ru   r   r   r   can_delete_resource   rw   r   c                 C   r}   )z$Check if user can view all resourcesr~   rt   ru   r   r   r   can_view_all_resources   rw   r   target_tenant_idc                 C   s$   t | rdS | jdu rdS | j|kS )z
    Check if user can access resources from a specific company

    Args:
        user: User object
        target_tenant_id: Target company ID to check access for

    Returns:
        True if user can access the company's resources
    TNF)r   	tenant_id)ro   r   r   r   r   can_access_company   s
   

r   resource_owner_idresource_tenant_idc                 C   s4   t | rdS || jkrdS t| r|r| j|kS dS )a#  
    Check if user can manage a specific resource

    Args:
        user: User object
        resource_owner_id: ID of the user who owns the resource
        resource_tenant_id: Optional company ID associated with the resource

    Returns:
        True if user can manage the resource
    TF)rv   idr{   r   )ro   r   r   r   r   r   can_manage_resource  s   

r   company_namec                 C   s>   ddddd}| | j| j}|r| jdv r| d| S |S )z
    Get display string for user role

    Args:
        user: User object
        company_name: Optional company name

    Returns:
        Formatted role display string
    u   平台管理员u   平台用户u   企业管理员u   企业用户)rs   rx   rz   rj   )rz   rj   /)getrm   )ro   r   
role_namesrole_displayr   r   r   get_role_display  s   r   c                 C   s*   ddl m}m} t| s||jdddS )zW
    Raise exception if user is not an admin
    Used as a simple permission check
    r   HTTPExceptionstatuszAdmin access requiredstatus_codedetailN)fastapir   r   r   HTTP_403_FORBIDDEN)ro   r   r   r   r   r   require_admin6  s   r   current_userUserc                    s^   ddl m mm} ddlm} | du r"||fd fdd}|S t| s- jd	d
| S )z5
    Dependency to require platform admin access
    r   )r   r   Depends)get_current_userNro   r   c                    s   t | s jdd| S )NPlatform admin access requiredr   )rv   r   ru   r   r   r   _require_platform_adminM  s   z7require_platform_admin.<locals>._require_platform_adminr   r   )ro   r   )r   r   r   r   app.api.depsr   rv   r   )r   r   r   r   r   r   r   require_platform_adminD  s   r   c                 C   s   t | drdd | jD S g S )z>Get list of role codes for a user from user_roles relationshipri   c                 S   s"   g | ]}|j s|jj s|jjqS r   )rl   rm   rn   )rJ   rp   r   r   r   
<listcomp>c  s   " z"get_user_roles.<locals>.<listcomp>)rk   ri   ru   r   r   r   get_user_roles`  s   
r   
role_codesc                    s,   t | }|s| j v S t fdd|D S )z,Check if user has any of the specified rolesc                 3   rP   rG   r   )rJ   rn   r   r   r   rL   m  rM   zhas_any_role.<locals>.<genexpr>)r   rm   rU   )ro   r   user_role_codesr   r   r   has_any_roleg  s   
r   	role_codec                 C   s   t | |gS )z!Check if user has a specific role)r   )ro   r   r   r   r   has_rolep  rw   r   rG   )r   r   )4__doc__passlib.contextr   joser   r   r   r   typingr   rB   r   
app.configr   r(   r   r   __annotations__r   r%   boolr*   r-   dictr<   r@   verify_tokenrD   tuplerV   rZ   r_   rh   rq   rv   ry   r{   r|   r   r   r   r   r   r   r   r   r   r   listr   r   r   r   r   r   r   <module>   sL    

!	