
    i)                        d dl Z d dlZd dlZd dlmZmZ d dlZd dlZd dlm	Z	m
Z
mZmZmZmZmZ d dlmZmZmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlZd d	lmZmZmZm Z  d dl!m"Z# d d
l$m%Z%  e%         e
       Z& ejN                  ejP                          ejR                  e*      Z+ e jX                  dd      Z-dZ.e- dZ/dZ0dZ1e- dZ2 e jX                  dd      Z3 e jX                  dd      Z4 e jX                  dd      Z5 e jX                  dd      Z6i Z7i Z8i Z9dZ:defdZ;dede<dee   fd Z=e&j}                  d!d"d#g$      d%efd&       Z?e&j}                  d'd"d#g$      d%efd(       Z@e&j                  d)      d6d*e<d+e<fd,       ZBe&j                  d-      d7d*e<d.e<fd/       ZCe&j                  d0      d1        ZDe&j                  d2      d3        ZEe&j                  d4      d5        ZFy)8    N)DictOptional)FastAPI	APIRouterDependsRequestResponseFormHTTPException)RedirectResponseHTMLResponseJSONResponse)jwt)base64url_decode)quote)	urlencode)LMSPlatform
DeploymentAccessToken
LoginState)load_dotenv)levelCANVAS_BASEzhttps://sso.canvaslms.comz4https://sso.canvaslms.com/api/lti/authorize_redirectz/login/oauth2/tokenz4https://canvas.instructure.com/api/lti/security/jwkszhttps://canvas.instructure.comz/api/v1	CLIENT_ID282480000000000004CLIENT_SECRET@yVCzMWhwrC6yrLKHRW7eXFyhLN7txRFz6KaKCCrfrKJmFG6G6mzDmQK6vcRGzrrCLTI_DEPLOYMENT_ID TARGET_LINK_URIz0https://answerous.contactous.com:9000/lti/launchg      $@returnc                  "  K   t        j                  t              4 d{   } | j                  t               d{   }|j                          |j                         cddd      d{    S 7 R7 77 	# 1 d{  7  sw Y   yxY ww)z Fetch JWKS from Canvas platform.timeoutN)httpxAsyncClientHTTPX_TIMEOUTgetJWKS_ENDPOINTraise_for_statusjson)clientrs     -/var/www/html/content_weaver/routers/lti13.py
fetch_jwksr/   <   so       7  6**]++	vvx  +   sT   BA4BA:A6#A:"B.A8/B6A:8B:B BBBjwkskidc                 b    | j                  dg       D ]  }|j                  d      |k(  s|c S  y )Nkeysr1   )r(   )r0   r1   ks      r.   _find_jwk_for_kidr5   D   s6    XXfb! 55<3H     z/loginGETPOST)methodsrequestc                   K   t        j                  d      }t        j                  d      }|t        j                         dt        |<   t	        | j
                        }| j                  dk(  r)| j                          d{   }|j                  |       t        j                  d|        |j                  d      }|j                  d      }|sd}|r|nt        }t        }|j                  d      }|j                  d	      }	|t        ||||d
dddd
}|	r|	|d	<   t        r	t        |d<   t          dt#        |       }
t        j                  d|
        t%        |
      S 7 w)z
    Initiate OIDC login with Canvas SSO.
    - login_hint: typically provided by the platform when building the deployment; for testing you can provide a dummy value.
    - target: optional override for the TARGET_LINK_URI
        )noncecreatedr8   NzLogin request params: target_link_uri
login_hint
syllabuildlti_message_hintid_token	form_postopenidnone)
iss	client_idr@   r?   stater=   response_typeresponse_modescopepromptlti_deployment_id?z*Redirecting to Canvas authorize endpoint: )secretstoken_urlsafetimeIN_MEMORY_STATEdictquery_paramsmethodformupdateloggerinfor(   r    CANVAS_ISSUERr   DEPLOYMENT_IDAUTHORIZE_ENDPOINTr   r   )r:   rI   r=   paramsrW   targetr@   r?   rG   rB   urls              r.   loginra   O   s_     !!"%E!!"%E',EOE'&&'F ~~\\^#d
KK(12ZZ)*FL)J!
 &fOO CL)Jzz"45  *#$F %5!"&3"#   )F"3!4
5C
KK<SEBCC  U $s   B E(E&C$E(z/launchc                   K   | j                          d{   }|j                  d      }|j                  d      }|r|st        dd      |t        vrt        dd      t        |   d   }	 t	        j
                  |      }|j                  d
      }t                d{   }t        ||      }	|	st        dd|       	 t	        j                  ||	dgt        t              }
|
j                  d      |k7  rt        dd      |
j                  d      }|
j                  d      }|
||t        j                         dt        |<   d| d|
j                  d       d|
j                  d       d|
j                  d       d| dt        j                  |
d       d| d }t!        |      S 7 # t        $ r}t        dd	|       d}~ww xY w7 3# t        $ r}t        dd|       d}~ww xY ww)!z>
    Canvas will POST an id_token (JWT) after OIDC login.
    NrC   rI     zMissing id_token or statestatus_codedetailzInvalid or expired stater=   zInvalid id_token header: r1   zNo matching JWK for kid=RS256)
