add token_authenticated property

indicates if a token is used for authentication, in which case xsrf checks should be skipped.
Min RK 9 years ago
parent 4a8af93b5b
commit 70e79a0ad6

@ -97,7 +97,7 @@ class LoginHandler(IPythonHandler):
auth_header_pat = re.compile('token\s+(.+)', re.IGNORECASE)
@classmethod
def get_user_token(cls, handler):
def get_token(cls, handler):
"""Get the user token from a request
Default:
@ -120,11 +120,22 @@ class LoginHandler(IPythonHandler):
Origin check should be skipped for token-authenticated requests.
"""
return not cls.is_token_authenticated(handler)
@classmethod
def is_token_authenticated(cls, handler):
"""Check if the handler has been authenticated by a token.
This is used to signal certain things, such as:
- permit access to REST API
- xsrf protection
- skip origin-checks for scripts
"""
if getattr(handler, '_user_id', None) is None:
# ensure get_user has been called, so we know if we're token-authenticated
handler.get_current_user()
token_authenticated = getattr(handler, '_token_authenticated', False)
return not token_authenticated
return getattr(handler, '_token_authenticated', False)
@classmethod
def get_user(cls, handler):
@ -136,40 +147,51 @@ class LoginHandler(IPythonHandler):
# called on LoginHandler itself.
if getattr(handler, '_user_id', None):
return handler._user_id
user_id = handler.get_secure_cookie(handler.cookie_name)
if not user_id:
user_id = cls.get_user_token(handler)
if user_id is None:
user_id = handler.get_secure_cookie(handler.cookie_name)
else:
cls.set_login_cookie(handler, user_id)
# Record that we've been authenticated with a token.
# Used in should_check_origin above.
handler._token_authenticated = True
if user_id is None:
# prevent extra Invalid cookie sig warnings:
handler.clear_login_cookie()
token = handler.token
if not token and not handler.login_available:
if not handler.login_available:
# Completely insecure! No authentication at all.
# No need to warn here, though; validate_security will have already done that.
return 'anonymous'
if token:
# check login token from URL argument or Authorization header
user_token = cls.get_user_token(handler)
one_time_token = handler.one_time_token
authenticated = False
if user_token == token:
# token-authenticated, set the login cookie
handler.log.info("Accepting token-authenticated connection from %s", handler.request.remote_ip)
authenticated = True
elif one_time_token and user_token == one_time_token:
# one-time-token-authenticated, only allow this token once
handler.settings.pop('one_time_token', None)
handler.log.info("Accepting one-time-token-authenticated connection from %s", handler.request.remote_ip)
authenticated = True
if authenticated:
user_id = uuid.uuid4().hex
cls.set_login_cookie(handler, user_id)
# Record that we've been authenticated with a token.
# Used in should_check_origin above.
handler._token_authenticated = True
user_id = 'anonymous'
# cache value for future retrievals on the same request
handler._user_id = user_id
return user_id
@classmethod
def get_user_token(cls, handler):
"""Identify the user based on a token in the URL or Authorization header"""
token = handler.token
if not token:
return
# check login token from URL argument or Authorization header
user_token = cls.get_token(handler)
one_time_token = handler.one_time_token
authenticated = False
if user_token == token:
# token-authenticated, set the login cookie
handler.log.debug("Accepting token-authenticated connection from %s", handler.request.remote_ip)
authenticated = True
elif one_time_token and user_token == one_time_token:
# one-time-token-authenticated, only allow this token once
handler.settings.pop('one_time_token', None)
handler.log.info("Accepting one-time-token-authenticated connection from %s", handler.request.remote_ip)
authenticated = True
if authenticated:
return uuid.uuid4().hex
else:
return None
@classmethod
def validate_security(cls, app, ssl_options=None):

@ -48,7 +48,7 @@ def log():
class AuthenticatedHandler(web.RequestHandler):
"""A RequestHandler with an authenticated user."""
@property
def content_security_policy(self):
"""The default Content-Security-Policy header
@ -95,6 +95,13 @@ class AuthenticatedHandler(web.RequestHandler):
return False
return not self.login_handler.should_check_origin(self)
@property
def token_authenticated(self):
"""Have I been authenticated with a token?"""
if self.login_handler is None or not hasattr(self.login_handler, 'is_token_authenticated'):
return False
return self.login_handler.is_token_authenticated(self)
@property
def cookie_name(self):
default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
@ -317,7 +324,15 @@ class IPythonHandler(AuthenticatedHandler):
self.request.path, origin, host,
)
return allow
def check_xsrf_cookie(self):
"""Bypass xsrf checks when token-authenticated"""
if self.token_authenticated:
# Token-authenticated requests do not need additional XSRF-check
# Servers without authentication are vulnerable to XSRF
return
return super(IPythonHandler, self).check_xsrf_cookie()
#---------------------------------------------------------------
# template rendering
#---------------------------------------------------------------
@ -346,6 +361,8 @@ class IPythonHandler(AuthenticatedHandler):
version_hash=self.version_hash,
ignore_minified_js=self.ignore_minified_js,
xsrf_form_html=self.xsrf_form_html,
token=self.token,
xsrf_token=self.xsrf_token,
**self.jinja_template_vars
)
@ -409,15 +426,6 @@ class APIHandler(IPythonHandler):
raise web.HTTPError(404)
return super(APIHandler, self).prepare()
def check_xsrf_cookie(self):
"""Check non-empty body on POST for XSRF
instead of checking the cookie for forms.
"""
if self.request.method.upper() == 'POST' and not self.request.body:
# Require non-empty POST body for XSRF
raise web.HTTPError(400, "POST requests must have a JSON body. If no content is needed, use '{}'.")
@property
def content_security_policy(self):
csp = '; '.join([

Loading…
Cancel
Save