Merge pull request #2968 from coreos-inc/joseph.schorr/QS-110/user-login-lock

Reduce lock contention on invalid user login calls
This commit is contained in:
josephschorr 2018-01-09 12:41:00 -05:00 committed by GitHub
commit b17c05fbd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -660,20 +660,28 @@ def get_matching_users(username_prefix, robot_namespace=None, organization=None,
def verify_user(username_or_email, password):
""" Verifies that the given username/email + password pair is valid. If the username or e-mail
address is invalid, returns None. If the password specified does not match for the given user,
either returns None or raises TooManyLoginAttemptsException if there have been too many
invalid login attempts. Returns the user object if the login was valid.
"""
# Make sure we didn't get any unicode for the username.
try:
str(username_or_email)
except ValueError:
return None
# Fetch the user with the matching username or e-mail address.
try:
fetched = User.get((User.username == username_or_email) |
(User.email == username_or_email))
fetched = User.get((User.username == username_or_email) | (User.email == username_or_email))
except User.DoesNotExist:
return None
# If the user has any invalid login attempts, check to see if we are within the exponential
# backoff window for the user. If so, we raise an exception indicating that the user cannot
# login.
now = datetime.utcnow()
if fetched.invalid_login_attempts > 0:
can_retry_at = exponential_backoff(fetched.invalid_login_attempts, EXPONENTIAL_BACKOFF_SCALE,
fetched.last_invalid_login)
@ -682,17 +690,25 @@ def verify_user(username_or_email, password):
retry_after = can_retry_at - now
raise TooManyLoginAttemptsException('Too many login attempts.', retry_after.total_seconds())
# Hash the given password and compare it to the specified password.
if (fetched.password_hash and
hash_password(password, fetched.password_hash) == fetched.password_hash):
if fetched.invalid_login_attempts > 0:
fetched.invalid_login_attempts = 0
fetched.save()
# If the user previously had any invalid login attempts, clear them out now.
if fetched.invalid_login_attempts > 0:
(User
.update(invalid_login_attempts=0)
.where(User.id == fetched.id)
.execute())
# Return the valid user.
return fetched
fetched.invalid_login_attempts += 1
fetched.last_invalid_login = now
fetched.save()
# Otherwise, update the user's invalid login attempts.
(User
.update(invalid_login_attempts=User.invalid_login_attempts+1, last_invalid_login=now)
.where(User.id == fetched.id)
.execute())
# We weren't able to authorize the user
return None