
    `gjˎ                    n    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 dddZ G d	 d
      Zy)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                    | s|S 	 ddl m}  ||       }|2t        |      j                         rt        |      j                         S t        j                  |       xs |j                         S # t        $ r Y 2w xY w)zERead allowlist/auth env; prefer profile secret_scope under multiplex.r   )
get_secret)agent.secret_scoper
   strstrip	Exceptionosgetenv)namedefaultr
   vals       F/root/.hermes/venv/lib/python3.12/site-packages/gateway/authz_mixin.py	_auth_envr      sq    1?s3x~~/s8>>## IIdO&w--//  s   AA/ /	A;:A;c                      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Zdd	 	 	 	 	 ddZy)GatewayAuthorizationMixinz6User/chat authorization methods for ``GatewayRunner``.Nc                    |sy|xs dj                         xs d}|r/|dk7  r*t        | dd      xs i }||v r||   j                  |      S yt        | dd      xs i }|j                  |      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 r   _profile_adaptersadapters)r   getattrget)selfplatformprofileprofile_nameprofile_adaptersr   s         r   _authorization_adapterz0GatewayAuthorizationMixin._authorization_adapter1   s     2,,.6$LI5&t-@$GM2//'599(CC 4T28b||H%%    c                X    |y| j                  t        |dd      t        |dd            S )z:Resolve the live adapter for an inbound ``SessionSource``.Nr   r    )r#   r   )r   sources     r   _adapter_for_sourcez-GatewayAuthorizationMixin._adapter_for_sourceL   s7    > **FJ-FIt,
 	
r$   r    c               ^    |sy| j                  ||      }|y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.
        Fauthorization_is_upstreamr#   boolr   r   r   r    adapters       r   "_adapter_authorization_is_upstreamz<GatewayAuthorizationMixin._adapter_authorization_is_upstreamW   s9    " --h@?GG%@%HIIr$   c               ^    |sy| j                  ||      }|y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.
        Fenforces_own_access_policyr+   r-   s       r   #_adapter_enforces_own_access_policyz=GatewayAuthorizationMixin._adapter_enforces_own_access_policyo   s;    $  --h@?GG%A5IJJr$   c               |   |sy| j                  ||      }|t        |dd      nd}|jt        | dd      }|'t        |d      r|j                  j	                  |      nd}|rt        |dd      nd}t        |t              r|j	                  d      }t        |xs d      j                         j                         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   hasattrr6   r   
isinstancedictr   r   lowerr   r   r    r.   policyr5   platform_cfgr7   s           r   _adapter_dm_policyz,GatewayAuthorizationMixin._adapter_dm_policy   s    * --h@9@9L,5RV>T8T2F %'&+*F   $$X. 
 =IGL'48dE%&;/6<R &&(..00r$   c               |   |sy| j                  ||      }|t        |dd      nd}|jt        | dd      }|'t        |d      r|j                  j	                  |      nd}|rt        |dd      nd}t        |t              r|j	                  d      }t        |xs d      j                         j                         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_policyr5   r6   r7   group_policyr9   r>   s           r   _adapter_group_policyz/GatewayAuthorizationMixin._adapter_group_policy   s    & --h@<C<O/48UY>T8T2F %'&+*F   $$X. 
 =IGL'48dE%&>26<R &&(..00r$   c                  |r|sy| j                  ||      }|t        |dd      nd}|jt        | dd      }|'t        |d      r|j                  j	                  |      nd}|rt        |dd      nd}t        |t              r|j	                  d      }t        |t              syt        |      }	|j	                  |	      }
t        |
t              sa|	j                         }|j                         D ]>  \  }}t        |t              s|j                         |k(  s+t        |t              s<|}
 n t        |
t              s|j	                  d      }
t        |
t              sy|
j	                  d	      xs |
j	                  d
      }t        |t              rt        |j                               S t        |t        t        t        f      rt        d |D              S y)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_groupsr5   r6   r7   groups*
allow_from	allowFromc              3  N   K   | ]  }t        |      j                           y wN)r   r   ).0items     r   	<genexpr>zPGatewayAuthorizationMixin._adapter_group_has_sender_allowlist.<locals>.<genexpr>  s     BTs4y(Bs   #%)r#   r   r:   r6   r   r;   r<   r   r=   itemsr,   r   listtuplesetany)r   r   chat_idr    r.   rH   r5   r@   r7   chat_id_str	group_cfgloweredkeyvaluesender_allows                  r   #_adapter_group_has_sender_allowlistz=GatewayAuthorizationMixin._adapter_group_has_sender_allowlist   s     w--h@6=6I)T2t>T8T2F %'&+*F   $$X. 
 =IGL'48dE%&8,&$''lJJ{+	)T*!'')G$lln 
Uc3'CIIK7,BzRWY]G^ %I )T*

3I)T* }}\2PimmK6PlC(**,--lT5#$67B\BBBr$   c                n    t        | dd      xs i }t        |dd      }|r	||v r||   S t        | dd      S )a  Pick the per-profile PairingStore for a source, falling back to global.

        In a multiplexing gateway, each profile owns its own pairing whitelist
        so isolation is preserved. When the source has no profile (single-
        profile gateway, or a path that hasn't stamped profile yet) or the
        profile isn't registered, fall back to ``self.pairing_store`` (the
        global default) so existing behavior is preserved.
        pairing_storesNr    pairing_store)r   )r   r&   per_profiler    s       r   _pairing_store_forz,GatewayAuthorizationMixin._pairing_store_for  sJ     d$4d;Ar&)T2w+-w''t_d33r$   c                   ddl m} |j                  t        j                  t        j
                  hv ry|j                  du s'| j                  |j                  |j                        ry|j                  }|j                  dv r|j                  rt        j                  dt        j                  dij                  |j                  d      }|rut        j                   |d      j#                         }|rO|j%                  d	      D ch c]"  }|j#                         r|j#                         $ }}d
|v s|j                  |v ryt        j&                  dt        j(                  dt        j                  dt        j*                  di}t-        |dd      rR|j                  |j                        }	|	r5t        j                   |	d      j/                         j#                         dv ry|syi t        j                  dt        j&                  dt        j0                  dt        j2                  dt        j*                  dt        j4                  dt        j6                  dt        j8                  dt        j:                  dt        j<                  dt        j>                  dt        j(                  dt        j@                  dt        jB                  d t        jD                  d!t        jF                  d"t        j                  d#t        jH                  d$i}
t        j                  d%i}t        j                  dt        j                  di}i t        j                  d&t        j&                  d't        j0                  d(t        j2                  d)t        j*                  d*t        j4                  d+t        j6                  d,t        j8                  d-t        j:                  d.t        j<                  d/t        j>                  d0t        j(                  d1t        j@                  d2t        jB                  d3t        jD                  d4t        jF                  d5t        j                  d6t        jH                  d7i}|j                  |
vrx	 dd8l%m&} |j                  |j                  jN                        }|rJ|jP                  r|jP                  |
|j                  <   |jR                  r|jR                  ||j                  <   |j                  |j                  d      }|rtW        |      j/                         d9v ryt-        |d:d      du ry|j                  r|j                  jN                  nd}| jY                  |      }||j[                  ||      rytW        |
j                  |j                  d            }d}d}|j                  d;v rJtW        |j                  |j                  d            }tW        |j                  |j                  d            }tW        d<      }|s|s|s|s| j]                  |j                  |j                        r|j                  dv rZ| j_                  |j                  |j                        }| ja                  |j                  |j                  |j                        r(y| jc                  |j                  |j                        }|d=k(  rytW        d>      j/                         d9v S |rj|j                  d;v r\|j                  rP|j%                  d	      D ch c]#  }|j#                         s|j#                         % }}d
|v s|j                  |v ry|j                  t        j                  k(  r|r|j                  d;v r|j                  r|j%                  d	      D ch c]1  }|j#                         je                  d?      r|j#                         3 }}|rMt-        | d@d      s1|jg                  dAd	ji                  tk        |                   d| _6        |j                  |v ryto               }|r'|jq                  dB |j%                  d	      D               |r'|jq                  dC |j%                  d	      D               |r'|jq                  dD |j%                  d	      D               d
|v ry|h}dE|v r#|js                  |j%                  dE      d          |j                  t        j0                  k(  rgto               }|D ]  }|jq                  tu        |              |r|}|jq                  tu        |             tw        |      }|r|js                  |       |j                  @|j                  jN                  dFk(  r'|jx                  r|js                  |jx                         t{        ||z        S c c}w # tT        $ r Y w xY wc c}w c c}w )Gao  
        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   ,rI   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>   re   rf   GATEWAY_ALLOWED_USERS	allowlistGATEWAY_ALLOW_ALL_USERS-#_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  ^   K   | ]%  }|j                         s|j                          ' y wrM   r   rN   uids     r   rP   z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<genexpr>A  s"     csWZW`W`Wbsyy{c   --c              3  ^   K   | ]%  }|j                         s|j                          ' y wrM   r   r   s     r   rP   z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<genexpr>C  s"     esY\YbYbYdsyy{er   c              3  ^   K   | ]%  }|j                         s|j                          ' y wrM   r   r   s     r   rP   z@GatewayAuthorizationMixin._is_user_authorized.<locals>.<genexpr>E  s"     asUXU^U^U`syy{ar   @simplex)>gateway.runrd   r   r   HOMEASSISTANTWEBHOOKdelivered_via_upstream_relayr/   r    user_id	chat_typerV   TELEGRAMQQBOTr   r   r   r   splitDISCORDFEISHUSLACKr   r=   WHATSAPPWHATSAPP_CLOUDSIGNALEMAILSMS
