+
    Q(ip                       R t ^ RIHt ^ RIt^ RIt^ RIt^ RIHt ^ RIH	t	 ^ RI
Ht ^ RIHt ^ RIHtHtHt ^ RIHt ^ R	IHtHtHtHt ^ R
IHtHtHtHt ^ RIHt ]'       d   ^ RI H!t! ]! ]"4      t# ! R R]4      t$ ! R R]%4      t& ! R R]%4      t']	 ! R R4      4       t(]	 ! R R4      4       t) ! R R4      t* ! R R4      t+ ! R R4      t,R# )a  CIMD (Client ID Metadata Document) support for FastMCP.

.. warning::
    **Beta Feature**: CIMD support is currently in beta. The API may change
    in future releases. Please report any issues you encounter.

CIMD is a simpler alternative to Dynamic Client Registration where clients
host a static JSON document at an HTTPS URL, and that URL becomes their
client_id. See the IETF draft: draft-parecki-oauth-client-id-metadata-document

This module provides:
- CIMDDocument: Pydantic model for CIMD document validation
- CIMDFetcher: Fetch and validate CIMD documents with SSRF protection
- CIMDClientManager: Manages CIMD client operations
)annotationsN)Mapping)	dataclass)timezone)parsedate_to_datetime)TYPE_CHECKINGAnyLiteral)urlparse)
AnyHttpUrl	BaseModelFieldfield_validator)	SSRFErrorSSRFFetchErrorssrf_safe_fetch_responsevalidate_url)
get_loggerJWTVerifierc                  X   ] tR t^-t$ Rt]! RRR7      tR]R&   ]! RRR	7      tR
]R&   ]! RRR	7      t	R]R&   ]! RRR	7      t
R]R&   ]! RRR7      tR]R&   ]! RRR	7      tR]R&   ]! R RR7      tR]R&   ]! R RR7      tR]R&   ]! RRR	7      tR
]R &   ]! RR!R	7      tR"]R#&   ]! RR$R	7      tR]R%&   ]! RR&R	7      tR]R'&   ]! RR(R	7      tR]R)&   ]! RR*R	7      tR+]R,&   ]! RR-R	7      tR
]R.&   ]! RR/R	7      tR
]R0&   ]! R4      ]R1 R2 l4       4       t]! R4      ]R3 R4 l4       4       tR5tR# )6CIMDDocumenta  CIMD document per draft-parecki-oauth-client-id-metadata-document.

The client metadata document is a JSON document containing OAuth client
metadata. The client_id property MUST match the URL where this document
is hosted.

Key constraint: token_endpoint_auth_method MUST NOT use shared secrets
(client_secret_post, client_secret_basic, client_secret_jwt).

redirect_uris is required and must contain at least one entry.
.z0Must match the URL where this document is hosted)descriptionr   	client_idNz!Human-readable name of the client)defaultr   
str | Noneclient_namezURL of the client's home pagezAnyHttpUrl | None
client_urizURL of the client's logo imagelogo_urizWArray of allowed redirect URIs (may include wildcards like http://localhost:*/callback)	list[str]redirect_urisnonezDAuthentication method for token endpoint (no shared secrets allowed)z"Literal['none', 'private_key_jwt']token_endpoint_auth_methodc                 	    R .# )authorization_code r%       ^/Users/agent/.openclaw/workspace/venv/lib/python3.14/site-packages/fastmcp/server/auth/cimd.py<lambda>CIMDDocument.<lambda>S   s    !5 6r&   z%OAuth grant types the client will use)default_factoryr   grant_typesc                 	    R .# )coder%   r%   r&   r'   r(   r)   W   s    r&   z(OAuth response types the client will useresponse_typesz5Space-separated list of scopes the client may requestscopez,Contact information for the client developerlist[str] | Nonecontactsz$URL of the client's terms of servicetos_uriz"URL of the client's privacy policy
policy_uriz:URL of the client's JSON Web Key Set (for private_key_jwt)jwks_uriz/Client's JSON Web Key Set (for private_key_jwt)zdict[str, Any] | Nonejwksz)Unique identifier for the client softwaresoftware_idzVersion of the client softwaresoftware_versionc                    V ^8  d   QhRRRR/# )   vstrreturnr%   )formats   "r'   __annotate__CIMDDocument.__annotate__}   s      S S r&   c                8    0 RmpW9   d   \        RV R24      hV# )z.Ensure no shared-secret auth methods are used.z6CIMD documents cannot use shared-secret auth methods: z*. Use 'none' or 'private_key_jwt' instead.>   client_secret_jwtclient_secret_postclient_secret_basic)
ValueError)clsr:   	forbiddens   && r'   validate_auth_method!CIMDDocument.validate_auth_method{   s5     W	>H L; ;  r&   c                    V ^8  d   QhRRRR/# )r9   r:   r   r<   r%   )r=   s   "r'   r>   r?      s      y Y r&   c                \   V'       g   \        R4      hV F  pV'       d   VP                  4       '       g   \        R4      h\        V4      pVP                  '       g   \        RV: 24      hVP                  '       d   Kl  VP                  R4      '       d   K  \        RV: 24      h	  V# )z@Ensure redirect_uris is non-empty and each entry is a valid URI.z5CIMD documents must include at least one redirect_uriz,CIMD redirect_uris must be non-empty stringszACIMD redirect_uri must have a scheme (e.g. http:// or https://): zurn:z$CIMD redirect_uri must have a host: )rD   stripr
   schemenetloc
