Ë
    âß²ji4  ã                  óÀ  — 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ZddlZ	ddl
mZmZmZmZ ddlZddlmZmZmZmZmZmZmZ dZdZdd	iZd d
„Zd!d„Z G d„ de«      Z	 	 	 	 	 	 	 	 	 	 d"d„Zd#d„Z d$d„Z!d%d„Z"d&d„Z#ddœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d'd„Z$d(d)d„Z%dddddœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d*d„Z&d+d„Z'	 	 	 	 	 	 	 	 	 	 	 	 	 	 d,d„Z( G d„ d«      Z) G d„ de«      Z*y)-a:  Helpers shared by the bundled dashboard-auth providers.

Each provider module keeps its own ``logger`` / ``LAST_SKIP_REASON`` (the gate reads
those by module) and its ``register(ctx)``; the config/env resolution, skip/register
bookkeeping, PKCE login start, token-endpoint exchange and JWT verification live here.
é    )ÚannotationsN)ÚAnyÚCallableÚDictÚOptional)ÚDashboardAuthProviderÚInvalidCodeErrorÚ
LoginStartÚProviderErrorÚRefreshExpiredErrorÚSessionÚclassify_jwks_lookup_errori,  g      $@ÚAcceptúapplication/jsonc                ó¸   — 	 ddl m}m}  |«       } ||g|¢­ddiŽ}t        |t        «      r|S i S # t        $ r}| j	                  d||«       i cY d}~S d}~ww xY w)u¥   The ``config.yaml`` block at ``path`` as a dict, or ``{}`` â€” robust to load_config()
    raising (fresh install, malformed YAML), absent keys, or a non-dict value.r   )Úcfg_getÚload_configzC%s: load_config() raised %s; falling back to env-only configurationNÚdefault)Úhermes_cli.configr   r   Ú	ExceptionÚdebugÚ
isinstanceÚdict)ÚloggerÚtagÚpathr   r   ÚcfgÚexcÚsections           úC/home/ubuntu/.hermes/hermes-agent/plugins/dashboard_auth/_shared.pyÚload_config_sectionr!       se   € ðß:á‹mˆñ �cÐ/˜DÒ/¨$Ñ/€GÜ  ¬$Ô/ˆ7Ð7°RÐ7øô	 ò Ø�‰ÐZÐ\_ÐadÔeØ�	ûðús   ‚1 ±	AºAÁAÁAc                óœ   — t         j                  j                  | d«      j                  «       xs t	        |xs d«      j                  «       S )zŠEnv-wins-over-config; an empty env value is treated as unset so a
    provisioned-but-blank secret can't shadow a valid config.yaml entry.Ú )ÚosÚenvironÚgetÚstripÚstr)Úenv_nameÚ	cfg_values     r    Úresolve_env_or_cfgr+   .   s9   € ô �:‰:�>‰>˜( BÓ'×-Ñ-Ó/ÒO´3°y²ÀBÓ3G×3MÑ3MÓ3OÐOó    c                  ó&   ‡ — e Zd ZdZddˆ fd„Zˆ xZS )ÚSkipRegistrationz™Raised by a provider's settings resolver to decline registration; ``reason`` is
    the operator-facing text stored in the module's ``LAST_SKIP_REASON``.c                óB   •— t         ‰| �  |«       ||c| _        | _        y ©N)ÚsuperÚ__init__ÚreasonÚlevel)Úselfr3   r4   Ú	__class__s      €r    r2   zSkipRegistration.__init__:   s    ø€ Ü‰Ñ˜Ô Ø"(¨%ÐˆŒ�T•Zr,   )r   )r3   r(   r4   r(   ÚreturnÚNone)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r2   Ú__classcell__)r6   s   @r    r.   r.   6   s   ø„ ñM÷0ò 0r,   r.   c                ób  — 	  |«       } |di |¤Ž}| j                  |«       |dfS # t         $ r@} t        ||j                  «      d||j                  «       d|j                  fcY d}~S d}~wt        t
        f$ r2}|j                  › d|› �}	|j                  d||	«       d|	fcY d}~S d}~ww xY w)aV  Build ``provider_cls(**settings())`` and register it on ``ctx``.

    Returns ``(kwargs, "")`` on success, ``(None, skip_reason)`` when ``settings`` raised
    ``SkipRegistration`` (logged at its level) or construction raised ``ValueError`` /
    ``ProviderError`` (logged as a warning). Callers store the reason in ``LAST_SKIP_REASON``.
    z%s: %sNz construction failed: r#   © )	r.   Úgetattrr4   r3   Ú
