''' Provide a hook for supplying authorization mechanisms to a Bokeh server.'''#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsimportimportlib.utilfromos.pathimportisfilefromtypesimportModuleTypefromtypingimport(TYPE_CHECKING,Awaitable,Callable,NewType,)# External importsfromtornado.httputilimportHTTPServerRequestfromtornado.webimportRequestHandler# Bokeh importsfrom..util.serializationimportmake_globally_unique_idifTYPE_CHECKING:from..core.typesimportPathLike#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('AuthModule','AuthProvider','NullAuth',)#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------User=NewType("User",object)
[docs]classAuthProvider:''' Abstract base class for implementing authorization hooks. Subclasses must supply one of: ``get_user`` or ``get_user_async``. Subclasses must also supply one of ``login_url`` or ``get_login_url``. Optionally, if ``login_url`` provides a relative URL, then ``login_handler`` may also be supplied. The properties ``logout_url`` and ``get_logout_handler`` are analogous to the corresponding login properties, and are optional. '''def__init__(self)->None:self._validate()@propertydefendpoints(self)->list[tuple[str,type[RequestHandler]]]:''' URL patterns for login/logout endpoints. '''endpoints:list[tuple[str,type[RequestHandler]]]=[]ifself.login_handler:assertself.login_urlisnotNoneendpoints.append((self.login_url,self.login_handler))ifself.logout_handler:assertself.logout_urlisnotNoneendpoints.append((self.logout_url,self.logout_handler))returnendpoints@propertydefget_login_url(self)->Callable[[HTTPServerRequest],str]|None:''' A function that computes a URL to redirect unauthenticated users to for login. This property may return None, if a ``login_url`` is supplied instead. If a function is returned, it should accept a ``RequestHandler`` and return a login URL for unauthenticated users. '''pass@propertydefget_user(self)->Callable[[HTTPServerRequest],User]|None:''' A function to get the current authenticated user. This property may return None, if a ``get_user_async`` function is supplied instead. If a function is returned, it should accept a ``RequestHandler`` and return the current authenticated user. '''pass@propertydefget_user_async(self)->Callable[[HTTPServerRequest],Awaitable[User]]|None:''' An async function to get the current authenticated user. This property may return None, if a ``get_user`` function is supplied instead. If a function is returned, it should accept a ``RequestHandler`` and return the current authenticated user. '''pass@propertydeflogin_handler(self)->type[RequestHandler]|None:''' A request handler class for a login page. This property may return None, if ``login_url`` is supplied instead. If a class is returned, it must be a subclass of RequestHandler, which will used for the endpoint specified by ``logout_url`` '''pass@propertydeflogin_url(self)->str|None:''' A URL to redirect unauthenticated users to for login. This property may return None, if a ``get_login_url`` function is supplied instead. '''pass@propertydeflogout_handler(self)->type[RequestHandler]|None:''' A request handler class for a logout page. This property may return None. If a class is returned, it must be a subclass of RequestHandler, which will used for the endpoint specified by ``logout_url`` '''pass@propertydeflogout_url(self)->str|None:''' A URL to redirect authenticated users to for logout. This property may return None. '''passdef_validate(self)->None:ifself.get_userandself.get_user_async:raiseValueError("Only one of get_user or get_user_async should be supplied")if(self.get_userorself.get_user_async)andnot(self.login_urlorself.get_login_url):raiseValueError("When user authentication is enabled, one of login_url or get_login_url must be supplied")ifself.login_urlandself.get_login_url:raiseValueError("At most one of login_url or get_login_url should be supplied")ifself.login_handlerandself.get_login_url:raiseValueError("LoginHandler cannot be used with a get_login_url() function")ifself.login_handlerandnotissubclass(self.login_handler,RequestHandler):raiseValueError("LoginHandler must be a Tornado RequestHandler")ifself.login_urlandnotprobably_relative_url(self.login_url):raiseValueError("LoginHandler can only be used with a relative login_url")ifself.logout_handlerandnotissubclass(self.logout_handler,RequestHandler):raiseValueError("LogoutHandler must be a Tornado RequestHandler")ifself.logout_urlandnotprobably_relative_url(self.logout_url):raiseValueError("LogoutHandler can only be used with a relative logout_url")
[docs]classAuthModule(AuthProvider):''' An AuthProvider configured from a Python module. The following properties return the corresponding values from the module if they exist, or None otherwise: * ``get_login_url``, * ``get_user`` * ``get_user_async`` * ``login_url`` * ``logout_url`` The ``login_handler`` property will return a ``LoginHandler`` class from the module, or None otherwise. The ``logout_handler`` property will return a ``LogoutHandler`` class from the module, or None otherwise. '''def__init__(self,module_path:PathLike)->None:ifnotisfile(module_path):raiseValueError(f"no file exists at module_path: {module_path!r}")self._module=load_auth_module(module_path)super().__init__()@propertydefget_user(self):returngetattr(self._module,'get_user',None)@propertydefget_user_async(self):returngetattr(self._module,'get_user_async',None)@propertydeflogin_url(self):returngetattr(self._module,'login_url',None)@propertydefget_login_url(self):returngetattr(self._module,'get_login_url',None)@propertydeflogin_handler(self):returngetattr(self._module,'LoginHandler',None)@propertydeflogout_url(self):returngetattr(self._module,'logout_url',None)@propertydeflogout_handler(self):returngetattr(self._module,'LogoutHandler',None)
[docs]classNullAuth(AuthProvider):''' A default no-auth AuthProvider. All of the properties of this provider return None. '''@propertydefget_user(self):returnNone@propertydefget_user_async(self):returnNone@propertydeflogin_url(self):returnNone@propertydefget_login_url(self):returnNone@propertydeflogin_handler(self):returnNone@propertydeflogout_url(self):returnNone@propertydeflogout_handler(self):returnNone
#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------defload_auth_module(module_path:PathLike)->ModuleType:''' Load a Python source file at a given path as a module. Arguments: module_path (str): path to a Python source file Returns module '''module_name="bokeh.auth_"+make_globally_unique_id().replace('-','')spec=importlib.util.spec_from_file_location(module_name,module_path)module=importlib.util.module_from_spec(spec)spec.loader.exec_module(module)returnmoduledefprobably_relative_url(url:str)->bool:''' Return True if a URL is not one of the common absolute URL formats. Arguments: url (str): a URL string Returns bool '''returnnoturl.startswith(("http://","https://","//"))#-----------------------------------------------------------------------------# Private API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Code#-----------------------------------------------------------------------------