startswith)rE   r:   uriparseds   &&  r'   validate_redirect_uris#CIMDDocument.validate_redirect_uris   s     TUUCciikk !OPPc]F=== WX[W^_  ===)?)? #Gw!OPP  r&   r%   )__name__
__module____qualname____firstlineno____doc__r   r   __annotations__r   r   r   r    r"   r+   r.   r/   r1   r2   r3   r4   r5   r6   r7   r   classmethodrG   rQ   __static_attributes__r%   r&   r'   r   r   -   s   
 "FIz  $7K  %*3%J!  #(4#H   %m M9  FKZF B  #6;K  !&(>!NI  KE:  "'B"H  "':"G  %*8%J!  #(P#H  #(E#D
  $?K  $)4$j 
 12  3 _%  &r&   r   c                      ] tR t^tRtRtR# )CIMDValidationErrorz+Raised when CIMD document validation fails.r%   NrS   rT   rU   rV   rW   rZ   r%   r&   r'   r\   r\      s    5r&   r\   c                      ] tR t^tRtRtR# )CIMDFetchErrorz)Raised when CIMD document fetching fails.r%   Nr]   r%   r&   r'   r_   r_      s    3r&   r_   c                  X    ] tR t^t$ RtR]R&   R]R&   R]R&   R]R&   R]R	&   R
]R&   RtR# )_CIMDCacheEntryz8Cached CIMD document and associated HTTP cache metadata.r   docr   etaglast_modifiedfloat
expires_atfreshness_lifetimeboolmust_revalidater%   NrS   rT   rU   rV   rW   rX   rZ   r%   r&   r'   ra   ra      s)    B	
r&   ra   c                  X    ] tR t^t$ RtR]R&   R]R&   R]R&   R]R&   R]R	&   R]R
&   RtR# )_CIMDCachePolicyz>Normalized cache directives parsed from HTTP response headers.r   rc   rd   re   rf   rg   rh   no_storeri   r%   Nrj   r%   r&   r'   rl   rl      s(    H
Nr&   rl   c                  n    ] tR t^tRtRtRtRR R lltR R ltR R	 lt	R
 R lt
R R ltR R ltRtR# )CIMDFetchera_  Fetch and validate CIMD documents with SSRF protection.

Delegates HTTP fetching to ssrf_safe_fetch_response, which provides DNS
pinning, IP validation, size limits, and timeout enforcement. Documents are
cached using HTTP caching semantics (Cache-Control/ETag/Last-Modified), with
a TTL fallback when response headers do not define caching behavior.
i   i  c                   V ^8  d   QhRR/# )r9   timeoutre   r%   )r=   s   "r'   r>   CIMDFetcher.__annotate__   s     
5 
5
5r&   c                     Wn         / V n        R# )z`Initialize the CIMD fetcher.

Args:
    timeout: HTTP request timeout in seconds (default 10.0)
N)rq   _cache)selfrq   s   &&r'   __init__CIMDFetcher.__init__   s     24r&   c               $    V ^8  d   QhRRRRRR/# )r9   headersMapping[str, str]nowre   r<   rl   r%   )r=   s   "r'   r>   rr      s$     3
 3