ValueErrorr   r9   ÚwarningÚ register_dashboard_auth_provider)
Úctxr   r   Úprovider_clsÚsettingsÚkwargsÚproviderÚskipr   r3   s
             r    Úregister_providerrJ   ?   s³   € ð	Ù“ˆÙÑ) &Ñ)ˆð ×(Ñ(¨Ô2Ø�2ˆ:Ðøô ò !Ø#Œ�˜Ÿ
™
Ó# H¨c°4·;±;Ô?Ø�T—[‘[Ð Õ ûÜœÐ&ò Ø ×)Ñ)Ð*Ð*@ÀÀÐFˆØ�‰�x  fÔ-Ø�Vˆ|Õûðús,   ‚& ¦	B.¯5A*Á$B.Á*B.Á<'B)Â#B.Â)B.c                óf   — t        j                  | «      j                  d«      j                  «       S )u6   Base64url-encode without ``=`` padding (RFC 7636 Â§4).ó   =)Úbase64Úurlsafe_b64encodeÚrstripÚdecode)Úraws    r    Úb64url_no_padrR   X   s'   € ä×#Ñ# CÓ(×/Ñ/°Ó5×<Ñ<Ó>Ð>r,   c                óä   — t         j                  j                  | «      }|j                  dvrt	        d| ›�«      ‚|j
                  r|j
                  j                  d«      st	        d| ›�«      ‚y)zÚFast-fail obviously-broken redirect_uris before bouncing to the IDP (whose allowlist
    is authoritative). Any ``http://`` host is allowed so dashboards behind TLS-terminating
    proxies / on LAN IPs aren't rejected.)ÚhttpsÚhttpz"redirect_uri must be http(s), got z/auth/callbackz6redirect_uri path must end with '/auth/callback', got N)ÚurllibÚparseÚurlparseÚschemer   r   Úendswith)Úredirect_uriÚparseds     r    Úvalidate_redirect_urir]   ]   sm   € ô �\‰\×"Ñ" <Ó0€FØ‡}�}Ð-Ñ-ÜÐ@ÀÐ@PÐQÓRÐRØ�;Š;˜fŸk™k×2Ñ2Ð3CÔDÜÐTÐUaÐTdÐeÓfÐfð Er,   c               ón  — t        t        j                  d«      «      }t        t        j                  d«      «      }d||||t        t        j                  |j                  d«      «      j                  «       «      ddœ}t        | › dt        j                  j                  |«      › �dd	|› d
|› �i¬«      S )u  Build the authorization-code + PKCE (S256) redirect and cookie payload. Callers
    validate ``redirect_uri`` first. The auth-route layer expects
    ``cookie_payload["hermes_session_pkce"]`` as a flat ``state=â€¦;verifier=â€¦`` string
    (it prepends ``provider=``).é@   é    ÚcodeÚasciiÚS256)Úresponse_typeÚ	client_idr[   ÚscopeÚstateÚcode_challengeÚcode_challenge_methodú?Úhermes_session_pkcezstate=z
;verifier=)Úredirect_urlÚcookie_payload)rR   ÚsecretsÚtoken_bytesÚhashlibÚsha256ÚencodeÚdigestr
   rV   rW   Ú	urlencode)Úauthorize_urlre   rf   r[   Úcode_verifierrg   Úparamss          r    Úpkce_login_startrx   h   s®   € ô
 "¤'×"5Ñ"5°bÓ"9Ó:€MÜœ'×-Ñ-¨bÓ1Ó2€Eà¨iÈÐ`eÐpuÜ'¬¯©°}×7KÑ7KÈGÓ7TÓ(U×(\Ñ(\Ó(^Ó_Ø!'ñ)€Fô Ø%� a¬¯©×(>Ñ(>¸vÓ(FÐ'GÐHØ-°¸°w¸jÈÈÐ/XÐYô[ð [r,   c                óÈ   — | j                   j                  dd«      j                  d«      si S 	 | j                  «       }t        |t        «      r|S i S # t        $ r i cY S w xY w)zOJSON object body, or ``{}`` for non-JSON content-type / parse error / non-dict.zcontent-typer#   r   )Úheadersr&   Ú
startswithÚjsonrA   r   r   )ÚresponseÚbodys     r    Úparse_json_bodyr   x   se   € à×Ñ×Ñ °Ó3×>Ñ>Ð?QÔRØˆ	ðØ�}‰}‹ˆô ˜d¤DÔ)ˆ4Ð1¨rÐ1øô ò ØŠ	ðús   ¯A ÁA!Á A!)rz   c          	     ó„  — 	 t        j                  | |i t        ¥|xs i ¥t        ¬«      }|j                  dk(  r(t        |«      j                  dd«      }
 ||› d|
› �«      ‚|j                  dk7  r*t        |› d	|j                  › d
|j                  dd ›�«      ‚t        |«      }|j                  |«      }|rt        |t        «      st        |«      ‚t        |j                  dd«      «      j                  «       }|r|dk7  rt        d|›�«      ‚||fS # t         j                  $ r}	t        |› d|	› �«      |	‚d}	~	ww xY w)uä  POST a token grant and return ``(token, payload)``.

    A 400 (OAuth-shaped error envelope) raises ``bad_request_exc`` â€” ``InvalidCodeError``
    for the auth-code path, ``RefreshExpiredError`` for refresh â€” so the middleware's
    distinct handling is preserved. Any other non-200, transport failure, missing
    ``token_key`` or non-bearer ``token_type`` raises ``ProviderError``. Redirects are
    deliberately NOT followed: the body carries an auth code / refresh token.
    )Údatarz   Útimeoutz unreachable: Ni�  ÚerrorÚinvalid_requestz rejected token request: éÈ   z
 returned z: Ú
