
    NQjj                        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mZmZ g dZddlmZmZ e G d	 d
                      ZdddZ ed          Z G d dee                   ZdS )u  Shared substrate for external secret-source backends.

Every backend (Bitwarden, 1Password, …) needs the same handful of
security-sensitive primitives:

  * a uniform result object (:class:`FetchResult`),
  * environment-variable name validation (:func:`is_valid_env_name`),
  * a two-layer fetch cache whose disk half writes atomically with ``0600``
    permissions and honours a TTL (:class:`DiskCache`, :class:`CachedFetch`).

These used to live inline inside ``bitwarden.py``.  Pulling them here means
the atomic-write / ``0600`` / TTL logic is audited and fixed in exactly one
place instead of drifting across copy-pasted per-backend modules — each
backend supplies only its own cache-key shape and a serializer for it.

Nothing in this module ever raises out to the caller's hot path: the disk
layer is strictly best-effort (a miss just triggers a refetch), because a
cache problem must never block Hermes startup.
    )annotationsN)	dataclass)Path)CallableDictGenericOptionalTypeVar)FetchResultCachedFetch	DiskCacheis_valid_env_nameresolve_cache_home)r   r   c                  0    e Zd ZU dZded<   ded<   dd	Zd
S )r   z;A set of fetched secret values plus when they were fetched.zDict[str, str]secretsfloat
fetched_atttl_secondsreturnboolc                P    |dk    rdS t          j                     | j        z
  |k     S )Nr   F)timer   )selfr   s     ?/home/alina/.hermes/hermes-agent/agent/secret_sources/_cache.pyis_freshzCachedFetch.is_fresh@   s*    !5	do-<<    N)r   r   r   r   )__name__
__module____qualname____doc____annotations__r    r   r   r   r   9   sH         EE= = = = = =r   r   	home_pathOptional[Path]r   r   c                v    | 6t          t          j        dt          j                    dz                      } | S )a   Resolve the Hermes home used for cache paths.

    ``home_path`` is whatever ``load_hermes_dotenv()`` already resolved;
    falling back to ``$HERMES_HOME`` / ``~/.hermes`` keeps direct callers
    (and tests that don't thread a home through) working.
    NHERMES_HOMEz.hermes)r   osgetenvhome)r#   s    r   r   r   M   s4     =$)++	2IJJKK	r   Kc                  F    e Zd ZdZddZdddZ	 dddZ	 dddZdddZd	S )r   u  Best-effort, profile-aware on-disk cache for fetched secret values.

    One JSON object per backend lives at ``<hermes_home>/cache/<basename>``::

        {"key": "<serialized cache key>", "secrets": {...}, "fetched_at": 1.0}

    The file holds only secret *values* keyed by the serialized cache key —
    never raw auth material.  Backends are responsible for fingerprinting
    tokens/sessions *before* they reach ``key_serializer`` so the token can't
    land in the key.

    Writes are atomic (``mkstemp`` → ``chmod 0600`` → ``os.replace``) and the
    containing ``cache/`` directory is forced to ``0700`` — ``mkdir``'s mode is
    umask-subject, so the chmod is the reliable form.  Both ``read`` and
    ``write`` short-circuit when ``ttl_seconds <= 0``, so setting the TTL to
    zero disables *both* cache layers symmetrically: a user opting out never
    gets secret values written to disk at all.
    basenamestrkey_serializerCallable[[K], str]r   Nonec               p    || _         || _        |                    dd          d         }d| d| _        d S )N.   r   _)	_basename_key_serializersplit_tmp_prefix)r   r,   r.   stems       r   __init__zDiskCache.__init__p   sA    !- ~~c1%%a(&t;;;r   Nr#   r$   r   c                6    t          |          dz  | j        z  S )Ncache)r   r5   r   r#   s     r   pathzDiskCache.pathx   s    !),,w6GGr   keyr*   r   r   Optional[CachedFetch]c                   |dk    rdS |                      |          }	 t          |dd          5 }t          j        |          }ddd           n# 1 swxY w Y   n# t          t          j        f$ r Y dS w xY wt          |t                    sdS |                    d          | 	                    |          k    rdS |                    d          }|                    d          }t          |t                    rt          |t          t          f          sdS d	 |                                D             }	t          |	t          |          
          }