(3
/43
	3
r&   c           	     >   VP                  4        UUu/ uF  w  r4VP                  4       VbK  	  pppVP                  RR4      pVP                  R4       Uu0 uF8  qwP	                  4       '       g   K  VP	                  4       P                  4       kK:  	  ppRV9   p	RV9   p
RpV FQ  pVP                  R4      '       g   K  VP                  R4      P	                  4       p \        ^ \        V4      4      p M	  RpVe
   W+,           pMXR	V9   dR    \        VR	,          4      pVP                  f!   VP                  \        P                   R
7      pVP#                  4       pVf   W P&                  ,           p\        RW,
          4      p\)        VP                  R4      VP                  R4      VVV	V
R7      # u uppi u upi   \         d    \        P                  RT4        Li ; i  \$        \        3 d!    \        P                  RTR	,          4        Li ; i)z3Parse HTTP cache headers and derive cache behavior.cache-control ,zno-storezno-cacheNzmax-age=z0Ignoring invalid Cache-Control max-age value: %sexpires)tzinfoz4Ignoring invalid Expires header on CIMD response: %sg        rc   zlast-modifiedrc   rd   rf   rg   rm   ri   )itemslowergetsplitrK   rN   removeprefixmaxintrD   loggerdebugr   r   replacer   utc	timestamp	TypeErrorDEFAULT_CACHE_TTL_SECONDSrl   )ru   ry   r{   kr:   
normalizedcache_controlpart
directivesrm   ri   max_age	directivevaluerf   dtrg   s   &&&              r'   _parse_cache_policyCIMDFetcher._parse_cache_policy   s    07}}?tqaggil
?";-:-@-@-E
-ET DJJL -E 	 
 +$
