diff --git a/authserv/app_common.py b/authserv/app_common.py index 9d5c8df10cec1b797839b68b94640a8942181049..32b6c0d4f1aa425be99e95de92ce63211f827ae1 100644 --- a/authserv/app_common.py +++ b/authserv/app_common.py @@ -1,3 +1,4 @@ +import re from flask import abort, current_app from authserv import auth from authserv import protocol @@ -22,8 +23,34 @@ def check_ratelimit(request, username, source_ip): abort(503) +def _validate_username(username): + """Validate a username before fetching it from the backend. + + More of a syntax check than anything. It's just to save cycles in + case of (only the most trivial of) brute-force / exploitation + attempts. + + Returns the validated username (type str). + """ + if not username: + raise Exception('empty username') + + if len(username) > 256: + raise Exception('name too long') + + # This will throw a UnicodeEncodeError on non-ASCII usernames. + username = str(username) + + # Check that it does not contain spaces or newlines. + if re.match(r'\s', username): + raise Exception('invalid characters in username') + + return username + + def do_auth(username, service, shard, password, otp_token, source_ip, password_only=False): + # Username must be an ASCII string. bl = AuthBlackList(current_app.config.get('BLACKLIST_COUNT', 5), current_app.config.get('BLACKLIST_PERIOD', 600), current_app.config.get('BLACKLIST_TIME', 6*3600)) @@ -38,6 +65,12 @@ def do_auth(username, service, shard, password, otp_token, source_ip, retval = protocol.ERR_AUTHENTICATION_FAILURE errmsg = 'user does not exist' out_shard = None + + try: + username = _validate_username(username) + except: + return (retval, errmsg, None) + user = current_app.userdb.get_user(username, service, shard) if user: retval, errmsg = auth.authenticate(