Source code for bokeh.server.auth_provider

''' Provide a hook for supplying authorization mechanisms to a Bokeh server.

'''

#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import absolute_import, division, print_function, unicode_literals

import logging
log = logging.getLogger(__name__)

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

# Standard library imports
from os.path import isfile

# External imports
from tornado.web import RequestHandler

# Bokeh imports
from ..util.serialization import make_globally_unique_id

#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------

__all__ = (
    'AuthModule',
    'AuthProvider',
    'NullAuth'
)

#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------

# TODO (bev) make this an ABC but wait until Bokeh 2 / drop Python 2.7
[docs]class AuthProvider(object): ''' 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_hander`` are analogous to the corresponding login properties, and are optional. ''' def __init__(self): self._validate() @property def endpoints(self): ''' URL patterns for login/logout endpoints. ''' endpoints = [] if self.login_handler: endpoints.append((self.login_url, self.login_handler)) if self.logout_handler: endpoints.append((self.logout_url, self.logout_handler)) return endpoints @property def get_login_url(self): ''' A function that computes a URL to redirect unathenticated 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 unathenticated users. ''' pass @property def get_user(self): ''' 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 @property def get_user_async(self): ''' 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 @property def login_hander(self): ''' 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 @property def login_url(self): ''' A URL to redirect unauthenticated users to for login. This proprty may return None, if a ``get_login_url`` function is supplied instead. ''' pass @property def logout_hander(self): ''' 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 @property def logout_url(self): ''' A URL to redirect unathenticated users to for logout. This proprty may return None. ''' pass def _validate(self): if self.get_user and self.get_user_async: raise ValueError("Only one of get_user or get_user_async should be supplied") if (self.get_user or self.get_user_async) and not (self.login_url or self.get_login_url): raise ValueError("When user authentication is enabled, one of login_url or get_login_url must be supplied") if self.login_url and self.get_login_url: raise ValueError("At most one of login_url or get_login_url should be supplied") if self.login_handler and self.get_login_url: raise ValueError("LoginHandler cannot be used with a get_login_url() function") if self.login_handler and not issubclass(self.login_handler, RequestHandler): raise ValueError("LoginHandler must be a Tornado RequestHandler") if self.login_url and not probably_relative_url(self.login_url): raise ValueError("LoginHandler can only be used with a relative login_url") if self.logout_handler and not issubclass(self.logout_handler, RequestHandler): raise ValueError("LogoutHandler must be a Tornado RequestHandler") if self.logout_url and not probably_relative_url(self.logout_url): raise ValueError("LogoutHandler can only be used with a relative login_url")
[docs]class AuthModule(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): if not isfile(module_path): raise ValueError("no file exists at module_path: %r" % module_path) self._module = load_auth_module(module_path) super(AuthModule, self).__init__() @property def get_user(self): return getattr(self._module, 'get_user', None) @property def get_user_async(self): return getattr(self._module, 'get_user_async', None) @property def login_url(self): return getattr(self._module, 'login_url', None) @property def get_login_url(self): return getattr(self._module, 'get_login_url', None) @property def login_handler(self): return getattr(self._module, 'LoginHandler', None) @property def logout_url(self): return getattr(self._module, 'logout_url', None) @property def logout_handler(self): return getattr(self._module, 'LogoutHandler', None)
[docs]class NullAuth(AuthProvider): ''' A default no-auth AuthProvider. All of the properties of this provider return None. ''' @property def get_user(self): return None @property def get_user_async(self): return None @property def login_url(self): return None @property def get_login_url(self): return None @property def login_handler(self): return None @property def logout_url(self): return None @property def logout_handler(self): return None
#----------------------------------------------------------------------------- # Dev API #----------------------------------------------------------------------------- def load_auth_module(module_path): ''' 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('-', '') try: import importlib.util spec = importlib.util.spec_from_file_location(module_name, module_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) except ImportError: try: # python 3.4 from importlib.machinery import SourceFileLoader module = SourceFileLoader(module_name, module_path).load_module() except ImportError: # python 2 import imp module = imp.load_source(module_name, module_path) return module def probably_relative_url(url): ''' Return True if a URL is not one of the common absolute URL formats. Arguments: url (str): a URL string Returns bool ''' return not url.startswith(("http://", "https://", "//")) #----------------------------------------------------------------------------- # Private API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Code #-----------------------------------------------------------------------------