MATTERMOSTMATRIXDINGTALKWECOMWECOM_CALLBACKWEIXINBLUEBUBBLESYUANBAOgateway.platform_registryr   r[   allowed_users_envallow_all_envr   r   rb   is_approvedr2   rE   r]   rA   
startswithwarningjoinsortedr   rT   updateadd_expand_whatsapp_auth_aliases_normalize_whatsapp_identifier	user_namer,   ) r   r&   rd   r   chat_allowlist_envraw_chat_allowlistcid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_namer`   platform_allowlistgroup_user_allowlistgroup_chat_allowlistglobal_allowlisteffective_policyrV   v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55x7G7GHH: ..$6$:a:aOONN ;b ;
 .. <<!!#A 8" c&//2&  "%'YY/A2%F%L%L%N"% $6#;#;C#@)99; 		)% )
 //6>>EV3V# 2OO04NN.	#
 68U+488IN"))NF"C"I"I"K"Q"Q"SWj"j
7
5
 7
 ##%C	

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

 NN3"
 OO5"
 NN3"
 LL/"
 !="
 OO5"
 9"
 OO5"
 NN3"
 ##%E"
 OO5"
    "?!"
" NN0#"
$ 7%"
, ??"22	G)--foo.C.CD..<A<S<S(9**BGBUBU.v?
 "8!;!;FOOR!P!i0F&G&M&M&OSg&g 6,e4<  28--b//7$)B)B=RY)Z ''7';';FOOR'PQ!!11#,-H-L-LV__^`-a#b #,-H-L-LV__^`-a#b $%<=!*>G[dt0 77 8  ##'DD'+'A'A & (B ($ ?? & @ 
  $'+'>'> & (? ($ ${267==?CWWW
  F$4$48J$Jv~~/C/I/I#/N!$+RYR_R_Ra! ! ''6>>=N+N OOx000$  $66 .33C8779'', 	O 
 t%JERNN6 !89 @DD<>>_4 ec6H6N6Ns6Scce6J6P6PQT6Ueea6F6L6LS6Qaa +I	'>MM'--,Q/0 ??h///%(U") Y
