
    k7iL                       S r SSKJr  SSKrSSKJr  SSKJrJr  SSK	r	SSK
JrJr  SSKJr  SSKJr  SS	KJr  SS
KJrJrJr  SSKJrJr  SSKJr  SSKJrJr  SSKJ r   SSK!J"r"  SSK#J$r$  SSK%J&r&J'r'  \$" \(5      r) " S S\SS9r* " S S\5      r+\" SSSS9 " S S5      5       r, " S S\5      r- " S S\5      r. " S  S!\5      r/g)"z*TokenVerifier implementations for FastMCP.    )annotationsN)	dataclass)Anycast)
JsonWebKeyJsonWebToken)	JoseError)serialization)rsa)
AnyHttpUrl	SecretStrfield_validator)BaseSettingsSettingsConfigDict)	TypedDict)AccessTokenTokenVerifier)ENV_FILEparse_scopes)
get_logger)NotSetNotSetTc                  j    \ rS rSr% SrS\S'   S\S'   S\S'   S\S'   S\S'   S\S	'   S
\S'   S\S'   Srg)JWKData   zJSON Web Key data structure.strktykidusealgne	list[str]x5cx5t N__name__
__module____qualname____firstlineno____doc____annotations____static_attributes__r'       [/home/james-whalen/.local/lib/python3.13/site-packages/fastmcp/server/auth/providers/jwt.pyr   r      s-    &	H	H	H	H
F
F	N	Hr0   r   F)totalc                  $    \ rS rSr% SrS\S'   Srg)JWKSData(   z JSON Web Key Set data structure.zlist[JWKData]keysr'   Nr(   r'   r0   r1   r4   r4   (   s    *
r0   r4   T)frozenkw_onlyreprc                  |    \ rS rSr% SrS\S'   S\S'   \SS j5       r       S               SS	 jjrS
r	g)
RSAKeyPair.   zRSA key pair for JWT testing.r   private_keyr   
public_keyc                   [         R                  " SSS9nUR                  [        R                  R
                  [        R                  R                  [        R                  " 5       S9R                  S5      nUR                  5       R                  [        R                  R
                  [        R                  R                  S9R                  S5      nU " [        U5      US9$ )zT
Generate an RSA key pair for testing.

Returns:
    RSAKeyPair: Generated key pair
i  i   )public_exponentkey_size)encodingformatencryption_algorithmutf-8)rB   rC   )r=   r>   )r   generate_private_keyprivate_bytesr
   EncodingPEMPrivateFormatPKCS8NoEncryptiondecoder>   public_bytesPublicFormatSubjectPublicKeyInfor   )clsr=   private_pem
public_pems       r1   generateRSAKeyPair.generate5   s     ..!
 "//"++// ..44!.!;!;!= 0 
 &/	 	 ""$\&//33$11FF   VG_ 	 !+.!
 	
r0   Nc                   SS0nU(       a  XxS'   UU[        [        R                  " 5       5      [        [        R                  " 5       5      U-   S.n	U(       a  X9S'   U(       a  SR                  U5      U	S'   U(       a  U	R                  U5        [	        S/5      n
U
R                  XU R                  R                  5       5      nUR                  S5      $ )	a  
Generate a test JWT token for testing purposes.

Args:
    subject: Subject claim (usually user ID)
    issuer: Issuer claim
    audience: Audience claim - can be a string or list of strings (optional)
    scopes: List of scopes to include
    expires_in_seconds: Token expiration time in seconds
    additional_claims: Any additional claims to include
    kid: Key ID to include in header
r!   RS256r   )subissiatexpaud scoperE   )	inttimejoinupdater   encoder=   get_secret_valuerM   )selfsubjectissueraudiencescopesexpires_in_secondsadditional_claimsr   headerpayloadjwt_libtoken_bytess               r1   create_tokenRSAKeyPair.create_tokenY   s    . !5M tyy{#tyy{#&88	
 %EN"xx/GGNN,- y)nnT-->>@
 !!'**r0   r'   )returnr;   )zfastmcp-userzhttps://fastmcp.example.comNN  NN)rf   r   rg   r   rh   str | list[str] | Noneri   list[str] | Nonerj   r_   rk   zdict[str, Any] | Noner   
str | Nonerr   r   )
r)   r*   r+   r,   r-   r.   classmethodrT   rp   r/   r'   r0   r1   r;   r;   .   s    'O!
 !
J &3+/#'"&372+2+ 2+ )	2+
 !2+  2+ 12+ 2+ 
