3
3
# Distributed under the terms of the Modified BSD License.
4
4
import datetime
5
5
import functools
6
+ import inspect
6
7
import ipaddress
7
8
import json
8
9
import mimetypes
@@ -134,7 +135,21 @@ def clear_login_cookie(self):
134
135
self .force_clear_cookie (self .cookie_name )
135
136
136
137
def get_current_user (self ):
137
- return self .identity_provider .get_user (self )
138
+ clsname = self .__class__ .__name__
139
+ msg = (
140
+ f"Calling `{ clsname } .get_current_user()` directly is deprecated in jupyter-server 2.0."
141
+ " Use `self.current_user` instead (works in all versions)."
142
+ )
143
+ if hasattr (self , "_jupyter_current_user" ):
144
+ # backward-compat: return _jupyter_current_user
145
+ warnings .warn (
146
+ msg ,
147
+ DeprecationWarning ,
148
+ stacklevel = 2 ,
149
+ )
150
+ return self ._jupyter_current_user
151
+ # haven't called get_user in prepare, raise
152
+ raise RuntimeError (msg )
138
153
139
154
def skip_check_origin (self ):
140
155
"""Ask my login_handler if I should skip the origin_check
@@ -164,7 +179,7 @@ def cookie_name(self):
164
179
@property
165
180
def logged_in (self ):
166
181
"""Is a user currently logged in?"""
167
- user = self .get_current_user ()
182
+ user = self .current_user
168
183
return user and not user == "anonymous"
169
184
170
185
@property
@@ -346,6 +361,13 @@ def allow_credentials(self):
346
361
def set_default_headers (self ):
347
362
"""Add CORS headers, if defined"""
348
363
super ().set_default_headers ()
364
+
365
+ def set_cors_headers (self ):
366
+ """Add CORS headers, if defined
367
+
368
+ Now that current_user is async (jupyter-server 2.0),
369
+ must be called at the end of prepare(), instead of in set_default_headers.
370
+ """
349
371
if self .allow_origin :
350
372
self .set_header ("Access-Control-Allow-Origin" , self .allow_origin )
351
373
elif self .allow_origin_pat :
@@ -484,6 +506,9 @@ def check_referer(self):
484
506
485
507
def check_xsrf_cookie (self ):
486
508
"""Bypass xsrf cookie checks when token-authenticated"""
509
+ if not hasattr (self , "_jupyter_current_user" ):
510
+ # Called too early, will be checked later
511
+ return
487
512
if self .token_authenticated or self .settings .get ("disable_check_xsrf" , False ):
488
513
# Token-authenticated requests do not need additional XSRF-check
489
514
# Servers without authentication are vulnerable to XSRF
@@ -543,9 +568,40 @@ def check_host(self):
543
568
)
544
569
return allow
545
570
546
- def prepare (self ):
571
+ async def prepare (self ):
547
572
if not self .check_host ():
548
573
raise web .HTTPError (403 )
574
+
575
+ from jupyter_server .auth import IdentityProvider
576
+
577
+ if (
578
+ type (self .identity_provider ) is IdentityProvider
579
+ and inspect .getmodule (self .get_current_user ).__name__ != __name__
580
+ ):
581
+ # check for overridden get_current_user + default IdentityProvider
582
+ # deprecated way to override auth (e.g. JupyterHub < 3.0)
583
+ # allow deprecated, overridden get_current_user
584
+ warnings .warn (
585
+ "Overriding JupyterHandler.get_current_user is deprecated in jupyter-server 2.0."
586
+ " Use an IdentityProvider class." ,
587
+ DeprecationWarning ,
588
+ # stacklevel not useful here
589
+ )
590
+ user = self .get_current_user ()
591
+ else :
592
+ user = self .identity_provider .get_user (self )
593
+ if inspect .isawaitable (user ):
594
+ # IdentityProvider.get_user _may_ be async
595
+ user = await user
596
+
597
+ # self.current_user for tornado's @web.authenticated
598
+ # self._jupyter_current_user for backward-compat in deprecated get_current_user calls
599
+ # and our own private checks for whether .current_user has been set
600
+ self .current_user = self ._jupyter_current_user = user
601
+ # complete initial steps which require auth to resolve first:
602
+ self .set_cors_headers ()
603
+ if self .request .method not in {"GET" , "HEAD" , "OPTIONS" }:
604
+ self .check_xsrf_cookie ()
549
605
return super ().prepare ()
550
606
551
607
# ---------------------------------------------------------------
@@ -638,10 +694,10 @@ def write_error(self, status_code, **kwargs):
638
694
class APIHandler (JupyterHandler ):
639
695
"""Base class for API handlers"""
640
696
641
- def prepare (self ):
697
+ async def prepare (self ):
698
+ await super ().prepare ()
642
699
if not self .check_origin ():
643
700
raise web .HTTPError (404 )
644
- return super ().prepare ()
645
701
646
702
def write_error (self , status_code , ** kwargs ):
647
703
"""APIHandler errors are JSON, not human pages"""
@@ -663,14 +719,6 @@ def write_error(self, status_code, **kwargs):
663
719
self .log .warning (reply ["message" ])
664
720
self .finish (json .dumps (reply ))
665
721
666
- def get_current_user (self ):
667
- """Raise 403 on API handlers instead of redirecting to human login page"""
668
- # preserve _user_cache so we don't raise more than once
669
- if hasattr (self , "_user_cache" ):
670
- return self ._user_cache
671
- self ._user_cache = user = super ().get_current_user ()
672
- return user
673
-
674
722
def get_login_url (self ):
675
723
# if get_login_url is invoked in an API handler,
676
724
# that means @web.authenticated is trying to trigger a redirect.
0 commit comments