2"#I##J//!..z:@@B!!SZ0G
  $ $(
J*$	*:i+@A99$8<<8B\\^
 ===J j&67'$..9!1+
 	
Q @
 " LLJE z* Jy)s6   F9F?4"F?"GAG+ !G('G(+.HHc                    V ^8  d   QhRRRR/# )r9   ry   rz   r<   rh   r%   )r=   s   "r'   r>   rr   	  s     H H.? HD Hr&   c                l    V Uu0 uF  q"P                  4       kK  	  ppRV9   ;'       g    RV9   # u upi )z>Return True when response includes cache freshness directives.r}   r   )r   )ru   ry   r   r   s   &&  r'   _has_freshness_headers"CIMDFetcher._has_freshness_headers	  s7    )01Aggi
1*,GG	Z0GG 2s   1c                    V ^8  d   QhRRRR/# r9   r   r;   r<   rh   r%   )r=   s   "r'   r>   rr     s      3 4 r&   c                    V'       g   R#  \        V4      pVP                  R8H  ;'       d-    \        VP                  4      ;'       d    VP                  R9  #   \
        \        3 d     R# i ; i)zdCheck if a client_id looks like a CIMD URL.

CIMD URLs must be HTTPS with a host and non-root path.
Fhttps)r~   /)r
   rL   rh   rM   pathrD   AttributeError)ru   r   rP   s   && r'   is_cimd_client_idCIMDFetcher.is_cimd_client_id  so    
 	i(F( 1 1'1 1KKy0
 N+ 		s    A A 
A A/.A/c                    V ^8  d   QhRRRR/# )r9   client_id_urlr;   r<   r   r%   )r=   s   "r'   r>   rr     s     E E E Er&   c           
     n	  "   V P                   P                  V4      p\        P                  ! 4       pRp^0pVe   VP                  '       g   W2P                  8  d   VP
                  # / pVP                  '       d   VP                  VR&   VP                  '       d   VP                  VR&   V'       d   ^R0p \        VRV P                  V P                  RVVR7      G Rj  xL
 pTP                   R8X  Ed`   Tf   \        R4      h\        P                  ! 4       pT P#                  TP$                  4      '       d   T P'                  TP$                  T4      pM5\)        RRY2P*                  ,           TP*                  R	TP                  R
7      pTP,                  '       g   \/        TP
                  TP                  ;'       g    TP                  TP                  ;'       g    TP                  TP                  TP*                  TP                  R7      T P                   T&   TP
                  # T P                   P1                  TR4       TP
                  # \        P                  ! 4       pT P'                  TP$                  T4      p \2        P4                  ! TP6                  4      p	 \:        P=                  T	4      p
\        T
P@                  4      PC                  R4      TPC                  R4      8w  d   \        RT
P@                   RT R24      hT
PD                  '       d*   \        T
PD                  4      p \G        T4      G Rj  xL
  \H        PK                  RTT
PL                  4       TP,                  '       gS   \/        T
TP                  TP                  TP                  TP*                  TP                  R7      T P                   T&   T
# T P                   P1                  TR4       T
#  EL  \         d   p\        \        T4      4      ThRp?i\         d   p\        \        T4      4      ThRp?ii ; i  \2        P8                   d   p\        RT 24      ThRp?ii ; i  \>         d   p\        RT 24      ThRp?ii ; i ELD  \         d   p\        RT 24      ThRp?ii ; i5i)a<  Fetch and validate a CIMD document with SSRF protection.

Uses ssrf_safe_fetch_response for the HTTP layer, which provides:
- HTTPS only, DNS resolution with IP validation
- DNS pinning (connects to validated IP directly)
- Blocks private/loopback/link-local/multicast IPs
- Response size limit and timeout enforcement
- Redirects disabled

Args:
    client_id_url: The URL to fetch (also the expected client_id)

Returns:
    Validated CIMDDocument

Raises:
    CIMDValidationError: If document is invalid or URL blocked
    CIMDFetchError: If document cannot be fetched
NzIf-None-MatchzIf-Modified-Sincei0  Tg      >@)require_pathmax_sizerq   overall_timeoutrequest_headersallowed_status_codesz=CIMD server returned 304 Not Modified without cached documentFr   )rb   rc   rd   rf   rg   ri   z!CIMD document is not valid JSON: zInvalid CIMD document: r   z(CIMD client_id mismatch: document says 'z' but was fetched from ''z&CIMD jwks_uri failed SSRF validation: z8CIMD document fetched and validated: %s (client_name=%s))'rt   r   timeri   rf   rb   rc   rd   r   MAX_RESPONSE_SIZErq   r   r\   r;   r   r_   status_coder   ry   r   rl   rg   rm   ra   popjsonloadscontentJSONDecodeErrorr   model_validate	Exceptionr   rstripr4   r   r   infor   )ru   r   cachedr{   r   r   responseepolicydatarb   jwks_uri_strs   &&          r'   fetchCIMDFetcher.fetch  s    ( /iik15 #u)))c4E4E.Ezz! O{{{39;;0###7=7K7K 34(+Sz$	05!// $ /%9 H 3&~$S  ))+C**8+;+;<<11(2B2BCH *"&"%>%>>'-'@'@"$*$:$: ???-<

33"("6"6"N"N&:N:N%00'-'@'@$*$:$:.M* :: t4::iik))(*:*:C@	V::h../D	L--d3C s}}$$S)]-A-A#-FF%:3==/ J))6q:  <<<s||,L"<000 	FOO	
 )8[[$22!,,#)#<#< & 6 6*DKK& 
 KKOOM40
  	5%c!f-14 	0 Q(a/	0T ## 	V%(I!&MNTUU	V
  	L%(?s&CD!K	L 1 )<QC@s  AR5/R5> R5R56R5;)O7 $O4%O7 )AR5 A#R5$"R5R5&B%R5 Q -Q. A%R5(R5>R RR 2R5A0R54O7 7P?PP?$P?%P::P??R5Q+Q&&Q++R5.R9RRR5R R2R--R22R5c               $    V ^8  d   QhRRRRRR/# )r9   rb   r   redirect_urir;   r<   rh   r%   )r=   s   "r'   r>   rr     s!       S T r&   c                    VP                   '       g   R# VP                  R4      pVP                    FE  pVP                  R4      pW$8X  d    R# RV9   g   K&  \        P                  ! W$4      '       g   KD   R# 	  R# )zValidate that a redirect_uri is allowed by the CIMD document.

Args:
    doc: The CIMD document
    redirect_uri: The redirect URI to validate

Returns:
    True if valid, False otherwise
Fr   T*)r    r   fnmatch)ru   rb   r   allowedallowed_strs   &&&  r'   validate_redirect_uri!CIMDFetcher.validate_redirect_uri  sm         $**3/((G!..-K* k!??<== ) r&   )rt   rq   N)g      $@)rS   rT   rU   rV   rW   r   r   rv   r   r   r   r   r   rZ   r%   r&   r'   ro   ro      s<      $
53
jH
"EN r&   ro   c                  T    ] tR tRtRtRtR tR R ltR R ltR	 R
 lt	R R lt
RtR# )CIMDAssertionValidatori  a  Validates JWT assertions for private_key_jwt CIMD clients.

Implements RFC 7523 (JSON Web Token (JWT) Profile for OAuth 2.0 Client
Authentication and Authorization Grants) for CIMD client authentication.

JTI replay protection uses TTL-based caching to ensure proper security:
- JTIs are cached with expiration matching the JWT's exp claim
- Expired JTIs are automatically cleaned up
- Maximum assertion lifetime is enforced (5 minutes)
i,  c                	    / V n         R V n        \        P                  ! 4       V n        ^<V n        / V n        ^dV n        \        \        4      V n
        R# )i'  N)
_jti_cache_jti_cache_max_sizer   	monotonic_last_cleanup_cleanup_interval_verifier_cache_verifier_cache_max_sizer   rS   r   )ru   s   &r'   rv   CIMDAssertionValidator.__init__  sF    ,.#( !^^-!# 8:(+% *r&   c                   V ^8  d   QhRR/# r9   r<   Noner%   )r=   s   "r'   r>   #CIMDAssertionValidator.__annotate__  s     U Ut Ur&   c                0   \         P                   ! 4       pV P                  P                  4        UUu. uF  w  r#W18  g   K  VNK  	  pppV F  pV P                  V K  	  V'       d(   V P                  P	                  R\        V4      4       R# R# u uppi )zRemove expired JTIs from cache.z%Cleaned up %d expired JTIs from cacheN)r   r   r   r   r   len)ru   r{   jtiexpexpireds   &    r'   _cleanup_expired_jtis,CIMDAssertionValidator._cleanup_expired_jtis  su    iik'+'<'<'>L'>83#)33'>LC$ KKEs7|T  Ms   
BBc                   V ^8  d   QhRR/# r   r%   )r=   s   "r'   r>   r     s     % % %r&   c                    \         P                  ! 4       pWP                  ,
          V P                  8  d   V P	                  4        Wn        R# R# )z>Periodically cleanup expired JTIs to prevent unbounded growth.N)r   r   r   r   r   )ru   r{   s   & r'   _maybe_cleanup%CIMDAssertionValidator._maybe_cleanup  s<    nn###d&<&<<&&(!$ =r&   c          
     ,    V ^8  d   QhRRRRRRRRRR/# )	r9   	assertionr;   r   token_endpointcimd_docr   r<   rh   r%   )r=   s   "r'   r>   r     sC     | || | 	|
 | 
|r&   c                  "   ^ RI Hp V P                  4        VP                  '       d   \	        VP                  4      pV RV RV 2pV P
                  P                  V4      pVfj   V! VVVRR7      p\        V P
                  4      V P                  8  d,   \        \        V P
                  4      4      p	V P
                  V	 WP
                  V&   MDVP                  '       d(   V P                  WP                  4      p
V! V
VVR7      pM\        R4      hVP                  V4      G Rj  xL
 pV'       g   \        R4      hVP                  p\         P                   ! 4       pVP                  R	4      pVP                  R
4      pV'       g   \        R4      hW^,
          8  d   \        R4      hV'       dS   W^,           8  d   \        R4      hW,
          V P"                  8  d#   \        RW,
           RV P"                   R24      hM0WV P"                  ,           8  d   \        RV P"                   R24      hVP                  R4      V8w  d   \        RV 24      hVP                  R4      pV'       g   \        R4      hVV P$                  9   d7   V P$                  V,          pVV8  d   \        RV R24      hV P$                  V WP$                  V&   \        V P$                  4      V P&                  8  df   V P)                  4        \        V P$                  4      V P&                  8  d2   V P*                  P-                  RV P&                  4       \        R4      hV P*                  P/                  RV4       R#  ELT5i)a]  Validate JWT assertion from client.

Args:
    assertion: The JWT assertion string
    client_id: Expected client_id (must match iss and sub claims)
    token_endpoint: Token endpoint URL (must match aud claim)
    cimd_doc: CIMD document containing JWKS for key verification

Returns:
    True if valid

Raises:
    ValueError: If validation fails
r   |NT)r4   issueraudience	ssrf_safe)
public_keyr   r   z<CIMD document must have jwks_uri or jwks for private_key_jwtzInvalid JWT assertionr   iatz Assertion must include exp claimzAssertion has expiredzAssertion iat is in the futurezAssertion lifetime too long: zs (max zs)z%Assertion exp too far in future (max subzAssertion sub claim must be r   z Assertion must include jti claimzAssertion replay detected: jti z already usedz/JTI cache at max capacity (%d), possible attackzServer overloaded, please retryz2JWT assertion validated successfully for client %s)!fastmcp.server.auth.providers.jwtr   r   r4   r;   r   r   r   r   nextiterr5   _extract_public_key_from_jwksrD   load_access_tokenclaimsr   MAX_ASSERTION_LIFETIMEr   r   r   r   warningr   )ru   r   r   r   r   _JWTVerifierr   	cache_keyverifier
oldest_keyr   access_tokenr   r{   r   r   r   
cached_exps   &&&&&             r'   validate_assertion)CIMDAssertionValidator.validate_assertion  s<    * 	R 	 x001L'.)An5EFI++//	:H')$+"	 t++,0M0MM!%d4+?+?&@!AJ,,Z82:$$Y/]]];;I}}UJ#% 'H N 
 &77	BB455$$ iikjjjj?@@ r>455 2X~ !ABBy4666 3CI;gdFaFaEbbde  7 46666 ;D<W<W;XXZ[ 
 ::e	);I;GHH jj?@@ $//!-JC #B3%}!UVV$  # t$":"::&&(4??#d&>&>>##E,, !!BCC@)	
 I Cs4   C"N%AN+M?,N8AN*N9B>N8DNc               $    V ^8  d   QhRRRRRR/# )r9   tokenr;   r5   dictr<   r%   )r=   s   "r'   r>   r   m  s&     5J 5J3 5Jd 5Js 5Jr&   c                ,   ^ RI p^ RIp^ RIHp  VP	                  R4      ^ ,          pVR^\        V4      ^,          ,
          ,          ,          pVP                  ! VP                  V4      4      pVP                  R4      pTP                  R. 4      p
T
'       g   \        R4      hRpT
 F'  pT'       g   K  TP                  R4      T8X  g   K%  Tp M	  T'       gE   \        T
4      ^8X  d&   T
^ ,          pT P                  P                  R	4       M\        R
T R24      h TP                  T4      pTP                  4       P                  R4      #   \         d   p	\        RT	 24      T	hRp	?	ii ; i  \         d   p	\        RT	 24      T	hRp	?	ii ; i)zExtract public key from inline JWKS.

Args:
    token: JWT token to extract kid from
    jwks: JWKS document containing keys

Returns:
    PEM-encoded public key

Raises:
    ValueError: If key cannot be found or extracted
N)
JsonWebKey.=kidz%Failed to extract key ID from token: keyszJWKS document contains no keysz3No matching kid in JWKS, using single available keyzNo matching key found for kid=z in JWKSzutf-8zFailed to convert JWK to PEM: )base64r   authlib.joser	  r   r   r   urlsafe_b64decoder   r   rD   r   r   
import_keyas_pemdecode)ru   r  r5   r  r   r	  
header_b64headerr  r   r  matching_keykeyjwks   &&&           r'   r   4CIMDAssertionValidator._extract_public_key_from_jwksm  sy    	+	QS)!,J#S_q%8!899JZZ 8 8 DEF**U#C
 xx#=>>Csswwu~," 
 4yA~#Aw##I !#A#h!OPP	J''5C::<&&w//7  	QDQCHIqP	Q8  	J=aSABI	Js0   A1E "/E4 E1E,,E14F?FF)r   r   r   r   r   r   r   N)rS   rT   rU   rV   rW   r   rv   r   r   r  r   rZ   r%   r&   r'   r   r     s2    	 !
+U%||5J 5Jr&   r   c                  N    ] tR tRtRtRR R lltR R ltR R	 ltR
 R ltRt	R# )CIMDClientManageri  a7  Manages all CIMD client operations for OAuth proxy.

This class encapsulates:
- CIMD client detection
- Document fetching and validation
- Synthetic OAuth client creation
- Private key JWT assertion validation

This allows the OAuth proxy to delegate all CIMD-specific logic to a
single, focused manager class.
Nc               $    V ^8  d   QhRRRRRR/# )r9   enable_cimdrh   default_scoper;   allowed_redirect_uri_patternsr0   r%   )r=   s   "r'   r>   CIMDClientManager.__annotate__  s(     + ++ + (8	+r&   c                    Wn         W n        W0n        \        4       V n        \        4       V n        \        \        4      V n	        R# )zInitialize CIMD client manager.

Args:
    enable_cimd: Whether CIMD support is enabled
    default_scope: Default scope for CIMD clients if not specified in document
    allowed_redirect_uri_patterns: Allowed redirect URI patterns (proxy's config)
N)
enabledr  r  ro   _fetcherr   _assertion_validatorr   rS   r   )ru   r  r  r  s   &&&&r'   rv   CIMDClientManager.__init__  s5     #*-J*#$:$<! *r&   c                    V ^8  d   QhRRRR/# r   r%   )r=   s   "r'   r>   r     s     	K 	K3 	K4 	Kr&   c                `    V P                   ;'       d    V P                  P                  V4      # )zCheck if client_id is a CIMD URL.

Args:
    client_id: Client ID to check

Returns:
    True if client_id is an HTTPS URL (CIMD format)
)r"  r#  r   )ru   r   s   &&r'   r   #CIMDClientManager.is_cimd_client_id  s%     ||JJ ? ?	 JJr&   c                   V ^8  d   QhRR/# )r9   r   r;   r%   )r=   s   "r'   r>   r     s     / /c /r&   c                ,  "   V P                   '       g   R#  V P                  P                  V4      G Rj  xL
 p^ RIHp RpT! TRTTP                  TP                  ;'       g    T P                  TP                  T P                  TP                  T\        P                  ! 4       R7
      pT P
                  P!                  RTTP                  4       T#  L  \        \        3 d(   pT P
                  P                  RY4        Rp?R# Rp?ii ; i5i)aI  Fetch CIMD document and create synthetic OAuth client.

Args:
    client_id_url: HTTPS URL pointing to CIMD document

Returns:
    OAuthProxyClient with CIMD document attached, or None if fetch fails

Note:
    Return type is left untyped to avoid circular import with oauth_proxy.
    Returns OAuthProxyClient instance or None.
NzCIMD fetch failed for %s: %s)ProxyDCRClient)
r   client_secretr    r+   r/   r"   r  r   cimd_documentcimd_fetched_atz"CIMD client resolved: %s (name=%s))r"  r#  r   r_   r\   r   r   &fastmcp.server.auth.oauth_proxy.modelsr+  r+   r/   r  r"   r  r   r   r   )ru   r   r   r   r+  r    clients   &&     r'   
get_clientCIMDClientManager.get_client  s      |||	!]]00??H 	J
 #' ,,..66D$6$6'/'J'J*.*L*L ,," IIK
 	0  	

 = @ 34 	KK >Q	sD   DC CC *D&A1DC D*DDDDc               $    V ^8  d   QhRRRRRR/# )r9   r   r;   r   r<   rh   r%   )r=   s   "r'   r>   r     s(     
 

 	

 