token_typer#   Úbearerzunexpected token_type=)ÚhttpxÚpostÚJSON_HEADERSÚTOKEN_ENDPOINT_TIMEOUT_SECÚRequestErrorr   Ústatus_coder   r&   Útextr   r(   Úlower)Úurlr�   rz   Úbad_request_excÚidpÚendpointÚ	token_keyÚmissing_msgr}   r   Ú
error_codeÚpayloadÚtokenr†   s                 r    Úexchange_tokenr™   ƒ   sQ  € ðGÜ—:‘:˜c¨Ð6Y¼Ð6YÈ'Ê-ÐUWÐ6YÔc}Ô~ˆð ×Ñ˜sÒ"Ü$ XÓ.×2Ñ2°7Ð<MÓNˆ
Ù  Ð%>¸z¸lÐKÓLÐLØ×Ñ˜sÒ"Ü˜x˜j¨
°8×3GÑ3GÐ2HÈÈ8Ï=É=ÐY]ÐZ]ÐK^ÐJaÐbÓcÐcÜ˜hÓ'€GØ�K‰K˜	Ó"€EÙœ
 5¬#Ô.Ü˜KÓ(Ð(Ü�W—[‘[ ¨rÓ2Ó3×9Ñ9Ó;€JÙ�j HÒ,ÜÐ4°Z°NÐCÓDÐDØ�'ˆ>Ðøô ×Ñò GÜ˜x˜j¨°s°eÐ<Ó=À3ÐFûðGús   ‚)D ÄD?Ä)D:Ä:D?r#   c                óP   — | j                  d«      }t        |t        «      r|r|S |S )zŒThe token response's refresh token, or ``fallback`` when absent/non-string
    (the session then behaves as access-token-only until expiry).Úrefresh_token)r&   r   r(   )r—   ÚfallbackÚrts      r    Úrefresh_token_fromrž   ¡   s(   € ð 
�‰�_Ó	%€BÜ˜B¤Ô$©ˆ2Ð9°Ð9r,   r˜   )ÚlabelÚemailÚdisplay_nameÚorg_idc               ó–   — t        |j                  dd«      «      }|st        |› d�«      ‚t        ||||| t	        |d   «      ||¬«      S )z=Map verified JWT claims onto a Session; ``sub`` is mandatory.Úsubr#   z missing 'sub' (user_id) claimÚexp)Úuser_idr    r¡   r¢   rH   Ú
expires_atÚaccess_tokenr›   )r(   r&   r   r   Úint)	rH   Úclaimsr¨   r›   rŸ   r    r¡   r¢   r¦   s	            r    Úsession_from_claimsr«   ¨   s[   € ô �&—*‘*˜U BÓ'Ó(€GÙÜ˜u˜gÐ%CÐDÓEÐEÜØ˜u°<ÈÐYaÜ�v˜e‘}Ó%°LÐP]ô_ð _r,   c                ó4   — ddl m}  || dt        dddœ¬«      S )z‡PyJWKClient with explicit Accept/User-Agent (some WAFs block the library default).
    Imported lazily so plugin discovery stays cheap.r   )ÚPyJWKClientTr   zHermesAgent/1.0)r   z
User-Agent)Ú
cache_keysÚlifespanrz   )Újwtr­   ÚJWKS_CACHE_SECONDS)Újwks_urlr­   s     r    Úmake_jwks_clientr³   ¶   s'   € õ  áØ˜TÔ,>Ø-Ð=NÑOôQð Qr,   c          
     óú  — ddl }	 |j                  | «      }	 |j	                  | |j
                  |||dg d¢i¬«      S # t        $ r}t        |«      |‚d}~ww xY w# |j                  $ r}t        |› d|› �«      |‚d}~w|j                  $ rn}d}		 |j	                  | ddd	œ¬
