
    (Gj                    l    d 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  G d d	          ZdS )
a  User-authorization methods for ``GatewayRunner``.

Extracted from ``gateway/run.py`` as part of the god-file decomposition campaign
(``~/.hermes/plans/god-file-decomposition.md``, Phase 3 mechanical mixin lifts).
This mixin holds the inbound-message authorization cluster: whether a user/chat
is allowed to talk to the agent, the per-adapter DM policy, and the
unauthorized-DM behavior.

Behavior-neutral: every method is lifted verbatim from ``GatewayRunner``.
``self.*`` calls resolve unchanged via the MRO. Neutral dependencies import at
module top; the module-level ``logger`` is imported lazily inside the one method
that uses it (``from gateway.run import logger`` resolves at call time, when
``gateway.run`` is fully loaded) so this module never imports ``gateway.run`` at
import time -> no import cycle. The lazy import preserves the exact logger name
(``"gateway.run"``) so log records are unchanged.
    )annotationsN)Optional)Platform)SessionSource)expand_whatsapp_aliasesnormalize_whatsapp_identifierc                      e Zd ZdZ	 dddZdd
ZddddZddddZddddZddddZ	ddddZ
ddZddddZdS )GatewayAuthorizationMixinz6User/chat authorization methods for ``GatewayRunner``.NplatformOptional[Platform]profileOptional[str]c                    |sdS |pd                                 pd}|r2t          | dd          pi }||v r||                             |          S t          | dd          pi }|                    |          S )a  Resolve the live adapter whose intake policy should gate authorization.

        In multiplex mode, secondary-profile adapters live in
        ``_profile_adapters[profile]`` while the default/active profile uses
        ``self.adapters``. ``SessionSource.profile`` selects which map to consult.
        When a stamped profile has its own adapter registry entry, the default
        profile's same-platform adapter must not be consulted as a fallback.
        N _profile_adaptersadapters)stripgetattrget)selfr   r   profile_nameprofile_adaptersr   s         9/home/rurouni/.hermes/hermes-agent/gateway/authz_mixin.py_authorization_adapterz0GatewayAuthorizationMixin._authorization_adapter"   s      	42,,..6$ 	D&t-@$GGM2///'599(CCC4T228b||H%%%    sourceOptional[SessionSource]c                r    |dS |                      t          |dd          t          |dd                    S )z:Resolve the live adapter for an inbound ``SessionSource``.Nr   r   )r   r   )r   r   s     r   _adapter_for_sourcez-GatewayAuthorizationMixin._adapter_for_source9   sD    >4 **FJ--FIt,,
 
 	
