
    (Gj0                       d Z ddlmZ ddlmZmZ ddlmZ ddlm	Z	  ed           G d d	                      Z
 ed           G d
 d                      Z ed           G d d                      Z G d de          Z G d de          Z G d de          Z G d de          Z G d de          ZddZdS )zFAbstract base + dataclasses + exceptions for dashboard auth providers.    )annotations)ABCabstractmethod)	dataclass)OptionalT)frozenc                  d    e Zd ZU dZded<   ded<   ded<   ded<   ded<   ded	<   ded
<   ded<   dS )Sessionu   A verified identity. Returned by ``complete_login`` and ``verify_session``.

    All fields are mandatory. Providers that don't have a concept of orgs
    should set ``org_id`` to an empty string. ``access_token`` and
    ``refresh_token`` are opaque to Hermes — provider-specific.
    struser_idemaildisplay_nameorg_idproviderint
expires_ataccess_tokenrefresh_tokenN__name__
__module____qualname____doc____annotations__     D/home/rurouni/.hermes/hermes-agent/hermes_cli/dashboard_auth/base.pyr
   r
   	   so           LLLJJJKKKMMMOOOr   r
   c                  6    e Zd ZU dZded<   ded<   dZded<   dS )	TokenPrincipalu4  A verified non-interactive (service-to-service) caller.

    The token analog of :class:`Session`. Where a ``Session`` represents an
    interactive human identity behind a session cookie, a ``TokenPrincipal``
    represents a machine/service caller that authenticated by presenting a
    bearer token in the ``Authorization`` request header on a single
    request — no login, no cookie, no refresh.

    Returned by :meth:`DashboardAuthProvider.verify_token` and attached to
    ``request.state.token_principal`` by the token-auth middleware seam so a
    route handler can see *who* called it.

    Fields:
      * ``principal`` — stable identifier for the caller (e.g. the provider
        name, a service account id, or an agent id). Opaque to the seam.
      * ``provider`` — the ``name`` of the provider that verified the token.
      * ``scopes`` — capability strings this principal is authorised for.
        Empty tuple means "unscoped" (the provider vouches for the caller but
        attaches no capability list); a route MAY enforce a required scope.
    r   	principalr   r   ztuple[str, ...]scopesN)r   r   r   r   r   r!   r   r   r   r   r      sA          * NNNMMM F      r   r   c                  (    e Zd ZU dZded<   ded<   dS )