«      }
d|
j                  d«      ›d|
j                  d«      ›d|›d|›d�	}	n# t        $ r Y nw xY wt        |› d|› |	› �«      |‚d}~ww xY w)uï  Verify ``token`` against ``jwks_client`` with pinned ``aud``/``iss``.

    Unreachable JWKS â†’ ``ProviderError`` (503); a bearer that is not one of our JWTs
    (opaque peer key, foreign kid) â†’ ``InvalidCodeError`` (None / next provider); folding
    both into 503 broke peer-key bearers. Expiry raises ``InvalidCodeError`` (verify_session
    maps it to None); any other claim failure raises ``ProviderError`` with the unverified
    iss/aud appended so operators can spot config drift.
    r   NÚrequire)r¥   ÚiatÚaudÚissr¤   )Ú
algorithmsÚaudienceÚissuerÚoptionsz
 expired: r#   F)Úverify_signatureÚ
verify_exp)r¼   z [token iss=r¸   z aud=r·   z; expected iss=ú]z verification failed: )r°   Úget_signing_key_from_jwtr   r   rP   ÚkeyÚExpiredSignatureErrorr	   ÚInvalidTokenErrorr&   r   )r˜   Újwks_clientr¹   rº   r»   rŸ   r°   Úsigning_keyr   ÚdetailsÚ
unverifieds              r    Ú
verify_jwtrÈ   À   sI  € ó ð7Ø!×:Ñ:¸5ÓAˆðUØ�z‰zØ�;—?‘?¨zÀHÐU[ØÒ CÐDð ó Fð 	Føô ò 7ô
 )¨Ó-°3Ð6ûð7ûð ×$Ñ$ò CÜ % ¨
°3°%Ð8Ó9¸sÐBûØ× Ñ ò Uð ˆð	ØŸ™ EÈÐ]bÑ3c˜ÓdˆJà˜zŸ~™~¨eÓ4Ð7°u¸Z¿^¹^ÈEÓ=RÐ<Uð V Ø &˜z¨¨x¨l¸!ð=ñ øô ò 	Ùð	úä˜u˜gÐ%;¸C¸5ÀÀ	ÐJÓKÐQTÐTûðUús]   †= ˜$A ½	AÁAÁAÁC:Á)A:Á:C:ÂC5ÂACÃC5Ã	CÃC5ÃCÃC5Ã5C:c                  ó>   — e Zd ZU dZdZded<   dZded<   d	d„Zd
d„Zy)ÚNonInteractiveMixinzâOAuth-redirect stubs for providers without a browser login flow (password / service
    credential). ``_NOT_INTERACTIVE`` is the operator-facing reason; ``_NO_START_LOGIN``
    optionally overrides the ``start_login`` message.r#   r(   Ú_NOT_INTERACTIVEÚ_NO_START_LOGINc               óH   — t        | j                  xs | j                  «      ‚r0   )ÚNotImplementedErrorrÌ   rË   )r5   r[   s     r    Ústart_loginzNonInteractiveMixin.start_loginò   s   € Ü! $×"6Ñ"6Ò"O¸$×:OÑ:OÓPÐPr,   c               ó,   — t        | j                  «      ‚r0   )rÎ   rË   ©r5   ra   rg   rv   r[   s        r    Úcomplete_loginz"NonInteractiveMixin.complete_loginõ   s   € Ü! $×"7Ñ"7Ó8Ð8r,   N)r[   r(   r7   r
   ©