r&   c                  "   \        VR4      '       d   VP                  '       g   \        R4      hVP                  pVP                  R8w  d   \        R4      hV P                  P                  WP                  W44      G Rj  xL
 #  L5i)aV  Validate JWT assertion for private_key_jwt auth.

Args:
    assertion: JWT assertion string from client
    client: OAuth proxy client (must have cimd_document)
    token_endpoint: Token endpoint URL for aud validation

Returns:
    True if assertion is valid

Raises:
    ValueError: If client doesn't have CIMD document or validation fails
r-  z2Client must have CIMD document for private_key_jwtprivate_key_jwtz6CIMD document must specify private_key_jwt auth methodN)hasattrr-  rD   r"   r$  r  r   )ru   r   r0  r   r   s   &&&& r'   validate_private_key_jwt*CIMDClientManager.validate_private_key_jwt  s     & v//v7K7K7KQRR''..2CCUVV..AA''
 
 	
 
s   $B
AB
BB
)r$  r#  r  r  r"  r   )Tr~   N)
rS   rT   rU   rV   rW   rv   r   r1  r7  rZ   r%   r&   r'   r  r    s"    
+*	K/b
 
r&   r  )-rW   
__future__r   r   r   r   collections.abcr   dataclassesr   datetimer   email.utilsr   typingr   r   r	   urllib.parser
   pydanticr   r   r   r   fastmcp.server.auth.ssrfr   r   r   r   fastmcp.utilities.loggingr   r   r   rS   r   r   r   r\   r_   ra   rl   ro   r   r  r%   r&   r'   <module>rC     s     #    # !  - . . ! B B  1=	H	j9 jZ6) 64Y 4      G GT^J ^JBz
 z
r&   