&--.KJ.WXY%4:7CD!?!H!01 OO'%%2  MM&**+I+,,Y)l  B!$s+   'gA7g  9g0g06g5 	g-,g-c               X   t        | dd      }|rYt        |d      rM|rKt        |d      r|j                  j                  |      nd}|r dt        |di       v r|j	                  |      S |t
        j                  k(  ry|r't        |d      r|j                  dk7  r|j                  S |r| j                  ||	      }|s|rt        |d      rv|j                  j                  |      }|rt        |dd      nd}t        |t              r:t        |j                  d
      xs d      j                         j                         }|dk(  ry|dv ry|ri 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
        j0                  dt
        j2                  dt
        j4                  dt
        j6                  dt
        j8                  dt
        j:                  d}t
        j                  dt
        j:                  d i}t=        j>                  |j                  |d      d      j                         ry|j                  |d!      D ](  }	t=        j>                  |	d      j                         s( y t=        j>                  d"d      j                         ryy)#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).
        r5   Nget_unauthorized_dm_behaviorr6   unauthorized_dm_behaviorr7   ignorepairr(   r8   r   pairing>   disabledr   rs   rt   ru   rv   rw   rx   ry   rz   r{   r|   r}   r~   r   r   r   r   r   )r   rh   )ri    r   ) r   r:   r6   r   r   r   r   r   rA   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    r5   r@   r8   r7   r   platform_group_env_mapenv_keys
             r   _get_unauthorized_dm_behaviorz7GatewayAuthorizationMixin._get_unauthorized_dm_behaviorn  s    0 x. gf&DE(=DV[=Y6++//9_cL :glT[]_>` `::8DD x~~% gf&@A..&8666 //'/JIGFK,H%//33H=@Lgt<RVeT* #EIIk$:$@b A G G I O O QII%55
  !!#;   #:  !!#;  '')G	 
 #8  #9  #8  #6  ##%?  #9  !!#;  #9  #8  '')G  #9   $$&A! " #5# ( !! $  ;&" yy)--h;R@FFH155hC $99Wb)//1#$ 99,b1779r$   rM   )r   Optional[Platform]r    Optional[str])r&   zOptional[SessionSource])r   r   r    r   returnr,   )r   r   r    r   r   r   )r   r   rV   r   r    r   r   r,   )r&   z'SessionSource')r&   r   r   r,   )__name__
__module____qualname____doc__r#   r'   r/   r2   rA   rE   r]   rb   r   r   r   r$   r   r   r   .   s0   @
 "&&$& &6	
 "&	J$J 	J
 
J8 "&	K$K 	K
 
K@ "&	#1$#1 	#1
 
#1R "&	!1$!1 	!1
 
!1P "&3$3 3
 3 
3j4U-v
 "&	g$g 	g
 
gr$   r   )r   )r   r   r   r   r   r   )r   
__future__r   r   typingr   gateway.configr   gateway.sessionr   gateway.whatsapp_identityr   r   r   r   r   r   r   r$   r   <module>r     s1   " # 	  # )0g
 g
r$   