1#----------------------------------------------------------------------------- 2# Copyright (c) 2012 - 2021, Anaconda, Inc., and Bokeh Contributors. 3# All rights reserved. 4# 5# The full license is in the file LICENSE.txt, distributed with this software. 6#----------------------------------------------------------------------------- 7 8#----------------------------------------------------------------------------- 9# Boilerplate 10#----------------------------------------------------------------------------- 11import pytest ; pytest 12 13#----------------------------------------------------------------------------- 14# Imports 15#----------------------------------------------------------------------------- 16 17# Standard library imports 18from types import ModuleType 19 20# External imports 21from tornado.web import RequestHandler 22 23# Bokeh imports 24from bokeh._testing.util.api import verify_all 25from bokeh._testing.util.filesystem import with_file_contents, with_file_contents_async 26 27# Module under test 28import bokeh.server.auth_provider as bsa # isort:skip 29 30#----------------------------------------------------------------------------- 31# Setup 32#----------------------------------------------------------------------------- 33 34ALL = ( 35 'AuthModule', 36 'AuthProvider', 37 'NullAuth' 38) 39 40@pytest.fixture 41def null_auth(): 42 return bsa.NullAuth() 43 44#----------------------------------------------------------------------------- 45# General API 46#----------------------------------------------------------------------------- 47 48Test___all__ = verify_all(bsa, ALL) 49 50class TestNullAuth: 51 def test_endpoints(self, null_auth) -> None: 52 assert null_auth.endpoints == [] 53 54 def test_get_user(self, null_auth) -> None: 55 assert null_auth.get_user == None 56 57 async def test_get_user_async(self, null_auth) -> None: 58 assert null_auth.get_user_async == None 59 60 def test_login_url(self, null_auth) -> None: 61 assert null_auth.login_url == None 62 63 def test_get_login_url(self, null_auth) -> None: 64 assert null_auth.get_login_url == None 65 66 def test_login_handler(self, null_auth) -> None: 67 assert null_auth.login_handler == None 68 69 def test_logout_url(self, null_auth) -> None: 70 assert null_auth.logout_url == None 71 72 def test_logout_handler(self, null_auth) -> None: 73 assert null_auth.logout_handler == None 74 75class TestAuthModule_properties: 76 def test_no_endpoints(self) -> None: 77 def func(filename): 78 am = bsa.AuthModule(filename) 79 assert am.endpoints == [] 80 81 with_file_contents(""" 82def get_user(): pass 83def get_login_url(): pass 84 """, func, suffix='.py') 85 86 with_file_contents(""" 87def get_user(): pass 88login_url = "/foo" 89 """, func, suffix='.py') 90 91 def test_login_url_endpoint(self) -> None: 92 def func(filename): 93 am = bsa.AuthModule(filename) 94 assert am.endpoints[0][0] == '/foo' 95 assert issubclass(am.endpoints[0][1], RequestHandler) 96 with_file_contents(""" 97from tornado.web import RequestHandler 98def get_user(): pass 99login_url = "/foo" 100class LoginHandler(RequestHandler): pass 101 """, func, suffix='.py') 102 103 def test_logout_url_endpoint(self) -> None: 104 def func(filename): 105 am = bsa.AuthModule(filename) 106 assert am.endpoints[0][0] == '/bar' 107 assert issubclass(am.endpoints[0][1], RequestHandler) 108 with_file_contents(""" 109from tornado.web import RequestHandler 110def get_user(): pass 111login_url = "/foo" 112logout_url = "/bar" 113class LogoutHandler(RequestHandler): pass 114 """, func, suffix='.py') 115 116 def test_login_logout_url_endpoint(self) -> None: 117 def func(filename): 118 am = bsa.AuthModule(filename) 119 endpoints = sorted(am.endpoints) 120 assert endpoints[0][0] == '/bar' 121 assert issubclass(endpoints[0][1], RequestHandler) 122 assert endpoints[1][0] == '/foo' 123 assert issubclass(endpoints[1][1], RequestHandler) 124 with_file_contents(""" 125def get_user(): pass 126login_url = "/foo" 127from tornado.web import RequestHandler 128class LoginHandler(RequestHandler): pass 129logout_url = "/bar" 130from tornado.web import RequestHandler 131class LogoutHandler(RequestHandler): pass 132 """, func, suffix='.py') 133 134 def test_get_user(self) -> None: 135 def func(filename): 136 am = bsa.AuthModule(filename) 137 assert am.get_user is not None 138 assert am.get_user('handler') == 10 139 140 with_file_contents(""" 141def get_user(handler): return 10 142login_url = "/foo" 143 """, func, suffix='.py') 144 145 async def test_get_user_async(self) -> None: 146 async def func(filename): 147 am = bsa.AuthModule(filename) 148 assert am.get_user_async is not None 149 assert await am.get_user_async('handler') == 10 150 151 await with_file_contents_async(""" 152async def get_user_async(handler): return 10 153login_url = "/foo" 154 """, func, suffix='.py') 155 156 157 def test_login_url(self) -> None: 158 def func(filename): 159 am = bsa.AuthModule(filename) 160 assert am.login_url == "/foo" 161 assert am.get_login_url is None 162 assert am.login_handler is None 163 assert am.logout_url is None 164 assert am.logout_handler is None 165 166 with_file_contents(""" 167def get_user(handler): return 10 168login_url = "/foo" 169 """, func, suffix='.py') 170 171 def test_get_login_url(self) -> None: 172 def func(filename): 173 am = bsa.AuthModule(filename) 174 assert am.login_url is None 175 assert am.get_login_url('handler') == 20 176 assert am.login_handler is None 177 assert am.logout_url is None 178 assert am.logout_handler is None 179 180 with_file_contents(""" 181def get_user(handler): return 10 182def get_login_url(handler): return 20 183 """, func, suffix='.py') 184 185 def test_login_handler(self) -> None: 186 def func(filename): 187 am = bsa.AuthModule(filename) 188 assert am.login_url == "/foo" 189 assert am.get_login_url is None 190 assert issubclass(am.login_handler, RequestHandler) 191 assert am.logout_url is None 192 assert am.logout_handler is None 193 194 with_file_contents(""" 195def get_user(handler): return 10 196login_url = "/foo" 197from tornado.web import RequestHandler 198class LoginHandler(RequestHandler): pass 199 """, func, suffix='.py') 200 201 def test_logout_url(self) -> None: 202 def func(filename): 203 am = bsa.AuthModule(filename) 204 assert am.login_url == "/foo" 205 assert am.get_login_url is None 206 assert am.login_handler is None 207 assert am.logout_url == "/bar" 208 assert am.logout_handler is None 209 210 with_file_contents(""" 211def get_user(handler): return 10 212login_url = "/foo" 213logout_url = "/bar" 214 """, func, suffix='.py') 215 216 def test_logout_handler(self) -> None: 217 def func(filename): 218 am = bsa.AuthModule(filename) 219 assert am.login_url == "/foo" 220 assert am.get_login_url is None 221 assert am.login_handler is None 222 assert am.logout_url == "/bar" 223 assert issubclass(am.logout_handler, RequestHandler) 224 225 with_file_contents(""" 226def get_user(handler): return 10 227login_url = "/foo" 228logout_url = "/bar" 229from tornado.web import RequestHandler 230class LogoutHandler(RequestHandler): pass 231 """, func, suffix='.py') 232 233 234class TestAuthModule_validation: 235 def test_no_file(self) -> None: 236 with pytest.raises(ValueError) as e: 237 bsa.AuthModule("junkjunkjunk") 238 assert str(e).startswith("no file exists at module_path:") 239 240 def test_both_user(self) -> None: 241 def func(filename): 242 with pytest.raises(ValueError) as e: 243 bsa.AuthModule(filename) 244 assert str(e) == "Only one of get_user or get_user_async should be supplied" 245 246 with_file_contents(""" 247def get_user(handler): return 10 248async def get_user_async(handler): return 20 249 """, func, suffix='.py') 250 251 @pytest.mark.parametrize('user_func', ['get_user', 'get_user_async']) 252 def test_no_login(self, user_func) -> None: 253 def func(filename): 254 with pytest.raises(ValueError) as e: 255 bsa.AuthModule(filename) 256 assert str(e) == "When user authentication is enabled, one of login_url or get_login_url must be supplied" 257 258 with_file_contents(""" 259def %s(handler): return 10 260 """ % user_func, func, suffix='.py') 261 262 def test_both_login(self) -> None: 263 def func(filename): 264 with pytest.raises(ValueError) as e: 265 bsa.AuthModule(filename) 266 assert str(e) == "At most one of login_url or get_login_url should be supplied" 267 268 with_file_contents(""" 269def get_user(handler): return 10 270def get_login_url(handler): return 20 271login_url = "/foo" 272 """, func, suffix='.py') 273 274 def test_handler_with_get_login_url(self) -> None: 275 def func(filename): 276 with pytest.raises(ValueError) as e: 277 bsa.AuthModule(filename) 278 assert str(e) == "LoginHandler cannot be used with a get_login_url() function" 279 280 with_file_contents(""" 281def get_user(handler): return 10 282def get_login_url(handler): return 20 283from tornado.web import RequestHandler 284class LoginHandler(RequestHandler): pass 285 """, func, suffix='.py') 286 287 def test_login_handler_wrong_type(self) -> None: 288 def func(filename): 289 with pytest.raises(ValueError) as e: 290 bsa.AuthModule(filename) 291 assert str(e) == "LoginHandler must be a Tornado RequestHandler" 292 293 with_file_contents(""" 294def get_user(handler): return 10 295login_url = "/foo" 296class LoginHandler(object): pass 297 """, func, suffix='.py') 298 299 @pytest.mark.parametrize('login_url', ['http://foo.com', 'https://foo.com', '//foo.com']) 300 def test_login_handler_wrong_url(self, login_url) -> None: 301 def func(filename): 302 with pytest.raises(ValueError) as e: 303 bsa.AuthModule(filename) 304 assert str(e) == "LoginHandler can only be used with a relative login_url" 305 306 with_file_contents(""" 307def get_user(handler): return 10 308login_url = %r 309 """ % login_url, func, suffix='.py') 310 311 def test_logout_handler_wrong_type(self) -> None: 312 def func(filename): 313 with pytest.raises(ValueError) as e: 314 bsa.AuthModule(filename) 315 assert str(e) == "LoginHandler must be a Tornado RequestHandler" 316 317 with_file_contents(""" 318def get_user(handler): return 10 319login_url = "/foo" 320class LogoutHandler(object): pass 321 """, func, suffix='.py') 322 323 @pytest.mark.parametrize('logout_url', ['http://foo.com', 'https://foo.com', '//foo.com']) 324 def test_logout_handler_wrong_url(self, logout_url) -> None: 325 def func(filename): 326 with pytest.raises(ValueError) as e: 327 bsa.AuthModule(filename) 328 assert str(e) == "LoginHandler can only be used with a relative login_url" 329 330 with_file_contents(""" 331def get_user(handler): return 10 332logout_url = %r 333 """ % logout_url, func, suffix='.py') 334 335_source = """ 336def get_login_url(): 337 pass 338 339logout_url = "foo" 340 341class LoginHandler(object): 342 pass 343""" 344 345def test_load_auth_module() -> None: 346 def func(filename): 347 m = bsa.load_auth_module(filename) 348 assert isinstance(m, ModuleType) 349 assert [x for x in sorted(dir(m)) if not x.startswith("__")] == ['LoginHandler', 'get_login_url', 'logout_url'] 350 with_file_contents(_source, func, suffix='.py') 351 352def test_probably_relative_url() -> None: 353 assert bsa.probably_relative_url("httpabc") 354 assert bsa.probably_relative_url("httpsabc") 355 assert bsa.probably_relative_url("/abc") 356 assert not bsa.probably_relative_url("http://abc") 357 assert not bsa.probably_relative_url("https://abc") 358 assert not bsa.probably_relative_url("//abc") 359 360#----------------------------------------------------------------------------- 361# Dev API 362#----------------------------------------------------------------------------- 363 364#----------------------------------------------------------------------------- 365# Private API 366#----------------------------------------------------------------------------- 367 368#----------------------------------------------------------------------------- 369# Code 370#----------------------------------------------------------------------------- 371