algorithmsaudienceissueri  zFailed to verify id_token: zInvalid nonce in id_tokensubcode)claimsrI   	auth_coder>   z7
    <h2>LTI Launch Success</h2>
    <p>sub (user id): z</p>
    <p>deployment_id: z7https://purl.imsglobal.org/spec/lti/claim/deployment_idz</p>
    <p>message_type: z6https://purl.imsglobal.org/spec/lti/claim/message_typez</p>
    <p>lti_version: z1https://purl.imsglobal.org/spec/lti/claim/versionz</p>
    <p>auth_code: z</p>
    <pre>   )indentz3</pre>
    <p>Next: call <code>/token_exchange?sub=z.&grant_type=authorization_code</code></p>
    )rW   r(   r   rS   r   get_unverified_header	Exceptionr/   r5   decoder   r[   rR   IN_MEMORY_LAUNCHESr+   dumpsr   )r:   rW   rC   rI   expected_nonceheaderser1   r0   jwkrm   rk   rn   htmls                 r.   launchr{      s/    
 Dxx
#HHHWE54OPPO#4NOO$U+G4NU++H5 ++e
CD
D#
&C6Nse4TUU	Wy 
 zz'n,4OPP
**U
C

6"I 99;	se zz"[\] ^jj!YZ[ \ZZ STU V+ 

**VA
&	' (--0E 2	D y    U6OPQs4STTU   W6QRSQT4UVVWse   G:F2AG:0F5 G:#G$"G:#G *C	G:5	G>GGG:	G7"G22G77G:z/token_exchangerk   
grant_typec                 ,  K   | t         vrt        dd      t         |    }i }|dk(  r8|d   j                  d      }|st        dd      dt        t        t
        |d	}n"|d
k(  rd
t        t        dd}nt        dd      t        j                  t              4 d{   }	 |j                  t        |ddi       d{   }|j                          ddd      d{    j!                         }	|	t#        j"                         dt$        | <   t'        |	      S 7 7 _# t        j                  $ rB}|j                  |j                  j                  n
t        |      }t        dd|       d}~ww xY w7 # 1 d{  7  sw Y   xY ww)z
    Exchange for an access token that can be used to call Canvas APIs.
    
    Supports:
    - grant_type=authorization_code (preferred in Canvas LTI 1.3)
    - grant_type=client_credentials (if enabled by your Canvas admin)
      zLaunch not found for subrd   authorization_coderm   z.https://purl.imsglobal.org/spec/lti/claim/coderc   z,No authorization code found in launch claims)r|   rH   client_secretredirect_urirl   client_credentialszurl:GET|/api/v1/*)r|   rH   r   rL   zUnsupported grant_typer#   NAcceptzapplication/json)datarw   i  zToken endpoint error: )token_responseobtained_at)rt   r   r(   r   r   r    r%   r&   r'   postTOKEN_ENDPOINTr*   HTTPStatusErrorresponsetextstrr+   rR   IN_MEMORY_ACCESS_TOKENSr   )
rk   r|   r{   r   rl   r,   respexcr   r   s
             r.   token_exchanger      s     $$4NOO$FD)) h##$TUC8fgg /"*+
 
+	+."*(
 4LMM  7 Y Y6	Y^$SeHfggD!!#Y Y YY[N )yy{$C 
 ''!Yg$$ 	Y(+(@3<<$$c#hDC:PQUPV8WXX	Y	Y Y Y Ysx   BF D!!F$E?&D%D#D%F#E=$>F#D%%E:8=E55E::E?=F?FFFFz/canvas_apiendpointc                 >  K   | t         vrt        dd      t         |    d   j                  d      }|st        dd      |j                  d      sd|z   }t         | }t        j                  t        	      4 d
{   }|j                  |dd| i       d
{   }|j                  dk\  r$t        |j                  d|j                         t        |j                               cd
d
d
      d
{    S 7 7 c7 	# 1 d
{  7  sw Y   y
xY ww)z
    Call a Canvas REST API endpoint using the stored access token.
    - endpoint should be the path under /api/v1 (e.g. /users/self/profile, /courses)
    r~   z:No access token found for sub. Call /token_exchange first.rd   r   access_tokenrc   z3Stored token response does not include access_token/r#   NAuthorizationzBearer )rw   zCanvas API returned error: )r   r   r(   
startswithCANVAS_API_BASEr%   r&   r'   re   r   r   r+   )rk   r   tokenr`   r,   r-   s         r.   
canvas_apir     s     ))4pqq#C()9:>>~NE4ijj s#>hZ
(C  7 & &6**S?geW<M*N*OO==CAMMD_`a`f`f_gBhiiAFFH%& & &O& & & &s[   A>D DDD D!AD0D<D=DDDDDDDz/_debug/statec                     K   t         S wN)rS    r6   r.   debug_stater   +  s        	z/_debug/launchesc                     K   t         S wr   )rt   r   r6   r.   debug_launchesr   0  s     r   z/_debug/tokensc                     K   t         S wr   )r   r   r6   r.   debug_tokensr   5  s     ""r   )r   )z/users/self)Gosr+   rR   typingr   r   loggingr%   fastapir   r   r   r   r	   r
   r   fastapi.responsesr   r   r   joser   
jose.utilsr   urllib.parser   r   rP   db_config.modelsr   r   r   r   db_config.databasedatabasedbasedotenvr   routerbasicConfigINFO	getLogger__name__rY   getenvr   r]   r   r)   r[   r   r   r   r\   r    rS   rt   r   r'   r/   r   r5   	api_routera   r{   r(   r   r   r   r   r   r   r6   r.   <module>r      s^   	   !   W W W J J  '  "  M M "  	   ',, '			8	$ bii'BC L = 34F0 M) BIIk#78			/+mn		-r2 "))-/ab    $ D s x~  (UFO4:!:! 5:!z )eV_5@' @ 6@F 9(c 9(s 9( 9(x M&# & & &8 O     # #r6   