
    
Bj                         U d Z ddlmZ ddlmZ ddlmZmZmZm	Z	m
Z
  eddh      Zded<    ed	
       G d d             Z eh d      ZddZddZddZddZddZddZddZg dZy)u'  Per-platform slash command access control.

This module sits beside the existing per-platform allowlist (``allow_from``)
and adds a second axis: of the users who are *allowed to talk to the
gateway*, which ones can run *which slash commands*.

Two lists per platform scope (DM vs group, mirroring ``allow_from`` vs
``group_allow_from``):

  - ``allow_admin_from``      — user IDs that get every registered slash
                                command (built-in + plugin-registered).
  - ``user_allowed_commands`` — slash command names non-admin users may
                                run. Empty / unset → non-admins get no
                                slash commands.

Backward compatibility:

  If ``allow_admin_from`` is not set for a scope, slash command gating
  is disabled entirely for that scope. Every allowed user can run every
  slash command, exactly like before. This means existing installs are
  unaffected until an operator opts in by listing at least one admin.

The gate is applied at the slash command dispatch site in
``gateway/run.py`` so it covers BOTH built-in and plugin-registered
commands via the live registry. Gating slash commands does not affect
plain chat — non-admin users can still talk to the agent normally,
they just can't trigger commands outside ``user_allowed_commands``.

Authored as a slimmed-down salvage of PR #4443's permission tiers
(co-authored by @ReqX). The full tier system, audit log, usage
tracking, rate limiting, and tool filtering from that PR are not
included here — only the slash-command access split.
    )annotations)	dataclass)Any	FrozenSetIterableOptionalTuplehelpwhoamiFrozenSet[str]_ALWAYS_ALLOWED_FOR_USERST)frozenc                  @    e Zd ZU dZded<   ded<   ded<   d
dZddZy	)SlashAccessPolicyu  Resolved access policy for a single (platform, scope) pair.

    ``scope`` is ``"dm"`` for direct messages and ``"group"`` for groups,
    channels, threads, and any other multi-user context. The mapping from
    SessionSource.chat_type → scope happens in ``policy_for_source``.
    boolenabledr   admin_user_idsuser_allowed_commandsc                P    | j                   sy|syt        |      | j                  v S NTF)r   strr   )selfuser_ids     9/home/ubuntu/.hermes/hermes-agent/gateway/slash_access.pyis_adminzSlashAccessPolicy.is_adminE   s)    || 7|t2222    c                t    | j                   sy| j                  |      ry|sy|t        v ry|| j                  v S r   )r   r   r   r   )r   r   canonical_cmds      r   can_runzSlashAccessPolicy.can_runO   s>    ||==!55 : :::r   N)r   Optional[str]returnr   )r   r    r   r   r!   r   )__name__
__module____qualname____doc____annotations__r   r    r   r   r   r   8   s$     M""))3	;r   r   >    dmdirectprivatec                :   | 
t               S t        | t        t        t        t         f      r| }n,t        | t
              rd | j                  d      D        }n| f}g }|D ]/  }t        |      j                         }|s|j                  |       1 t        |      S )zNormalize a YAML-loaded admin/user list into a frozenset of strings.

    Accepts ``None``, list, tuple, or comma-separated string. Stringifies
    each entry and strips whitespace; empty entries are dropped.
    c              3  B   K   | ]  }|j                         s|  y wNstrip.0ss     r   	<genexpr>z"_coerce_id_list.<locals>.<genexpr>i        8qaggi8   ,)		frozenset
isinstancelisttuplesetr   splitr0   appendrawitemsoutitr3   s        r   _coerce_id_listrD   ^   s     {{#eS)45"	C	8CIIcN8 C GMMOJJqM S>r   c                t   | 
t               S t        | t        t        t        t         f      r| }n,t        | t
              rd | j                  d      D        }n| f}g }|D ]L  }t        |      j                         j                  d      j                         }|s<|j                  |       N t        |      S )zNormalize a slash command allowlist.

    Strips leading slashes so YAML can read either ``["help", "status"]``
    or ``["/help", "/status"]``. Lowercase canonicalization matches how
    ``resolve_command()`` stores names.
    c              3  B   K   | ]  }|j                         s|  y wr.   r/   r1   s     r   r4   z'_coerce_command_list.<locals>.<genexpr>   r5   r6   r7   /)r8   r9   r:   r;   r<   r   r=   r0   lstriplowerr>   r?   s        r   _coerce_command_listrJ   u   s     {{#eS)45"	C	8CIIcN8C GMMO""3'--/JJqM S>r   c                6    | r| j                         t        v ryy)Nr)   group)rI   _DM_CHAT_TYPES)	chat_types    r   _scope_for_chat_typerO      s    Y__&.8r   c                p    | i S t        | dd      }t        |t              r|S t        | t              r| S i S )zReturn the ``extra`` dict from a PlatformConfig-like object.

    Defensively handles None and non-PlatformConfig shapes so calling
    code can stay simple.
    Nextra)getattrr9   dict)platform_configrQ   s     r   _platform_extrarU      sA     	OWd3E%/4(Ir   c                    | dk(  ryy)z3Return (admin_key, user_cmd_key) names for a scope.rL   )group_allow_admin_fromgroup_user_allowed_commands)allow_admin_fromr   r'   )scopes    r   _keys_for_scoper[      s    H8r   c                    t        |      \  }}t        | j                  |            }t        | j                  |            }|dk(  r|st        | j                  d            }t	        |      }t        |||      S )a  Build a policy from a platform's ``extra`` dict for one scope.

    DM scope falls back to group scope keys ONLY for ``user_allowed_commands``
    when the DM scope didn't specify its own. This keeps the common case
    (operator wants the same command set DM and group) ergonomic without
    forcing duplication. Admin lists are NOT cross-scope: an admin in
    DMs is not implicitly an admin in a group.
    r)   rX   r   r   r   )r[   rD   getrJ   r   r   )rQ   rZ   	admin_keycmd_key	admin_idscmdsr   s          r   policy_from_extrarc      sw     )/Iw		) 45I		' 23D}T $EII.K$LM9oG " r   c                   | |t        dt               t                     S t        | dd      }d}|	 |j                  |j                        }t        |      }t        t        |dd            }t        ||      S # t
        $ r d}Y :w xY w)a  Resolve the access policy for a SessionSource.

    Returns a "disabled" policy (gating off, allow everything) when:
      - gateway_config is None
      - the platform has no PlatformConfig
      - the platform's PlatformConfig has no admin list set for the scope

    Callers should treat the returned policy as authoritative for slash
    command gating only. It does not gate plain chat messages.
    NFr]   	platformsrN   )	r   r8   rR   r^   platform	ExceptionrU   rO   rc   )gateway_configsourcere   rT   rQ   rZ   s         r   policy_for_sourcerj      s      $;"++
 	

 T:IO	#'mmFOO<O O,E d!CDEUE**	  	#"O	#s   A= =B
B)r   rc   rj   N)r@   r   r!   r   )rN   r    r!   r   )rT   r   r!   rS   )rZ   r   r!   zTuple[str, str])rQ   rS   rZ   r   r!   r   )rh   r   ri   r   r!   r   )r%   
__future__r   dataclassesr   typingr   r   r   r   r	   r8   r   r&   r   rM   rD   rJ   rO   rU   r[   rc   rj   __all__r'   r   r   <module>ro      s    D # ! < < -6
7 - >  $; ; ;D :;.."94+:r   