r   r   returnboolc               z    |sdS |                      ||          }|dS t          t          |dd                    S )a  Whether the adapter for *platform* delegates authz to a trusted upstream.

        Mirrors ``BasePlatformAdapter.authorization_is_upstream``. The relay
        adapter sets this True: the Team Gateway connector authenticates the
        gateway's WS and resolves owner-only author bindings before delivering,
        so an inbound relay event is already authorized as this instance's bound
        user. Unlike ``_adapter_enforces_own_access_policy`` (a LOCAL config
        policy the gateway mirrors only when it's an allowlist), this is an
        UPSTREAM decision the gateway honors directly. Defaults to ``False`` when
        the adapter is unknown or doesn't expose the flag.
        FNauthorization_is_upstreamr   r"   r   r   r   r   adapters       r   "_adapter_authorization_is_upstreamz<GatewayAuthorizationMixin._adapter_authorization_is_upstreamD   sK    "  	5--h@@?5GG%@%HHIIIr   c               z    |sdS |                      ||          }|dS t          t          |dd                    S )a  Whether the adapter for *platform* gates access at intake itself.

        Mirrors ``BasePlatformAdapter.enforces_own_access_policy``. Adapters
        such as WeCom, Weixin, Yuanbao, QQBot, and WhatsApp evaluate their
        documented ``dm_policy`` / ``group_policy`` / ``allow_from`` config before a
        message is dispatched to the gateway. The flag alone is NOT "already
        authorized": these adapters default to ``open``, which forwards every
        sender, so ``_is_user_authorized`` only trusts the adapter when its
        effective policy for the chat type is an actual ``allowlist`` restriction
        (see that method). Defaults to ``False`` when the adapter is unknown or
        doesn't expose the flag.
        FNenforces_own_access_policyr%   r&   s       r   #_adapter_enforces_own_access_policyz=GatewayAuthorizationMixin._adapter_enforces_own_access_policy\   sM    $  	5 --h@@?5GG%A5IIJJJr   strc                  |sdS |                      ||          }|t          |dd          nd}|~t          | dd          }|*t          |d          r|j                            |          nd}|rt          |dd          nd}t          |t                    r|                    d          }t          |pd                                          	                                S )u  Best-effort read of an own-policy adapter's effective DM policy.

        Returns the lowercased ``dm_policy`` (``"open"`` / ``"allowlist"`` /
        ``"disabled"`` / ``"pairing"``) for *platform*, or ``""`` when unknown.
        Prefers the live adapter's resolved ``_dm_policy`` — which already folds
        in both ``config.extra`` and the ``<PLATFORM>_DM_POLICY`` env var (the
        env var is not always bridged back into ``config.extra``) — and falls
        back to ``config.extra`` for bare runners built without a live adapter.

        Used by ``_is_user_authorized`` to decide whether an own-policy adapter
        actually restricted DM senders to a configured allowlist (trustworthy)
        or merely forwarded everyone under ``dm_policy: open`` / for a pairing
        handshake (not authorization). "Reached the gateway" only carries an
        authorization signal in the ``allowlist`` case.
        r   N
_dm_policyconfig	platformsextra	dm_policy
r   r   hasattrr0   r   
isinstancedictr,   r   lowerr   r   r   r'   policyr/   platform_cfgr1   s           r   _adapter_dm_policyz,GatewayAuthorizationMixin._adapter_dm_policyx   s    *  	2--h@@9@9L,555RV>T8T22F %'&+*F*F%  $$X... 
 =IRGL'4888dE%&& 0;//6<R  &&((..000r   c                  |sdS |                      ||          }|t          |dd          nd}|~t          | dd          }|*t          |d          r|j                            |          nd}|rt          |dd          nd}t          |t                    r|                    d          }t          |pd                                          	                                S )a  Best-effort read of an own-policy adapter's effective group policy.

        Mirror of ``_adapter_dm_policy`` for group / forum / channel traffic:
        returns the lowercased ``group_policy`` (``"open"`` / ``"allowlist"`` /
        ``"disabled"``) for *platform*, or ``""`` when unknown. Prefers the live
        adapter's resolved ``_group_policy`` and falls back to ``config.extra``
        for bare runners built without a live adapter.

        Used by ``_is_user_authorized`` to decide whether an own-policy adapter
        restricted group senders to a configured allowlist (trustworthy) or
        forwarded the whole channel under ``group_policy: open`` (not
        authorization).
        r   N_group_policyr/   r0   r1   group_policyr3   r8   s           r   _adapter_group_policyz/GatewayAuthorizationMixin._adapter_group_policy   s    &  	2--h@@<C<O/4888UY>T8T22F %'&+*F*F%  $$X... 
 =IRGL'4888dE%&& 3>226<R  &&((..000r   chat_idc                  |r|sdS |                      ||          }|t          |dd          nd}|~t          | dd          }|*t          |d          r|j                            |          nd}|rt          |dd          nd}t          |t                    r|                    d          }t          |t                    sdS t          |          }	|                    |	          }
t          |
t                    st|	                                }|	                                D ]K\  }}t          |t                    r1|                                |k    rt          |t                    r|}
 nLt          |
