mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-02-04 08:13:38 +00:00
Give the same error if the username doesn't exist or if the password is wrong. If we deliver separate errors, we tell the attacker whether they've picked a valid password or not. Also, if username doesn't exist, hash the password anyway to avoid this timing side-channel attack: 1. Invalid Username: A. User tries to log in with invalid username. B. User name is not found in database. C. Password is never hashed. 2. Invalid Password: A. User tries to log in with valid username. B. User name is found in database. C. Password is hashed. Given that proper password hashing will take a minute, *not* hashing the password takes so much less time that we've effectively indicated to the attacker that the username didn't exist, regardless of the error message. This way, no such error occurs.
119 lines
3.7 KiB
Python
119 lines
3.7 KiB
Python
# Form based authentication for CherryPy. Requires the
|
|
# Session tool to be loaded.
|
|
#
|
|
# Thanks for this code is owed to Arnar Birgisson -at - gmail.com. It
|
|
# is based on code he wrote that was retrieved from
|
|
# http://tools.cherrypy.org/wiki/AuthenticationAndAccessRestrictions
|
|
# on 1 February 2011.
|
|
|
|
# TODO: DeprecationWarning: the md5 module is deprecated; use hashlib instead import md5
|
|
|
|
import cherrypy
|
|
import urllib, hashlib
|
|
import cfg
|
|
import random
|
|
|
|
cfg.session_key = '_cp_username'
|
|
|
|
def check_credentials(username, passphrase):
|
|
"""Verifies credentials for username and passphrase.
|
|
Returns None on success or a string describing the error on failure"""
|
|
|
|
start = time.clock()
|
|
|
|
if not username or not passphrase:
|
|
error = "No username or password."
|
|
cfg.log(error)
|
|
return error
|
|
|
|
u = cfg.users[username]
|
|
|
|
elif u is None:
|
|
# hash the password whether the user exists, to foil timing
|
|
# side-channel attacks
|
|
hashlib.md5(passphrase).hexdigest()
|
|
error = "Bad user-name or password."
|
|
elif u['passphrase'] != hashlib.md5(passphrase).hexdigest():
|
|
error = "Bad user-name or password."
|
|
else:
|
|
error = None
|
|
|
|
if error:
|
|
cfg.log(error)
|
|
return error
|
|
|
|
def check_auth(*args, **kwargs):
|
|
"""A tool that looks in config for 'auth.require'. If found and it
|
|
is not None, a login is required and the entry is evaluated as a
|
|
list of conditions that the user must fulfill"""
|
|
conditions = cherrypy.request.config.get('auth.require', None)
|
|
# format GET params
|
|
get_params = urllib.quote(cherrypy.request.request_line.split()[1])
|
|
if conditions is not None:
|
|
username = cherrypy.session.get(cfg.session_key)
|
|
if username:
|
|
cherrypy.request.login = username
|
|
for condition in conditions:
|
|
# A condition is just a callable that returns true or false
|
|
if not condition():
|
|
# Send old page as from_page parameter
|
|
raise cherrypy.HTTPRedirect("/auth/login?from_page=%s" % get_params)
|
|
else:
|
|
# Send old page as from_page parameter
|
|
raise cherrypy.HTTPRedirect("/auth/login?from_page=%s" % get_params)
|
|
|
|
cherrypy.tools.auth = cherrypy.Tool('before_handler', check_auth)
|
|
|
|
def require(*conditions):
|
|
"""A decorator that appends conditions to the auth.require config
|
|
variable."""
|
|
def decorate(f):
|
|
if not hasattr(f, '_cp_config'):
|
|
f._cp_config = dict()
|
|
if 'auth.require' not in f._cp_config:
|
|
f._cp_config['auth.require'] = []
|
|
f._cp_config['auth.require'].extend(conditions)
|
|
return f
|
|
return decorate
|
|
|
|
|
|
# Conditions are callables that return True
|
|
# if the user fulfills the conditions they define, False otherwise
|
|
#
|
|
# They can access the current username as cherrypy.request.login
|
|
#
|
|
# Define those at will however suits the application.
|
|
|
|
def member_of(groupname):
|
|
def check():
|
|
# replace with actual check if <username> is in <groupname>
|
|
return cherrypy.request.login == 'joe' and groupname == 'admin'
|
|
return check
|
|
|
|
def name_is(reqd_username):
|
|
return lambda: reqd_username == cherrypy.request.login
|
|
|
|
# These might be handy
|
|
|
|
def any_of(*conditions):
|
|
"""Returns True if any of the conditions match"""
|
|
def check():
|
|
for c in conditions:
|
|
if c():
|
|
return True
|
|
return False
|
|
return check
|
|
|
|
# By default all conditions are required, but this might still be
|
|
# needed if you want to use it inside of an any_of(...) condition
|
|
def all_of(*conditions):
|
|
"""Returns True if all of the conditions match"""
|
|
def check():
|
|
for c in conditions:
|
|
if not c():
|
|
return False
|
|
return True
|
|
return check
|
|
|
|
|