2+ 2+r0   r;   c                      \ rS rSr% Sr\" S\SS9rSrS\	S'   Sr
S\	S	'   SrS\	S
'   SrS\	S'   SrS\	S'   SrS\	S'   SrS\	S'   \" SSS9\S 5       5       rSrg)JWTVerifierSettings   z$Settings for JWT token verification.FASTMCP_SERVER_AUTH_JWT_ignore)
env_prefixenv_fileextraNrv   r>   jwks_urirg   	algorithmrt   rh   ru   required_scopeszAnyHttpUrl | str | Nonebase_urlbefore)modec                    [        U5      $ Nr   )rQ   vs     r1   _parse_scopes!JWTVerifierSettings._parse_scopes   s     Ar0   r'   )r)   r*   r+   r,   r-   r   r   model_configr>   r.   r   rg   r   rh   r   r   r   rw   r   r/   r'   r0   r1   ry   ry      s    .%-L "J
!HjFJ Iz '+H$+(,O%,(,H%,&X6  7r0   ry   c                     ^  \ rS rSrSr\\\\\\\S.             SU 4S jjjrSS jrSS jrSS jr	SS jr
SS	 jrS
rU =r$ )JWTVerifier   aI  
JWT token verifier supporting both asymmetric (RSA/ECDSA) and symmetric (HMAC) algorithms.

This verifier validates JWT tokens using various signing algorithms:
- **Asymmetric algorithms** (RS256/384/512, ES256/384/512, PS256/384/512):
  Uses public/private key pairs. Ideal for external clients and services where
  only the authorization server has the private key.
- **Symmetric algorithms** (HS256/384/512): Uses a shared secret for both
  signing and verification. Perfect for internal microservices and trusted
  environments where the secret can be securely shared.