LoginStartu  First leg of the OAuth round trip.

    ``redirect_url`` is the URL the browser must navigate to (e.g. the
    Portal's ``/oauth/authorize``). ``cookie_payload`` is a dict of cookie
    name → serialised value that the auth route will ``Set-Cookie`` on the
    response. Used for PKCE state, CSRF nonces, etc. Cookies set here MUST
    be HttpOnly + Secure (when over HTTPS) + SameSite=Lax with a TTL ≤ 10
    minutes (the login lifetime).
    r   redirect_urlzdict[str, str]cookie_payloadNr   r   r   r   r#   r#   8   s6           """"""r   r#   c                      e Zd ZdZdS )ProviderErrorzmIDP unreachable, network error, or other transient failure.

    Middleware translates this to HTTP 503.
    Nr   r   r   r   r   r   r   r'   r'   H              r   r'   c                      e Zd ZdZdS )InvalidCodeErrorzlThe OAuth callback ``code`` / ``state`` failed validation.

    Middleware translates this to HTTP 400.
    Nr(   r   r   r   r+   r+   O   r)   r   r+   c                      e Zd ZdZdS )InvalidCredentialsErroraf  A username/password pair was rejected by a password provider.

    Raised by :meth:`DashboardAuthProvider.complete_password_login`. The
    ``/auth/password-login`` route translates this to HTTP 401 with a
    deliberately generic detail (never distinguishing "unknown user" from
    "wrong password") so the endpoint can't be used as a username oracle.
    Nr(   r   r   r   r-   r-   V   s           r   r-   c                      e Zd ZdZdS )RefreshExpiredErroruh   The refresh token is dead.

    Middleware clears cookies and forces re-login (302 → ``/login``).
    Nr(   r   r   r   r/   r/   `   r)   r   r/   c                      e Zd ZU dZdZded<   dZded<   dZded<   dZded	<   d
Z	ded<   e
d$d            Ze
d%d            Ze
d&d            Ze
d'd            Ze
d(d            Zd)dZd*d"Zd#S )+DashboardAuthProvideru
  Protocol every dashboard-auth provider plugin implements.

    Lifecycle:
      1. ``start_login`` — user clicks "Log in with X" on the login page.
         Provider returns a redirect URL and any PKCE/CSRF state to stash
         in short-lived cookies.
      2. Browser bounces through the OAuth IDP and lands at /auth/callback.
      3. ``complete_login`` — exchange the code + verifier for a Session.
      4. ``verify_session`` — called on every request to validate the
         access token in the cookie. Returns ``None`` if the token is
         expired or invalid (middleware then triggers refresh or logout).
      5. ``refresh_session`` — called when the access token is near expiry.
         Returns a new Session with rotated tokens.
      6. ``revoke_session`` — called on /auth/logout. Best-effort.

    Failure semantics:
      * ``start_login`` may raise ``ProviderError`` if the IDP is
        unreachable.
      * ``complete_login`` raises ``InvalidCodeError`` on bad code/state;
        ``ProviderError`` if the IDP is unreachable.
      * ``verify_session`` returns ``None`` on expiry / unknown token;
        raises ``ProviderError`` if the IDP is unreachable. Middleware
        treats expiry and unreachable differently (expiry → refresh;
        unreachable → 503).
      * ``refresh_session`` raises ``RefreshExpiredError`` when the
        refresh token is also invalid; middleware then forces re-login.
        Raises ``ProviderError`` on network failure.
      * ``revoke_session`` is best-effort and must not raise.

    Subclasses MUST set ``name`` (lowercase identifier, stable forever)
    and ``display_name`` (user-facing label on the login page).

    Password (non-redirect) providers:
      A provider that authenticates with a username + password instead of
      an OAuth redirect sets ``supports_password = True`` and implements
      ``complete_password_login``. The login page then renders a
      credential form (POSTing to ``/auth/password-login``) instead of a
      "Log in with X" redirect button. Everything downstream of login —
      ``verify_session`` / ``refresh_session`` / ``revoke_session``, the
      session cookies, the WS-ticket mint — is identical to the OAuth
      path, because a password session is just a :class:`Session` with
      provider-minted opaque tokens. The OAuth methods (``start_login`` /
      ``complete_login``) remain abstract; a pure-password provider that
      will never be reached via the redirect flow may implement them as
      stubs that raise ``NotImplementedError``.
     r   namer   Fboolsupports_passwordsupports_tokenTsupports_sessionredirect_urireturnr#   c                   d S Nr   )selfr8   s     r   start_loginz!DashboardAuthProvider.start_login   s    ?Bsr   codestatecode_verifierr
   c                   d S r;   r   )r<   r>   r?   r@   r8   s        r   complete_loginz$DashboardAuthProvider.complete_login   s	     #r   r   Optional[Session]c                   d S r;   r   )r<   r   s     r   verify_sessionz$DashboardAuthProvider.verify_session   s    ILr   r   c                   d S r;   r   r<   r   s     r   refresh_sessionz%DashboardAuthProvider.refresh_session   s    ADr   Nonec                   d S r;   r   rG   s     r   revoke_sessionz$DashboardAuthProvider.revoke_session   s    =@Sr   usernamepassword	'Session'c               J    t          t          |           j         d          )u$  Verify a username/password pair and mint a :class:`Session`.

        Only called when ``supports_password`` is True (the
        ``/auth/password-login`` route guards on the flag). The default
        raises ``NotImplementedError`` so an OAuth-only provider that
        forgets to set the flag fails loudly rather than silently
        accepting credentials.

        The returned ``Session`` carries provider-minted opaque
        ``access_token`` / ``refresh_token`` exactly like the OAuth path,
        so all downstream session handling (cookies, verify, refresh,
        ws-tickets, logout) is identical.

        Failure semantics:
          * ``InvalidCredentialsError`` — username/password rejected. The
            route surfaces a generic 401 (no user-vs-password
            distinction). Implementations SHOULD spend constant time on
            unknown users (dummy hash verify) to avoid a timing oracle.
          * ``ProviderError`` — the backing credential store is
            unreachable (LDAP/DB down); the route surfaces 503.
        zd does not support password login (set supports_password = True and override complete_password_login)NotImplementedErrortyper   )r<   rL   rM   s      r   complete_password_loginz-DashboardAuthProvider.complete_password_login   s1    0 "Dzz" ' ' '
 
 	
r   token'Optional[TokenPrincipal]'c               J    t          t          |           j         d          )u  Verify a non-interactive bearer token; return its principal.

        The token analog of ``verify_session``. Only consulted when
        ``supports_token`` is True. Called by the ``token_auth`` middleware
        seam for every request to a token-authable route, in registration
        order, until one provider returns a non-None principal.

        Contract (mirrors ``verify_session`` stacking semantics):
          * Return a :class:`TokenPrincipal` if this provider recognises and
            accepts the token.
          * Return ``None`` for a token this provider does NOT recognise —
            never raise, so the seam can fall through to the next provider.
            A malformed/expired/wrong token is "not recognised" → ``None``.
          * Raise ``ProviderError`` ONLY for a genuine backing-store outage
            (the provider can neither confirm nor deny). The seam treats this
            like ``verify_session``: remember it, keep trying other providers,
            and surface 503 only if NO provider accepts the token AND at least
            one was unreachable.

        Implementations MUST use a constant-time comparison
        (``hmac.compare_digest``) when matching a shared secret so the
        endpoint isn't a timing oracle.

        The default raises ``NotImplementedError`` so a provider that sets
        ``supports_token`` but forgets to implement this fails loudly rather
        than silently accepting every caller.
        zR does not support token auth (set supports_token = True and override verify_token)rP   )r<   rT   s     r   verify_tokenz"DashboardAuthProvider.verify_token   s4    8 "Dzz" D D D
 
 	
r   N)r8   r   r9   r#   )
r>   r   r?   r   r@   r   r8   r   r9   r
   )r   r   r9   rC   )r   r   r9   r
   )r   r   r9   rI   )rL   r   rM   r   r9   rN   )rT   r   r9   rU   )r   r   r   r   r3   r   r   r5   r6   r7   r   r=   rB   rE   rH   rK   rS   rW   r   r   r   r1   r1   g   s        - -^ DNNNNL $#### !N     "!!!!BBB ^B   ^ LLL ^LDDD ^D@@@ ^@
 
 
 
<
 
 
 
 
 
r   r1   clsrR   r9   rI   c                `   d}d}|D ].}t          | |d          }|st          | j         d|          /|D ]9}t          t          | |d                    st          | j         d|           :t          | dd          r+t          | j         dt	          | j                             dS )	a+  Raise ``TypeError`` if ``cls`` doesn't fully implement the provider protocol.

    Call this in every provider plugin's unit tests::

        def test_protocol_compliance():
            assert_protocol_compliance(MyProvider)

    Returns ``None`` on success so callers can assert it explicitly.
    )r=   rB   rE   rH   rK   )r3   r   r2   z missing or empty attribute: Nz missing method: __abstractmethods__z% has unimplemented abstract methods: )getattr	TypeErrorr   callablesortedrZ   )rX   required_methodsrequired_attrsattrvalmethods         r   assert_protocol_compliancerd     s    .N  c4$$ 	<FFdFF  	 # H HVT2233 	Hs|FFfFFGGG	H s)400 
| 1 1c-..1 1
 
 	

 
r   N)rX   rR   r9   rI   )r   
__future__r   abcr   r   dataclassesr   typingr   r
   r   r#   	Exceptionr'   r+   r-   r/   r1   rd   r   r   r   <module>rj      s   L L " " " " " " # # # # # # # # ! ! ! ! ! !       $       $ $! ! ! ! ! ! ! !6 $# # # # # # # #    I       y       i       )   a
 a
 a
 a
 a
C a
 a
 a
H!
 !
 !
 !
 !
 !
r   