@@ -538,15 +538,33 @@ def _check_handler_auth(
538
538
method = getattr (handler , method_name .lower ())
539
539
is_unimplemented = method == web .RequestHandler ._unimplemented_method
540
540
is_allowlisted = hasattr (method , "__allow_unauthenticated" )
541
- # TODO: modify `tornado.web.authenticated` upstream?
542
- is_blocklisted = method .__code__ .co_qualname .startswith ("authenticated" )
541
+ is_blocklisted = _has_tornado_web_authenticated (method )
543
542
if not is_unimplemented and not is_allowlisted and not is_blocklisted :
544
543
missing_authentication .append (
545
544
f"- { method_name } of { handler .__name__ } registered for { matcher } "
546
545
)
547
546
return missing_authentication
548
547
549
548
549
+ def _has_tornado_web_authenticated (method : t .Callable ) -> bool :
550
+ """Check if given method was decorated with @web.authenticated.
551
+
552
+ Note: it is ok if we reject on @authorized @web.authenticated
553
+ because the correct order is @web.authenticated @authorized.
554
+ """
555
+ if not hasattr (method , "__wrapped__" ):
556
+ return False
557
+ if not hasattr (method , "__code__" ):
558
+ return False
559
+ code = method .__code__
560
+ if hasattr (code , "co_qualname" ):
561
+ # new in 3.11
562
+ return code .co_qualname .startswith ("authenticated" )
563
+ elif hasattr (code , "co_filename" ):
564
+ return code .co_filename .replace ("\\ " , "/" ).endswith ("tornado/web.py" )
565
+ return False
566
+
567
+
550
568
class JupyterPasswordApp (JupyterApp ):
551
569
"""Set a password for the Jupyter server.
552
570
0 commit comments