Use this when:
- You have JWT tokens issued by an external service (asymmetric)
- You need JWKS support for automatic key rotation (asymmetric)
- You have internal microservices sharing a secret key (symmetric)
- Your tokens contain standard OAuth scopes and claims
r>   r   rg   rh   r   r   r   c          
       > [         R                  UUUUUUUS.R                  5        VV	s0 s H  u  pU	[        Ld  M  X_M     sn	n5      n
U
R                  (       d  U
R
                  (       d  [        S5      eU
R                  (       a  U
R
                  (       a  [        S5      eU
R                  =(       d    SnUS;  a  [        SU S35      e[        TU ]%  U
R                  U
R                  S9  XPl        U
R                  U l        U
R                  U l        U
R                  U l        U
R
                  U l        [        U R                  /5      U l        [!        ["        5      U l        0 U l        S	U l        S
U l        gs  sn	nf )a  
Initialize the JWT token verifier.

Args:
    public_key: For asymmetric algorithms (RS256, ES256, etc.): PEM-encoded public key.
               For symmetric algorithms (HS256, HS384, HS512): The shared secret string.
    jwks_uri: URI to fetch JSON Web Key Set (only for asymmetric algorithms)
    issuer: Expected issuer claim
    audience: Expected audience claim(s)
    algorithm: JWT signing algorithm. Supported algorithms:
              - Asymmetric: RS256/384/512, ES256/384/512, PS256/384/512 (default: RS256)
              - Symmetric: HS256, HS384, HS512
    required_scopes: Required scopes for all tokens
    base_url: Base URL for TokenVerifier protocol
r   z.Either public_key or jwks_uri must be providedz/Provide either public_key or jwks_uri, not bothrW   >   ES256ES384ES512HS256HS384HS512PS256PS384PS512rW   RS384RS512zUnsupported algorithm: .)r   r   r   rs   N)ry   model_validateitemsr   r>   r   
ValueErrorr   super__init__r   r   rg   rh   r   jwtr   r)   logger_jwks_cache_jwks_cache_time
_cache_ttl)re   r>   r   rg   rh   r   r   r   kr   settings	__class__s              r1   r   JWTVerifier.__init__   sw   4 '55 #- ($ (!*'6 ( %'DA F? 
  ""8+<+<MNN8#4#4NOO&&1'	 
 
 6ykCDD 	&&$44 	 	

 #oo ))"-- )) 01 * ,.'(os
   F
F
c                  #    U R                   (       a  U R                   $  SSKnSSKnUR                  S5      S   nUSS[	        U5      S-  -
  -  -  nUR                  UR                  U5      5      nUR                  S5      nU R                  U5      I Sh  vN $  N! [         a  n[        SU 35      UeSnAff = f7f)z'Get the verification key for the token.r   Nr   =   r   z%Failed to extract key ID from token: )r>   base64jsonsplitlenloadsurlsafe_b64decodeget_get_jwks_key	Exceptionr   )re   tokenr   r   
header_b64rl   r   r#   s           r1   _get_verification_key!JWTVerifier._get_verification_key  s     ????"	QS)!,J#S_q%8!899JZZ 8 8 DEF**U#C++C0000 	QDQCHIqP	Qs;   CA8B  BB  CB   
B>*B99B>>Cc                  #    U R                   (       d  [        S5      e[        R                  " 5       nX R                  -
  U R                  :  aq  U(       a  XR
                  ;   a  U R
                  U   $ U(       dE  [        U R
                  5      S:X  a,  [        [        U R
                  R                  5       5      5      $  [        R                  " 5        ISh  vN nUR                  U R                   5      I Sh  vN nUR                  5         UR                  5       nSSS5      ISh  vN   0 U l        WR                  S/ 5       H_  nUR                  S5      n[        R                   " U5      nUR#                  5       n	U(       a  XR
                  U'   MQ  XR
                  S'   Ma     X l        U(       aI  XR
                  ;  a+  U R$                  R'                  SU5        [        SU S	35      eU R
                  U   $ [        U R
                  5      S:X  a,  [        [        U R
                  R                  5       5      5      $ [        U R
                  5      S:  a  [        S
5      e[        S5      e GN GN~ GNQ! , ISh  vN  (       d  f       GNg= f! [        R(                   a  n
[        SU
 35      U
eSn
A
f[*         a2  n
U R$                  R'                  SU
 35        [        SU
 35      U
eSn
A
ff = f7f)z(Fetch key from JWKS with simple caching.zJWKS URI not configured   Nr6   r   _defaultz-JWKS key lookup failed: key ID '%s' not foundzKey ID 'z' not found in JWKSz2Multiple keys in JWKS but no key ID (kid) in tokenzNo keys found in JWKSzFailed to fetch JWKS: zJWKS fetch failed: )r   r   r`   r   r   r   r   nextitervalueshttpxAsyncClientr   raise_for_statusr   r   
import_keyget_public_keyr   debug	HTTPErrorr   )re   r   current_timeclientresponse	jwks_datakey_datakey_kidjwkr>   r#   s              r1   r   JWTVerifier._get_jwks_key   s~    }}677yy{ ///$//As...'',,S!1!12a7D!1!1!8!8!:;<<,	B((**f!'DMM!::))+$MMO	 +*  "D%MM&"5",,u- ++H5 //1
0:$$W- 4>$$Z0 6 %1! ...KK%%G %xu4G%HII'',, t''(A-T%5%5%<%<%> ?@@))*Q.$L  %%<==K +: +***N  	B5aS9:A 	BKK 3A3785aS9:A	Bs   B?LJ I9J J>I<?$J#J .I?/CJ LAJ 	L
0J <J?J JJ	JJ K?1K  K?-K::K??Lc                    S HR  nX!;   d  M
  [        X   [        5      (       a  X   R                  5       s  $ [        X   [        5      (       d  MN  X   s  $    / $ )z
Extract scopes from JWT claims. Supports both 'scope' and 'scp'
claims.

Checks the `scope` claim first (standard OAuth2 claim), then the `scp`
claim (used by some Identity Providers).
)r^   scp)
isinstancer   r   list)re   claimsclaims      r1   _extract_scopesJWTVerifier._extract_scopes^  sQ     &EfmS11!=..00t44!=( & 	r0   c                  ^#     U R                  U5      I Sh  vN nU R                  R                  X5      nUR                  S5      =(       d2    UR                  S5      =(       d    UR                  S5      =(       d    SnUR                  S5      nU(       aR  U[        R                  " 5       :  a9  U R
                  R                  SU5        U R
                  R                  SU5        gU R                  (       aX  UR                  S	5      U R                  :w  a9  U R
                  R                  S
U5        U R
                  R                  SU5        gU R                  (       a  UR                  S5      mSn[        U R                  [        5      (       aS  [        T[        5      (       a   [        U4S jU R                   5       5      nORT[        [        U R                  5      ;   nO4[        T[        5      (       a  U R                  T;   nOTU R                  :H  nU(       d9  U R
                  R                  SU5        U R
                  R                  SU5        gU R                  U5      nU R                  (       ap  [!        U5      n[!        U R                  5      n	U	R#                  U5      (       d:  U R
                  R                  SUU	5        U R
                  R                  SU5        g[%        U['        U5      UU(       a  [)        U5      OSUS9$  GN! [*         a    U R
                  R                  S5         g[,         a/  n
U R
                  R                  S['        U
5      5         Sn
A
gSn
A
ff = f7f)z
Validates the provided JWT bearer token.

Args:
    token: The JWT token string to validate

Returns:
    AccessToken object if valid, None if invalid or expired
N	client_idazprX   unknownr[   z4Token validation failed: expired token for client %sz#Bearer token rejected for client %srY   z6Token validation failed: issuer mismatch for client %sr\   Fc              3  ,   >#    U  H	  oT;   v   M     g 7fr   r'   ).0expectedr\   s     r1   	<genexpr>0JWTVerifier.load_access_token.<locals>.<genexpr>  s      -<IOMs   z8Token validation failed: audience mismatch for client %sz4Token missing required scopes. Has: %s, Required: %sr   r   ri   
expires_atr   z5Token validation failed: JWT signature/format invalidzToken validation failed: %s)r   r   rM   r   r`   r   r   inforg   rh   r   r   anyr   r   r   setissubsetr   r   r_   r	   r   )re   r   verification_keyr   r   r[   audience_validri   token_scopesr   r#   r\   s              @r1   load_access_tokenJWTVerifier.load_access_tokeno  s    ^	%)%?%?%FF XX__U=F 

;' ::e$::e$ 	  **U#CsTYY[(!!JI   !F	R {{vzz%0DKK?!!L   !F	R }}jj' "'dmmT22!#t,,), -<@MM- *
 *-T4==0I)I "#t,,)-#)=),)=%KK%%R! KK$$%JIV ))&1F ##"6{"%d&:&:";&//==KK%%N$'
 KK$$%JIVi.'*3s8 _  Gn  	KKUV 	KK;SVD	s~   M?L LCL ,M?-A(L M?D	L M? BL 1M?2'L M?L %M<M?	M<%M72M?7M<<M?c                @   #    U R                  U5      I Sh  vN $  N7f)a  
Verify a bearer token and return access info if valid.

This method implements the TokenVerifier protocol by delegating
to our existing load_access_token method.

Args:
    token: The JWT token string to validate

Returns:
    AccessToken object if valid, None if invalid or expired
N)r   )re   r   s     r1   verify_tokenJWTVerifier.verify_token  s      ++E2222s   )
r   r   r   r   rh   rg   r   r   r   r>   )r>   str | NotSetT | Noner   r   rg   r   rh   z str | list[str] | NotSetT | Noner   r   r   zlist[str] | NotSetT | Noner   z!AnyHttpUrl | str | NotSetT | None)r   r   rr   r   )r   rv   rr   r   )r   zdict[str, Any]rr   r$   r   r   rr   zAccessToken | None)r)   r*   r+   r,   r-   r   r   r   r   r   r   r   r/   __classcell__r   s   @r1   r   r      s    * ,2)/'-5;*06<6<R )R '	R
 %R 3R (R 4R 4R RhQ(<B|"hT3 3r0   r   c                  F   ^  \ rS rSrSr S   SU 4S jjjrSS jrSrU =r$ )	StaticTokenVerifieri  a  
Simple static token verifier for testing and development.

This verifier validates tokens against a predefined dictionary of valid token
strings and their associated claims. When a token string matches a key in the
dictionary, the verifier returns the corresponding claims as if the token was
validated by a real authorization server.

Use this when:
- You're developing or testing locally without a real OAuth server
- You need predictable tokens for automated testing
- You want to simulate different users/scopes without complex setup
- You're prototyping and need simple API key-style authentication

WARNING: Never use this in production - tokens are stored in plain text!
c                ,   > [         TU ]  US9  Xl        g)z
Initialize the static token verifier.

Args:
    tokens: Dict mapping token strings to token metadata
           Each token should have: client_id, scopes, expires_at (optional)
    required_scopes: Required scopes for all tokens
)r   N)r   r   tokens)re   r   r   r   s      r1   r   StaticTokenVerifier.__init__  s     	9r0   c                  #    U R                   R                  U5      nU(       d  gUR                  S5      nUb  U[        R                  " 5       :  a  gUR                  S/ 5      nU R                  (       aR  [	        U5      n[	        U R                  5      nUR                  U5      (       d  [        R                  SU SU 35        g[        UUS   UUUS9$ 7f)z-Verify token against static token dictionary.Nr   ri   z$Token missing required scopes. Has: z, Required: r   r   )	r   r   r`   r   r   r   r   r   r   )re   r   
token_datar   ri   r   r   s          r1   r    StaticTokenVerifier.verify_token  s     [[__U+
  ^^L1
!j499;&>"- v;L!$"6"67O"++L99:<.UdTef  -!
 	
s   CC)r   r   )r   zdict[str, dict[str, Any]]r   ru   r   )	r)   r*   r+   r,   r-   r   r   r/   r   r   s   @r1   r   r     s3    ( -1) *  
 
r0   r   )0r-   
__future__r   r`   dataclassesr   typingr   r   r   authlib.joser   r   authlib.jose.errorsr	   cryptography.hazmat.primitivesr
   )cryptography.hazmat.primitives.asymmetricr   pydanticr   r   r   pydantic_settingsr   r   typing_extensionsr   fastmcp.server.authr   r   fastmcp.settingsr   fastmcp.utilities.authr   fastmcp.utilities.loggingr   fastmcp.utilities.typesr   r   r)   r   r   r4   r;   ry   r   r   r'   r0   r1   <module>r
     s    0 "  !   1 ) 8 9 ; ; > ' : % / 0 3	H	
iu 
y  $51\+ \+ 2\+~, .A3- A3H
?
- ?
r0   