t                    s|                    d          }
t          |
t                    sdS |
                    d	          p|
                    d
          }t          |t                    r!t          |                                          S t          |t          t          t          f          rt          d |D                       S dS )a  Whether a per-group sender allowlist gated this group message.

        WeCom supports ``groups.<group_id>.allow_from`` on top of the top-level
        ``group_policy``. A group may be open at the chat level while still
        restricting which senders inside that group can invoke Hermes. If such a
        message reached the gateway, the adapter already checked that sender
        allowlist, so it is a trustworthy intake decision rather than the
        fail-open ``group_policy: open`` case.
        FN_groupsr/   r0   r1   groups*
allow_from	allowFromc              3  X   K   | ]%}t          |                                          V  &d S N)r,   r   ).0items     r   	<genexpr>zPGatewayAuthorizationMixin._adapter_group_has_sender_allowlist.<locals>.<genexpr>   s2      BBTs4yy((BBBBBBr   )r   r   r4   r0   r   r5   r6   r,   r7   itemsr"   r   listtuplesetany)r   r   r@   r   r'   rC   r/   r:   r1   chat_id_str	group_cfgloweredkeyvaluesender_allows                  r   #_adapter_group_has_sender_allowlistz=GatewayAuthorizationMixin._adapter_group_has_sender_allowlist   sJ      	w 	5--h@@6=6I)T222t>T8T22F %'&+*F*F%  $$X... 
 =IRGL'4888dE%&& -8,,&$'' 	5'llJJ{++	)T** 	!''))G$llnn  
Uc3'' CIIKK7,B,BzRWY]G^G^,B %IE)T** 	(

3I)T** 	5 }}\22PimmK6P6PlC(( 	.**,,---lT5#$677 	CBB\BBBBBBur   r   c                   ddl m} |j        t          j        t          j        hv rdS |j        du s!|                     |j        |j                  rdS |j	        }|j
        dv r|j        rt          j        dt          j        di                    |j        d          }|rWt          j        |d                                          }|r.d	 |                    d
          D             }d|v s	|j        |v rdS t          j        dt          j        dt          j        dt          j        di}t-          |dd          rY|                    |j                  }|r=t          j        |d                                                                          dv rdS |sdS i t          j        dt          j        dt          j        dt          j        dt          j        dt          j        dt          j        dt          j        dt          j        dt          j        dt          j        dt          j        dt          j         d t          j!        d!t          j"        d"t          j#        d#t          j        d$t          j$        d%i}	t          j        d&i}
t          j        dt          j        di}i t          j        d't          j        d(t          j        d)t          j        d*t          j        d+t          j        d,t          j        d-t          j        d.t          j        d/t          j        d0t          j        d1t          j        d2t          j         d3t          j!        d4t          j"        d5t          j#        d6t          j        d7t          j$        d8i}|j        |	vre	 dd9l%m&} |                    |j        j'                  }|r,|j(        r|j(        |	|j        <   |j)        r|j)        ||j        <   n# tT          $ r Y nw xY w|                    |j        d          }|r+t          j        |d                                          d:v rdS t-          |d;d          du rdS |j        r|j        j'        nd}| j+        ,                    ||          rdS t          j        |	                    |j        d          d                                          }d}d}|j
        d<v rt          j        |
                    |j        d          d                                          }t          j        |                    |j        d          d                                          }t          j        d=d                                          }|s|s|s|s| -                    |j        |j                  r}|j
        dv rK| .                    |j        |j                  }| /                    |j        |j        |j                  rdS n!| 0                    |j        |j                  }|d>k    rdS t          j        d?d                                          d:v S |r>|j
        d<v r5|j        r.d@ |                    d
          D             }d|v s	|j        |v rdS |j        t          j        k    r|r|j
        d<v r|j        rzdA |                    d
          D             }|rYt-          | dBd          s=|1                    dCd
