Ë
    âß²jy  ã                  ó  — U d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZmZmZ ddlmZmZmZmZ  ej(                  e«      ZdZd	Zd
ZdZdZdaded<   dd„Zedœdd„Z G d„ dee«      Z dd„Z!dd„Z"dd„Z#ddiZ$d„ Z%y)ur  DrainSecretProvider â€” shared-bearer-secret auth for the drain-control endpoint.

Non-interactive token capability of the ``DashboardAuthProvider`` ABC (``verify_token`` +
the ``token_auth`` middleware seam): ``nous-account-service`` provisions a per-agent unique
secret (``HERMES_DASHBOARD_DRAIN_SECRET``, env-only â€” it is a credential); an inbound bearer
is compared constant-time and vouched for as the ``drain-control`` principal. Fail-CLOSED
entropy gate at registration (length, distinct chars, Shannon bits); interactive ABC methods
raise. Knobs ``scope`` / ``min_secret_chars`` live under ``dashboard.drain_auth``.
é    )ÚannotationsN)ÚCounter)ÚOptional)ÚDashboardAuthProviderÚSessionÚTokenPrincipal)ÚNonInteractiveMixinÚSkipRegistrationÚload_config_sectionÚregister_providerzdashboard-auth-drainé+   é   g      `@z/api/gateway/drainÚ ÚstrÚLAST_SKIP_REASONc                ó‚   ‡— | syt        | «      Št        ˆfd„t        | «      j                  «       D «       «       }|‰z  S )zJTotal Shannon entropy (bits) of ``value`` over its character distribution.g        c              3  óZ   •K  — | ]"  }|‰z  t        j                  |‰z  «      z  –— Œ$ y ­w©N)ÚmathÚlog2)Ú.0ÚcÚns     €úJ/home/ubuntu/.hermes/hermes-agent/plugins/dashboard_auth/drain/__init__.pyú	<genexpr>z _shannon_bits.<locals>.<genexpr>,   s'   øè ø€ ÒP°1�Q˜‘UœdŸi™i¨¨A©Ó.Õ.ÑPùs   ƒ(+)ÚlenÚsumr   Úvalues)ÚvalueÚper_charr   s     @r   Ú_shannon_bitsr!   '   s<   ø€ áØÜˆE‹
€AÜÓP¼À»×8MÑ8MÓ8OÔPÓPÐP€HØ�a‰<Ðó    ©Ú	min_charsc               óä   — | syt        | «      |k  rdt        | «      › d|› d�S t        t        | «      «      }|t        k  rd|› dt        › d�S t        | «      }|t        k  rd|d	›d
t        d	›d�S y)zÑHuman-readable rejection reason if ``secret`` is too weak, else ``None``. Checks, in
    order: length >= ``min_chars``, distinct chars >= ``_MIN_DISTINCT_CHARS``, Shannon
    entropy >= ``_MIN_SHANNON_BITS``.zsecret is emptyzsecret too short: z chars (need >= z]; use a >=256-bit value, e.g. `python -c "import secrets; print(secrets.token_urlsafe(32))"`)zsecret has only z distinct characters (need >= z); looks structured/low-entropyzsecret entropy too low: z.0fz bits (need >= z); looks structured/repeatedN)r   ÚsetÚ_MIN_DISTINCT_CHARSr!   Ú_MIN_SHANNON_BITS)Úsecretr$   ÚdistinctÚbitss       r   Úassess_secret_strengthr,   0   s�   € ñ Ø Ü
ˆ6ƒ{�YÒà ¤ V£ Ð-=¸i¸[ð I3ð 3ð	4ô ”3�v“;Ó€HØÔ%Ò%Ø! ( Ð+IÔJ]ÐI^Ð^}Ð~Ð~Ü˜Ó €DØÔÒØ)¨$¨s¨°?ÔCTÐUXÐBYÐYuÐvÐvØr"   c                  óV   — e Zd ZdZdZdZdZdZdZdZ	dd	œdd
„Z
dd„Zdd„Zdd„Zdd„Zy)ÚDrainSecretProviderz@Non-interactive shared-bearer-secret provider for drain control.zdrain-secretz"Drain Control (service credential)TFz<DrainSecretProvider is a non-interactive service credential.zTDrainSecretProvider is a non-interactive service credential; there is no login flow.Údrain)Úscopec               ó^   — t        |«      }|�t        d|› �«      ‚|| _        |xs d| _        y )Nzdrain secret rejected: r/   )r,   Ú
ValueErrorÚ_secretÚ_scope)Úselfr)   r0   Úreasons       r   Ú__init__zDrainSecretProvider.__init__N   s:   € ô (¨Ó/ˆØÐÜÐ6°v°hÐ?Ó@Ð@ØˆŒØÒ&˜wˆ�r"   c               óÊ   — |rat        j                  |j                  d«      | j                  j                  d«      «      r#t	        d| j
                  | j                  f¬«      S y)z„Constant-time compare; ``drain-control`` principal on match, else
        ``None`` so the generic seam falls through / fails closed.zutf-8zdrain-control)Ú	principalÚproviderÚscopesN)ÚhmacÚcompare_digestÚencoder3   r   Únamer4   )r5   Útokens     r   Úverify_tokenz DrainSecretProvider.verify_tokenY   sO   € ñ ”T×(Ñ(¨¯©°gÓ)>ÀÇÁ×@SÑ@SÐT[Ó@\Ô]Ü!¨OÀdÇiÁiÐY]×YdÑYdÐXfÔgÐgØr"   c                ó   — y r   © )r5   Úaccess_tokens     r   Úverify_sessionz"DrainSecretProvider.verify_sessionb   s   € ð r"   c               ó,   — t        | j                  «      ‚r   )ÚNotImplementedErrorÚ_NOT_INTERACTIVE©r5   Úrefresh_tokens     r   Úrefresh_sessionz#DrainSecretProvider.refresh_sessiong   s   € Ü! $×"7Ñ"7Ó8Ð8r"   c                ó   — y r   rC   rI   s     r   Úrevoke_sessionz"DrainSecretProvider.revoke_sessionj   s   € Ør"   N)r)   r   r0   r   ÚreturnÚNone)r@   r   rN   zOptional[TokenPrincipal])rD   r   rN   zOptional[Session])rJ   r   rN   r   )rJ   r   rN   rO   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r?   Údisplay_nameÚsupports_tokenÚsupports_sessionrH   Ú_NO_START_LOGINr7   rA   rE   rK   rM   rC   r"   r   r.   r.   D   sA   „ ÙJà€DØ7€LØ€NØÐØUÐØl€Oà4;õ 'óóó
9ôr"   r.   c                 ó.   — t        t        t        dd«      S )NÚ	dashboardÚ
drain_auth)r   ÚloggerÚ_TAGrC   r"   r   Ú_load_config_drain_auth_sectionr]   p   s   € Üœv¤t¨[¸,ÓGÐGr"   c                 ó®  — t         j                  j                  dd«      j                  «       } | st	        d«      ‚t        «       }t        |j                  dd«      xs d«      j                  «       xs d}	 t        |j                  dt        «      «      }t        | |¬«      }|�t	        d|› d	�d
¬«      ‚| |dœS # t        t        f$ r	 t        }Y Œ<w xY w)zPResolve DrainSecretProvider kwargs from env/config; raises ``SkipRegistration``.ÚHERMES_DASHBOARD_DRAIN_SECRETr   zçHERMES_DASHBOARD_DRAIN_SECRET is not set. Set a per-agent >=256-bit secret (e.g. `python -c "import secrets; print(secrets.token_urlsafe(32))"`) to enable NAS-driven drain coordination; leave it unset to disable the drain endpoint.r0   r/   Úmin_secret_charsr#   u+   HERMES_DASHBOARD_DRAIN_SECRET rejected â€” z2. The drain endpoint stays disabled (fail-closed).Úwarning)Úlevel)r)   r0   )ÚosÚenvironÚgetÚstripr
   r]   r   ÚintÚ_DEFAULT_MIN_SECRET_CHARSÚ	TypeErrorr2   r,   )r)   Úsectionr0   r$   r6   s        r   Ú	_settingsrk   t   sß   € ä�Z‰Z�^‰^Ð;¸RÓ@×FÑFÓH€FÙÜð[ó\ð 	\ô .Ó/€GÜ�—‘˜G WÓ-Ò8°Ó9×?Ñ?ÓAÒLÀW€Eð.Ü˜Ÿ™Ð$6Ô8QÓRÓSˆ	ô $ F°iÔ@€FØÐÜØ9¸&¸ÐAsÐtØôð 	ð  uÑ-Ð-øô ”zÐ"ò .Ü-Š	ð.ús   Á8B< Â<CÃCc                ó  — da t        | t        t        t        t
        «      \  }a |€y	 ddlm}  |t        «       t        j                  d|d   t        «       y# t        $ r%}t        j                  dt        |«       Y d}~ŒHd}~ww xY w)a   Register ``DrainSecretProvider`` when a strong secret is set; no-op (records a skip
    reason) when ``HERMES_DASHBOARD_DRAIN_SECRET`` is unset or fails the entropy gate. On
    success also registers the drain route as token-authable via the generic seam.r   Nr   )Úregister_token_routez;dashboard-auth-drain: could not register token route %s: %szWdashboard-auth-drain: registered drain service-credential provider (scope=%s, route=%s)r0   )r   r   r[   r\   r.   rk   Ú$hermes_cli.dashboard_auth.token_authrm   ÚDRAIN_ROUTE_PATHÚ	Exceptionra   Úinfo)ÚctxÚkwargsrm   Úexcs       r   Úregisterru   Š   sƒ   € ð
 ÐÜ0°´f¼dÔDWÔYbÓcÑ€FÐØ€~ØðmÝMáÔ-Ô.ô ‡K�KØaØˆw‰Ô)õ+øô ò mÜ�‰ÐTÔVfÐhk×lÑlûðmús   ©A Á	BÁ#BÂBÚ
LoginStart)zhermes_cli.dashboard_authrv   c                óÌ   — t         j                  | «      }|€t        dt        ›d| ›�«      ‚dd l}ddlm}  |t        | g|¢­Ž  t        |j                  |d   «      |d   «      S )Nzmodule z has no attribute r   )Ú	warn_onceé   )	Ú_PLUGIN_COMPAT_LAZYre   ÚAttributeErrorrP   Ú	importlibÚhermes_cli.plugin_compatrx   ÚgetattrÚimport_module)r?   Útargetr|   rx   s       r   Ú__getattr__r�   «   se   € Ü ×$Ñ$ TÓ*€FØ€~Ü˜w¤x lÐ2DÀTÀHÐMÓNÐNÛÝ2ÙŒh˜Ð&˜vÓ&Ü�9×*Ñ*¨6°!©9Ó5°v¸a±yÓAÐAr"   )r   r   rN   Úfloat)r)   r   r$   rg   rN   zOptional[str])rN   Údict)rN   rO   )&rS   Ú
__future__r   r<   Úloggingr   rc   Úcollectionsr   Útypingr   Úhermes_cli.dashboard_authr   r   r   Úplugins.dashboard_auth._sharedr	   r
   r   r   Ú	getLoggerrP   r[   r\   rh   r'   r(   ro   r   Ú__annotations__r!   r,   r.   r]   rk   ru   rz   r�   rC   r"   r   ú<module>rŒ      s²   ðòõ #ã Û Û Û 	Ý Ý ç TÑ Tß xÓ xà	ˆ×	Ñ	˜8Ó	$€Ø€ð Ð àÐ àÐ ð (Ð àÐ �#Ó óð =Võ ô('Ð-Ð/Dô 'óXHó.ó,+ð: Ð=ðÐ ó
Br"   