ra   r(   rg   r(   rv   r(   r[   r(   r7   r   )	r9   r:   r;   r<   rË   Ú__annotations__rÌ   rÏ   rÒ   r?   r,   r    rÊ   rÊ   ê   s)   … ñ9ð Ð�cÓØ€O�SÓóQô9r,   rÊ   c                  óN   — e Zd ZU dZdZded<   dZded<   dd„Zdd	„Zdd
„Z	dd„Z
y)ÚJwtOAuthProvidera2  Authorization-code + PKCE provider whose session token is a JWT we verify ourselves
    (nous: Portal access token; self-hosted: OIDC ID token). Subclasses set ``_client_id`` and
    implement: ``_jwks_uri() -> str``; ``_claims_for(token) -> claims`` (raises
    ``InvalidCodeError`` on expiry/foreign token, ``ProviderError`` otherwise);
    ``_grant(data, *, bad_request_exc, headers=None, previous_refresh_token="") -> Session``;
    ``_refresh_request(refresh_token) -> (form_data, extra_headers)``;
    ``_session(token, refresh_token, claims) -> Session``.Nr   Ú_jwks_clientr#   r(   Ú
_client_idc                ón   — | j                   €t        | j                  «       «      | _         | j                   S r0   )r×   r³   Ú	_jwks_uri)r5   s    r    Ú_get_jwks_clientz!JwtOAuthProvider._get_jwks_client  s.   € Ø×ÑÐ$Ü 0°·±Ó1AÓ BˆDÔØ× Ñ Ð r,   c               óP   — | j                  d||| j                  |dœt        ¬«      S )NÚauthorization_code)Ú
grant_typera   r[   re   rv   )r‘   )Ú_grantrØ   r	   rÑ   s        r    rÒ   zJwtOAuthProvider.complete_login
  s1   € à�{‰{Ø/¸È|ØŸ/™/¸MñKä,ð ó .ð 	.r,   c               óv   — |st        d«      ‚| j                  |«      \  }}| j                  ||t         |¬«      S )Nz#no refresh token present in session)rz   r‘   Úprevious_refresh_token)r   Ú_refresh_requestrß   )r5   r›   r�   rz   s       r    Úrefresh_sessionz JwtOAuthProvider.refresh_session  sH   € ÙÜ%Ð&KÓLÐLØ×-Ñ-¨mÓ<‰ˆˆgØ�{‰{Ø˜'Ô3FÐ_lð ó nð 	nr,   c               ój   — 	 | j                  |«      }| j                  |d|«      S # t        $ r Y y w xY w)Nr#   )Ú_claims_forr	   Ú_session)r5   r¨   rª   s      r    Úverify_sessionzJwtOAuthProvider.verify_session  s@   € ð	Ø×%Ñ% lÓ3ˆFð �}‰}˜\¨2¨vÓ6Ð6øô  ò 	Ùð	ús   ‚& ¦	2±2)r7   r   rÓ   )r›   r(   r7   r   )r¨   r(   r7   zOptional[Session])r9   r:   r;   r<   r×   rÔ   rØ   rÛ   rÒ   rã   rç   r?   r,   r    rÖ   rÖ   ù   s2   … ñ>ð €L�#ÓØ€J�Óó!ó
.ónô7r,   rÖ   )r   úlogging.Loggerr   r(   r   r(   r7   r   )r)   r(   r*   r   r7   r(   )
r   rè   r   r(   rE   ÚtyperF   zCallable[[], dict]r7   ztuple[Optional[dict], str])rQ   Úbytesr7   r(   )r[   r(   r7   r8   )
ru   r(   re   r(   rf   r(   r[   r(   r7   r
   )r}   zhttpx.Responser7   úDict[str, Any])r�   r(   r�   zDict[str, str]rz   zOptional[Dict[str, str]]r‘   ztype[Exception]r’   r(   r“   r(   r”   r(   r•   r(   r7   ztuple[str, Dict[str, Any]])r#   )r—   rë   rœ   r(   r7   r(   )rH   r(   rª   rë   r¨   r(   r›   r(   rŸ   r(   r    r(   r¡   r(   r¢   r(   r7   r   )r²   r(   r7   r   )r˜   r(   rÄ   r   r¹   z	list[str]rº   r(   r»   r(   rŸ   r(   r7   rë   )+r<   Ú
__future__r   rM   rp   Úloggingr$   rn   Úurllib.parserV   Útypingr   r   r   r   rˆ   Úhermes_cli.dashboard_authr   r	   r
   r   r   r   r   r±   r‹   rŠ   r!   r+   r   r.   rJ   rR   r]   rx   r   r™   rž   r«   r³   rÈ   rÊ   rÖ   r?   r,   r    ú<module>rñ      sË  ðñõ #ã Û Û Û 	Û Û ß 0Ó 0ã ÷ ÷  ñ  ð
 Ð Ø!Ð ØÐ,Ð-€ó
8óPô0�yô 0ðØðØ&)ðØ9=ðØI[ðàóó2?ó
gó[ó 2ð LPñØ	ðØ"ðØ0HðØbqðà	ðàðà(+ðà:=ðàB\óô<:ð  r¸rÐQSñ	_Øð	_Ø)ð	_Ø<?ð	_ØPSð	_àð	_à!$ð	_à9<ð	_àKNð	_àX_ó	_óQð%UØð%UØ ð%UØ1:ð%UØFIð%UØSVð%UØ_bð%UØguó%U÷T9ñ 9ô&7Ð,õ &7r,   