|
                    |          sdS |
S )zReturn a fresh cached entry for ``key``, or None.

        Best-effort: any I/O or parse error, a key mismatch, or a stale entry
        all return None so the caller re-fetches.
        r   Nrutf-8encodingr?   r   r   c                n    i | ]2\  }}t          |t                    t          |t                    /||3S r"   )
isinstancer-   ).0kvs      r   
<dictcomp>z"DiskCache.read.<locals>.<dictcomp>   sQ     !
 !
 !
Q
1c0B0B!
GQRSUXGYGY!
q!
 !
 !
r   )r   r   )r>   openjsonloadOSErrorJSONDecodeErrorrG   dictgetr6   intr   itemsr   r   )r   r?   r   r#   r>   fpayloadr   r   typedentrys              r   readzDiskCache.read{   s    !4yy##	dC'222 'a)A,,' ' ' ' ' ' ' ' ' ' ' ' ' ' '-. 	 	 	44	'4(( 	4;;u!5!5c!:!:::4++i(([[..
'4(( 	
:U|0T0T 	4!
 !
$]]__!
 !
 !
 EeJ6G6GHHH~~k** 	4s4   A AA AA AA A87A8rX   r   c                   |dk    rdS |                      |          }	 |j        }|                    dd           	 t          j        |d           n# t
          $ r Y nw xY w|                     |          |j        |j        d}t          j
        | j        dt          |                    \  }}		 t          j        |d	d
          5 }
t          j        ||
           ddd           n# 1 swxY w Y   t          j        |	d           t          j        |	|           dS # t"          $ r( 	 t          j        |	           n# t
          $ r Y nw xY w w xY w# t
          $ r Y dS w xY w)u   Persist ``entry`` for ``key`` atomically at mode ``0600``.

        No-op when ``ttl_seconds <= 0`` (so caching is genuinely off) or on any
        I/O error — the next invocation just re-fetches.
        r   NT)parentsexist_oki  )r?   r   r   z.tmp)prefixsuffixdirwrC   rD   i  )r>   parentmkdirr'   chmodrO   r6   r   r   tempfilemkstempr8   r-   fdopenrM   dumpreplaceBaseExceptionunlink)r   r?   rX   r   r#   r>   	cache_dirrV   fdtmprU   s              r   writezDiskCache.write   s    !Fyy##	IOOD4O888E****    ++C00 =#. G &'C	NN  GB
Yr3999 *QIgq)))* * * * * * * * * * * * * * *e$$$
3%%%%%    IcNNNN   D  	 	 	DD	s   E A E 
A!E  A!!AE 4D$ C-!D$ -C11D$ 4C15-D$ $
E/EE
EEEEE 
E'&E'c                    	 |                      |                                           dS # t          t          f$ r Y dS w xY w)z6Delete the on-disk cache file if present (idempotent).N)r>   rj   FileNotFoundErrorrO   r=   s     r   clearzDiskCache.clear   sR    	IIi  '')))))!7+ 	 	 	DD	s   '+ A A )r,   r-   r.   r/   r   r0   Nr#   r$   r   r   )r?   r*   r   r   r#   r$   r   r@   )
r?   r*   rX   r   r   r   r#   r$   r   r0   )r#   r$   r   r0   )	r   r   r   r    r:   r>   rY   rn   rq   r"   r   r   r   r   \   s         &' ' ' 'H H H H H %)	# # # # #T %). . . . .`      r   r   rr   rs   )r    
__future__r   rM   r'   rd   r   dataclassesr   pathlibr   typingr   r   r   r	   r
   __all__agent.secret_sources.baser   r   r   r   r*   r   r"   r   r   <module>rz      sl   ( # " " " " "  				   ! ! ! ! ! !       = = = = = = = = = = = = = =          	= 	= 	= 	= 	= 	= 	= 	=&	 	 	 	 	 GCLLy y y y y
 y y y y yr   