raise 403 on disallowed user, rather than redirect to login url

raise UserNotAllowed exception in generic `check_hub_user`
when a user or service is identified and not allowed.

turn it into `HTTPError(403)` in tornado `get_current_user` wrapper,
caching `None` so that subsequent calls don't re-trigger the same error.
This commit is contained in:
Min RK
2017-06-07 15:30:12 +02:00
parent ca4952a85d
commit dda3762b48
3 changed files with 34 additions and 8 deletions

View File

@@ -6,7 +6,7 @@ import json
import traceback import traceback
from tornado.log import LogFormatter, access_log from tornado.log import LogFormatter, access_log
from tornado.web import StaticFileHandler from tornado.web import StaticFileHandler, HTTPError
def coroutine_traceback(typ, value, tb): def coroutine_traceback(typ, value, tb):
@@ -88,7 +88,7 @@ def log_request(handler):
try: try:
user = handler.get_current_user() user = handler.get_current_user()
except web.HTTPError: except HTTPError:
username = '' username = ''
else: else:
if user is None: if user is None:

View File

@@ -7,7 +7,6 @@ HubAuth can be used in any application, even outside tornado.
HubAuthenticated is a mixin class for tornado handlers that should authenticate with the Hub. HubAuthenticated is a mixin class for tornado handlers that should authenticate with the Hub.
""" """
import json
import os import os
import re import re
import socket import socket
@@ -494,6 +493,18 @@ class HubOAuth(HubAuth):
handler.clear_cookie(self.cookie_name, path=self.base_url) handler.clear_cookie(self.cookie_name, path=self.base_url)
class UserNotAllowed(Exception):
"""Exception raised when a user is identified and not allowed"""
def __init__(self, model):
self.model = model
def __str__(self):
return '<{cls} {kind}={name}>'.format(
cls=self.__class__.__name__,
kind=self.model['kind'],
name=self.model['name'],
)
class HubAuthenticated(object): class HubAuthenticated(object):
"""Mixin for tornado handlers that are authenticated with JupyterHub """Mixin for tornado handlers that are authenticated with JupyterHub
@@ -568,7 +579,7 @@ class HubAuthenticated(object):
""" """
name = model['name'] name = model['name']
kind = model.get('kind', 'user') kind = model.setdefault('kind', 'user')
if self.allow_all: if self.allow_all:
app_log.debug("Allowing Hub %s %s (all Hub users and services allowed)", kind, name) app_log.debug("Allowing Hub %s %s (all Hub users and services allowed)", kind, name)
return model return model
@@ -584,7 +595,7 @@ class HubAuthenticated(object):
return model return model
else: else:
app_log.warning("Not allowing Hub service %s", name) app_log.warning("Not allowing Hub service %s", name)
return None raise UserNotAllowed(model)
if self.hub_users and name in self.hub_users: if self.hub_users and name in self.hub_users:
# user in whitelist # user in whitelist
@@ -597,7 +608,7 @@ class HubAuthenticated(object):
return model return model
else: else:
app_log.warning("Not allowing Hub user %s", name) app_log.warning("Not allowing Hub user %s", name)
return None raise UserNotAllowed(model)
def get_current_user(self): def get_current_user(self):
"""Tornado's authentication method """Tornado's authentication method
@@ -611,7 +622,15 @@ class HubAuthenticated(object):
if not user_model: if not user_model:
self._hub_auth_user_cache = None self._hub_auth_user_cache = None
return return
try:
self._hub_auth_user_cache = self.check_hub_user(user_model) self._hub_auth_user_cache = self.check_hub_user(user_model)
except UserNotAllowed as e:
# cache None, in case get_user is called again while processing the error
self._hub_auth_user_cache = None
raise HTTPError(403, "{kind} {name} is not allowed.".format(**e.model))
except Exception:
self._hub_auth_user_cache = None
raise
return self._hub_auth_user_cache return self._hub_auth_user_cache

View File

@@ -37,6 +37,13 @@ def test_singleuser_auth(app, io_loop):
r = requests.get(url_path_join(url, 'logout'), cookies=cookies) r = requests.get(url_path_join(url, 'logout'), cookies=cookies)
assert len(r.cookies) == 0 assert len(r.cookies) == 0
# another user accessing should get 403, not redirect to login
cookies = app.login_user('burgess')
r = requests.get(url, cookies=cookies)
assert r.status_code == 403
print(r.text)
assert r.text == ''
def test_disable_user_config(app, io_loop): def test_disable_user_config(app, io_loop):
# use StubSingleUserSpawner to launch a single-user app in a thread # use StubSingleUserSpawner to launch a single-user app in a thread