2                    tg          |                               d| _4        |j        |v rdS tk                      }|r2|6                    dD |                    d
          D                        |r2|6                    dE |                    d
          D                        |r2|6                    dF |                    d
          D                        d|v rdS |h}dG|v r.|7                    |                    dG          d                    |j        t          j        k    rtk                      }|D ]$}|6                    tq          |                     %|r|}|6                    tq          |                     ts          |          }|r|7                    |           |j        1|j        j'        dIk    r!|j:        r|7                    |j:                   tw          ||z            S )Jao  
        Check if a user is authorized to use the bot.
        
        Checks in order:
        1. Per-platform allow-all flag (e.g., DISCORD_ALLOW_ALL_USERS=true)
        2. Environment variable allowlists (TELEGRAM_ALLOWED_USERS, etc.)
        3. DM pairing approved list
        4. Global allow-all (GATEWAY_ALLOW_ALL_USERS=true)
        5. Default: deny
        r   )loggerTr    >   forumgroupchannelTELEGRAM_GROUP_ALLOWED_CHATSQQ_GROUP_ALLOWED_USERSr   c                ^    h | ]*}|                                 |                                 +S  r   )rI   cids     r   	<setcomp>z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<setcomp>>  s>     ) ) )99;;)		) ) )r   ,rD   DISCORD_ALLOW_BOTSFEISHU_ALLOW_BOTSTELEGRAM_ALLOW_BOTSSLACK_ALLOW_BOTSis_botFnone>   allmentionsTELEGRAM_ALLOWED_USERSDISCORD_ALLOWED_USERSWHATSAPP_ALLOWED_USERSWHATSAPP_CLOUD_ALLOWED_USERSSLACK_ALLOWED_USERSSIGNAL_ALLOWED_USERSEMAIL_ALLOWED_USERSSMS_ALLOWED_USERSMATTERMOST_ALLOWED_USERSMATRIX_ALLOWED_USERSDINGTALK_ALLOWED_USERSFEISHU_ALLOWED_USERSWECOM_ALLOWED_USERSWECOM_CALLBACK_ALLOWED_USERSWEIXIN_ALLOWED_USERSBLUEBUBBLES_ALLOWED_USERSQQ_ALLOWED_USERSYUANBAO_ALLOWED_USERSTELEGRAM_GROUP_ALLOWED_USERSTELEGRAM_ALLOW_ALL_USERSDISCORD_ALLOW_ALL_USERSWHATSAPP_ALLOW_ALL_USERSWHATSAPP_CLOUD_ALLOW_ALL_USERSSLACK_ALLOW_ALL_USERSSIGNAL_ALLOW_ALL_USERSEMAIL_ALLOW_ALL_USERSSMS_ALLOW_ALL_USERSMATTERMOST_ALLOW_ALL_USERSMATRIX_ALLOW_ALL_USERSDINGTALK_ALLOW_ALL_USERSFEISHU_ALLOW_ALL_USERSWECOM_ALLOW_ALL_USERSWECOM_CALLBACK_ALLOW_ALL_USERSWEIXIN_ALLOW_ALL_USERSBLUEBUBBLES_ALLOW_ALL_USERSQQ_ALLOW_ALL_USERSYUANBAO_ALLOW_ALL_USERS)platform_registry>   1yestruerole_authorized>   rZ   r[   GATEWAY_ALLOWED_USERS	allowlistGATEWAY_ALLOW_ALL_USERSc                ^    h | ]*}|                                 |                                 +S r`   ra   )rI   r@   s     r   rc   z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<setcomp>  sB     ! ! !$+RYR_R_RaRa!! ! !r   c                    h | ]=}|                                                     d           )|                                 >S )-)r   
startswith)rI   vs     r   rc   z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<setcomp>  sL       7799'',,		  r   #_warned_telegram_group_users_legacyu   TELEGRAM_GROUP_ALLOWED_USERS contains chat-ID-shaped values (%s). Treating them as chat IDs for backward compatibility. Move chat IDs to TELEGRAM_GROUP_ALLOWED_CHATS — the _USERS var is now for sender user IDs.c              3  f   K   | ],}|                                 |                                 V  -d S rH   ra   rI   uids     r   rK   z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<genexpr>  s<      ccsWZW`W`WbWbcsyy{{ccccccr   c              3  f   K   | ],}|                                 |                                 V  -d S rH   ra   r   s     r   rK   z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<genexpr>  s<      eesY\YbYbYdYdesyy{{eeeeeer   c              3  f   K   | ],}|                                 |                                 V  -d S rH   ra   r   s     r   rK   z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<genexpr>  s<      aasUXU^U^U`U`asyy{{aaaaaar   @Nsimplex)<gateway.runrY   r   r   HOMEASSISTANTWEBHOOKdelivered_via_upstream_relayr(   r   user_id	chat_typer@   TELEGRAMQQBOTr   osgetenvr   splitDISCORDFEISHUSLACKr   r7   WHATSAPPWHATSAPP_CLOUDSIGNALEMAILSMS
MATTERMOSTMATRIXDINGTALKWECOMWECOM_CALLBACKWEIXINBLUEBUBBLESYUANBAOgateway.platform_registryr   rU   allowed_users_envallow_all_env	Exceptionpairing_storeis_approvedr+   r?   rW   r;   warningjoinsortedr   rO   updateadd_expand_whatsapp_auth_aliases_normalize_whatsapp_identifier	user_namer"   )r   r   rY   r   chat_allowlist_envraw_chat_allowlistallowed_group_idsplatform_allow_bots_mapallow_bots_varplatform_env_mapplatform_group_user_env_mapplatform_group_chat_env_mapplatform_allow_all_mapr   entryplatform_allow_all_varplatform_nameplatform_allowlistgroup_user_allowlistgroup_chat_allowlistglobal_allowlisteffective_policylegacy_chat_idsallowed_ids	check_idsnormalized_allowed_ids
allowed_idnormalized_user_ids                               r   _is_user_authorizedz-GatewayAuthorizationMixin._is_user_authorized   s
    	'&&&&& ?x5x7GHHH4: .$66$:a:aON ;b ;
 ;
6 4. <<<<!#A 8" c&/2&&  " 	$%'Y/A2%F%F%L%L%N%N"% $) )#5#;#;C#@#@) ) )%
 ///6>EV3V3V#t 2O04N.	#
 68U++ 	488IIN ")NF"C"C"I"I"K"K"Q"Q"S"SWj"j"jt 	5
7
5
 7
 #%C	

 N1
 O3
 N1
 L-
 !;
 O3
 7
 O3
 N1
 #%C
 O3
   "=!
" N.#
$ 5%
 
* ='
# =N4'
#"
9"
7"
 9"
 #%E	"

 N3"
 O5"
 N3"
 L/"
 !="
 O5"
 9"
 O5"
 N3"
 #%E"
 O5"
   "?!"
" N0#"
$ 7%"
 "
, ?"222	GGGGGG)--fo.CDD V. T<A<S(9* VBGBU.v?    "8!;!;FOR!P!P! 	bi0F&K&K&Q&Q&S&SWk&k&k4 6,e44<<4 28H--b))-AA 	4  Y'7';';FOR'P'PRTUU[[]]!!111#%9-H-L-LV_^`-a-ace#f#f#l#l#n#n #%9-H-L-LV_^`-a-ace#f#f#l#l#n#n 9%<bAAGGII! /	\*> /	\G[ /	\dt /	\0 77 8     #'DDD'+'A'A & (B ( ($ ?? & @   $
  $t$ (,'>'> & (? ( ($ ${22496;;AACCG[[[
   	F$48J$J$Jv~$J! !/C/I/I#/N/N! ! ! '''6>=N+N+Nt Ox000$ 1 $666 7 -33C88  O
   t%JERR DNN6 !8!899   @DD<>_444 ee 	dcc6H6N6Ns6S6Scccccc 	fee6J6P6PQT6U6Ueeeeee 	baa6F6L6LS6Q6Qaaaaaa +4I	'>>MM'--,,Q/000 ?h///%(UU") Y Y
&--.KJ.W.WXXXX% 54:7CCDDD!?!H!H! 20111 O'%22  3 MM&*+++I+,,,s   #AO7 7
PPc               @   t          | dd          }|rht          |d          rX|rVt          |d          r|j                            |          nd}|r(dt          |di           v r|                    |          S |t
          j        k    rdS |r"t          |d          r|j        dk    r|j        S |r|                     ||	          }|s|rt          |d          r|j                            |          }|rt          |dd          nd}t          |t                    rHt          |                    d
          pd                                                                          }|dk    rdS |dv rdS |ryi t
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        dt
          j        d}t
          j        dt
          j        d i}t=          j        |                    |d          d                                          rdS |                    |d!          D ],}	t=          j        |	d                                          r dS -t=          j        d"d                                          rdS dS )#u}  Return how unauthorized DMs should be handled for a platform.

        Resolution order:
        1. Explicit per-platform ``unauthorized_dm_behavior`` in config — always wins.
        2. Email defaults to ``"ignore"`` unless explicitly opted into
           pairing. Inboxes may contain arbitrary unread human messages, so
           replying with pairing codes is not a safe platform default.
        3. Explicit global ``unauthorized_dm_behavior`` in config — wins for
           chat-shaped platforms when no per-platform override is set.
        4. When an adapter-level DM policy opts into pairing or silent drop, honor it.
        5. When an allowlist (``PLATFORM_ALLOWED_USERS``,
           ``PLATFORM_GROUP_ALLOWED_USERS`` / ``PLATFORM_GROUP_ALLOWED_CHATS``,
           or ``GATEWAY_ALLOWED_USERS``) is configured, default to ``"ignore"`` —
           the allowlist signals that the owner has deliberately restricted
           access; spamming unknown contacts with pairing codes is both noisy
           and a potential info-leak. (#9337)
        6. No allowlist and no explicit config → ``"pair"`` (open-gateway default).
        r/   Nget_unauthorized_dm_behaviorr0   unauthorized_dm_behaviorr1   ignorepairr    r2   r   pairing>   disabledr   rm   rn   ro   rp   rq   rr   rs   rt   ru   rv   rw   rx   ry   rz   r{   r|   r}   )r   r]   )r^   r`   r   ) r   r4   r0   r   r   r   r   r   r;   r5   r6   r,   r   r7   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )
r   r   r   r/   r:   r2   r1   r   platform_group_env_mapenv_keys
             r   _get_unauthorized_dm_behaviorz7GatewayAuthorizationMixin._get_unauthorized_dm_behaviorH  s   0 x..  	Egf&DEE 	E( 	E=DV[=Y=Yc6+//999_cL E :glT[]_>`>` ` `::8DDD x~%%8  	7gf&@AA 	7.&8866  
	 //'/JJI R RGFK,H,H R%/33H==@LVgt<<<RVeT** R #EIIk$:$:$@b A A G G I I O O Q QII%%v555x
  	$ !#;  #:  !#;  ')G	 
 #8  #9  #8  #6  #%?  #9  !#;  #9  #8  ')G  #9   $&A! " #5# ( ! $  ;&" y)--h;;R@@FFHH  x155hCC $ $9Wb))//11 $#88$ 9,b117799 	8vr   rH   )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   r!   r"   )__name__
__module____qualname____doc__r   r   r(   r+   r;   r?   rW   r   r   r`   r   r   r
   r
      sO       @@
 "&& & & & &.	
 	
 	
 	
 "&	J J J J J J8 "&	K K K K K K@ "&	#1 #1 #1 #1 #1 #1R "&	!1 !1 !1 !1 !1 !1P "&3 3 3 3 3 3jQ- Q- Q- Q-n
 "&	g g g g g g g gr   r
   )r   
__future__r   r   typingr   gateway.configr   gateway.sessionr   gateway.whatsapp_identityr   r   r   r   r
   r`   r   r   <module>r      s    " # " " " " " 				       # # # # # # ) ) ) ) ) )       P
 P
 P
 P
 P
 P
 P
 P
 P
 P
r   