#!/usr/bin/env python
# -*- coding: utf-8 -*-
# actions_user.py - Waqas Bhatti (wbhatti@astro.princeton.edu) - Aug 2018
# License: MIT - see the LICENSE file for the full text.
'''This contains functions to drive user account related auth actions.
'''
#############
## LOGGING ##
#############
import logging
# get a logger
LOGGER = logging.getLogger(__name__)
#############
## IMPORTS ##
#############
try:
from datetime import datetime, timezone, timedelta
utc = timezone.utc
except Exception:
from datetime import datetime, timedelta, tzinfo
ZERO = timedelta(0)
class UTC(tzinfo):
"""UTC"""
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
utc = UTC()
import multiprocessing as mp
import socket
import uuid
from tornado.escape import squeeze
from sqlalchemy import select
from fuzzywuzzy.fuzz import UQRatio
from .. import authdb
from .session import auth_session_exists, auth_delete_sessions_userid
from ..permissions import pii_hash
from argon2 import PasswordHasher
from .. import validators
######################
## PASSWORD CONTEXT ##
######################
pass_hasher = PasswordHasher()
#######################
## PASSWORD HANDLING ##
#######################
[docs]def change_user_password(payload,
override_authdb_path=None,
raiseonfail=False,
min_pass_length=12,
max_similarity=30):
'''Changes the user's password.
Parameters
----------
payload : dict
This is a dict with the following required keys:
- user_id: int
- session_token: str
- full_name: str
- email: str
- current_password: str
- new_password: str
In addition to these items received from an authnzerver client, the
payload must also include the following keys (usually added in by a
wrapping function):
- reqid: int or str
- pii_salt: str
override_authdb_path : str or None
If given as a str, is the alternative path to the auth DB.
raiseonfail : bool
If True, will raise an Exception if something goes wrong.
min_pass_length : int
The minimum required character length of the password.
max_similarity : int
The maximum UQRatio required to fuzzy-match the input password against
the server's domain name, the user's email, or their name.
Returns
-------
dict
Returns a dict with the user's user_id and email as keys if successful.
Notes
-----
This logs out the user from all of their other sessions.
'''
for key in ('reqid','pii_salt'):
if key not in payload:
LOGGER.error(
"Missing %s in payload dict. Can't process this request." % key
)
return {
'success':False,
'user_id':None,
'email':None,
'messages':["Invalid password change request."],
}
for key in ('user_id',
'session_token',
'full_name',
'email',
'current_password',
'new_password'):
if key not in payload:
LOGGER.error(
'[%s] Invalid password change request, missing %s.' %
(payload['reqid'], key)
)
return {
'success':False,
'user_id':None,
'email':None,
'messages':['Invalid password change request. '
'Some args are missing.'],
}
# this checks if the database connection is live
currproc = mp.current_process()
engine = getattr(currproc, 'authdb_engine', None)
if override_authdb_path:
currproc.auth_db_path = override_authdb_path
if not engine:
currproc.authdb_engine, currproc.authdb_conn, currproc.authdb_meta = (
authdb.get_auth_db(
currproc.auth_db_path,
echo=raiseonfail
)
)
users = currproc.authdb_meta.tables['users']
# get the current password
sel = select([
users.c.password,
]).select_from(
users
).where(
users.c.user_id == payload['user_id']
).where(
users.c.email == payload['email']
).where(
users.c.is_active.is_(True)
)
result = currproc.authdb_conn.execute(sel)
rows = result.fetchone()
result.close()
if not rows or len(rows) == 0:
LOGGER.error(
"[%s] Password change request failed for "
"user_id: %s, email: %s. "
"The user was not found in the DB or is inactive." %
(payload['reqid'],
pii_hash(payload['user_id'],
payload['pii_salt']),
pii_hash(payload['email'],
payload['pii_salt']))
)
return {
'success':False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':['Your current password did '
'not match the stored password.']
}
#
# proceed with hashing
#
current_password = payload['current_password'][:1024]
new_password = payload['new_password'][:1024]
try:
pass_check = pass_hasher.verify(rows['password'],
current_password)
except Exception:
pass_check = False
if not pass_check:
LOGGER.error(
"[%s] Password change request failed for "
"user_id: %s, email: %s. "
"The input password did not match the stored password." %
(payload['reqid'],
pii_hash(payload['user_id'],
payload['pii_salt']),
pii_hash(payload['email'],
payload['pii_salt']))
)
return {
'success':False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':['Your current password did '
'not match the stored password.']
}
# check if the new hashed password is the same as the old hashed password,
# meaning that the new password is just the old one
try:
same_check = pass_hasher.verify(rows['password'], new_password)
except Exception:
same_check = False
if same_check:
LOGGER.error(
"[%s] Password change request failed for "
"user_id: %s, email: %s. "
"The new password was the same as the current password." %
(payload['reqid'],
pii_hash(payload['user_id'],
payload['pii_salt']),
pii_hash(payload['email'],
payload['pii_salt']))
)
return {
'success':False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':['Your new password cannot '
'be the same as your old password.']
}
# hash the user's password
hashed_password = pass_hasher.hash(new_password)
# validate the input password to see if it's OK
# do this here to make sure the password hash completes at least once
# verify the new password is OK
passok, messages = validate_input_password(
payload['full_name'],
payload['email'],
new_password,
payload['pii_salt'],
min_length=min_pass_length,
max_match_threshold=max_similarity
)
if passok:
# update the table for this user
upd = users.update(
).where(
users.c.user_id == payload['user_id']
).where(
users.c.is_active.is_(True)
).where(
users.c.email == payload['email']
).values({
'password': hashed_password
})
result = currproc.authdb_conn.execute(upd)
sel = select([
users.c.password,
]).select_from(users).where(
(users.c.user_id == payload['user_id'])
)
result = currproc.authdb_conn.execute(sel)
rows = result.fetchone()
result.close()
if rows and rows['password'] == hashed_password:
messages.append('Password changed successfully.')
LOGGER.info(
"[%s] Password change request succeeded for "
"user_id: %s, email: %s." %
(payload['reqid'],
pii_hash(payload['user_id'],
payload['pii_salt']),
pii_hash(payload['email'],
payload['pii_salt']))
)
# delete all of this user's other sessions
auth_delete_sessions_userid(
{'session_token':payload['session_token'],
'user_id':payload['user_id'],
'keep_current_session':True,
'reqid':payload['reqid'],
'pii_salt':payload['pii_salt']},
override_authdb_path=override_authdb_path,
raiseonfail=raiseonfail
)
return {
'success':True,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':(messages +
['For security purposes, you have been '
'logged out of all other sessions.'])
}
else:
messages.append('Password could not be changed.')
LOGGER.error(
"[%s] Password change request failed for "
"user_id: %s, email: %s. "
"The user row could not be updated in the DB." %
(payload['reqid'],
pii_hash(payload['user_id'],
payload['pii_salt']),
pii_hash(payload['email'],
payload['pii_salt']))
)
return {
'success':False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':messages
}
else:
LOGGER.error(
"[%s] Password change request failed for "
"user_id: %s, email: %s. "
"The new password entered is insecure." %
(payload['reqid'],
pii_hash(payload['user_id'],
payload['pii_salt']),
pii_hash(payload['email'],
payload['pii_salt']))
)
messages.append("The new password you entered is insecure. "
"It must be at least 12 characters long and "
"be sufficiently complex.")
return {
'success':False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages': messages
}
###################
## USER HANDLING ##
###################
[docs]def create_new_user(
payload,
min_pass_length=12,
max_similarity=30,
override_authdb_path=None,
raiseonfail=False,
):
'''Makes a new user.
Parameters
----------
payload : dict
This is a dict with the following required keys:
- full_name: str
- email: str
- password: str
In addition to these items received from an authnzerver client, the
payload must also include the following keys (usually added in by a
wrapping function):
- reqid: int or str
- pii_salt: str
override_authdb_path : str or None
If given as a str, is the alternative path to the auth DB.
raiseonfail : bool
If True, will raise an Exception if something goes wrong.
min_pass_length : int
The minimum required character length of the password.
max_similarity : int
The maximum UQRatio required to fuzzy-match the input password against
the server's domain name, the user's email, or their name.
Returns
-------
dict
Returns a dict with the user's user_id and user_email, and a boolean for
send_verification.
Notes
-----
The emailverify_sent_datetime is set to the current time. The initial
account's is_active is set to False and user_role is set to 'locked'.
The email verification token sent by the frontend expires in 2 hours. If the
user doesn't get to it by then, they'll have to wait at least 24 hours until
another one can be sent.
If the email address already exists in the database, then either the user
has forgotten that they have an account or someone else is being
annoying. In this case, if is_active is True, we'll tell the user that we've
sent an email but won't do anything. If is_active is False and
emailverify_sent_datetime is at least 24 hours in the past, we'll send a new
email verification email and update the emailverify_sent_datetime. In this
case, we'll just tell the user that we've sent the email but won't tell them
if their account exists.
Only after the user verifies their email, is_active will be set to True and
user_role will be set to 'authenticated'.
'''
for key in ('reqid','pii_salt'):
if key not in payload:
LOGGER.error(
"Missing %s in payload dict. Can't process this request." % key
)
return {
'success':False,
'user_email':None,
'user_id':None,
'send_verification':False,
'messages':["Invalid user creation request."],
}
for key in ('full_name',
'email',
'password'):
if key not in payload:
LOGGER.error(
'[%s] Invalid user creation request, missing %s.' %
(payload['reqid'], key)
)
return {
'success':False,
'user_email':None,
'user_id':None,
'send_verification':False,
'messages':["Invalid user creation request."]
}
#
# validate the email provided
#
# check for Unicode confusables and dangerous usernames
email_confusables_ok = (
validators.validate_confusables_email(payload['email'])
)
# check if the email is a valid one according to HTML5 specs
email_regex_ok = validators.validate_email_address(payload['email'])
# check if the email domain is not a disposable email address
if email_confusables_ok and email_regex_ok:
email_domain = payload['email'].split('@')[1].casefold()
email_domain_not_disposable = (
email_domain not in validators.DISPOSABLE_EMAIL_DOMAINS
)
else:
email_domain_not_disposable = False
# if all of the tests above pass, the email is OK
email_ok = (
email_regex_ok and
email_confusables_ok and
email_domain_not_disposable
)
if not email_ok:
LOGGER.error(
"[%s] User creation request failed for "
"email: %s. "
"The email address provided is not valid." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']))
)
return {
'success':False,
'user_email':None,
'user_id':None,
'send_verification':False,
'messages':["The email address provided doesn't "
"seem to be a valid email address and cannot be used "
"to sign up for an account on this server."]
}
email = validators.normalize_value(payload['email'])
full_name = validators.normalize_value(payload['full_name'])
password = payload['password']
# this checks if the database connection is live
currproc = mp.current_process()
engine = getattr(currproc, 'authdb_engine', None)
if override_authdb_path:
currproc.auth_db_path = override_authdb_path
if not engine:
currproc.authdb_engine, currproc.authdb_conn, currproc.authdb_meta = (
authdb.get_auth_db(
currproc.auth_db_path,
echo=raiseonfail
)
)
users = currproc.authdb_meta.tables['users']
input_password = password[:1024]
# hash the user's password
hashed_password = pass_hasher.hash(input_password)
# validate the input password to see if it's OK
# do this here to make sure the password hash completes at least once
passok, messages = validate_input_password(
full_name,
email,
input_password,
payload['pii_salt'],
min_length=min_pass_length,
max_match_threshold=max_similarity
)
if not passok:
LOGGER.error(
"[%s] User creation request failed for "
"email: %s. "
"The password provided is not secure." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']))
)
return {
'success':False,
'user_email':email,
'user_id':None,
'send_verification':False,
'messages':messages
}
# insert stuff into the user's table, set is_active = False, user_role =
# 'locked', the emailverify_sent_datetime to datetime.utcnow()
try:
# create a system_id for this user
system_id = str(uuid.uuid4())
new_user_dict = {
'full_name':full_name,
'system_id':system_id,
'password':hashed_password,
'email':email,
'email_verified':False,
'is_active':False,
'emailverify_sent_datetime':datetime.utcnow(),
'created_on':datetime.utcnow(),
'user_role':'locked',
'last_updated':datetime.utcnow(),
}
ins = users.insert(new_user_dict)
result = currproc.authdb_conn.execute(ins)
result.close()
user_added = True
# this will catch stuff like people trying to sign up again with their email
# address
except Exception:
user_added = False
# get back the user ID
sel = select([
users.c.email,
users.c.user_id,
users.c.is_active,
users.c.emailverify_sent_datetime,
]).select_from(users).where(
users.c.email == email
)
result = currproc.authdb_conn.execute(sel)
rows = result.fetchone()
result.close()
# if the user was added successfully, tell the frontend all is good and to
# send a verification email
if user_added and rows:
LOGGER.info(
"[%s] User creation request succeeded for "
"email: %s. New user_id: %s" %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(rows['user_id'], payload['pii_salt']))
)
messages.append(
'User account created. Please verify your email address to log in.'
)
return {
'success':True,
'user_email':rows['email'],
'user_id':rows['user_id'],
'send_verification':True,
'messages':messages
}
# if the user wasn't added successfully, then they exist in the DB already
elif (not user_added) and rows:
LOGGER.error(
"[%s] User creation request failed for "
"email: %s. "
"The email provided probably exists in the DB already. " %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']))
)
# check the timedelta between now and the emailverify_sent_datetime
verification_timedelta = (datetime.utcnow() -
rows['emailverify_sent_datetime'])
# this sets whether we should resend the verification email
resend_verification = (
not(rows['is_active']) and
(verification_timedelta > timedelta(hours=24))
)
LOGGER.warning(
'[%s] Existing user_id = %s for new user creation '
'request with email = %s, is_active = %s. '
'Email verification originally sent at = %sZ, '
'will resend verification = %s' %
(payload['reqid'],
pii_hash(rows['user_id'], payload['pii_salt']),
pii_hash(payload['email'], payload['pii_salt']),
rows['is_active'],
rows['emailverify_sent_datetime'].isoformat(),
resend_verification)
)
messages.append(
'User account created. Please verify your email address to log in.'
)
return {
'success':False,
'user_email':rows['email'],
'user_id':rows['user_id'],
'send_verification':resend_verification,
'messages':messages
}
# otherwise, the user wasn't added successfully and they don't already exist
# in the database so something else went wrong.
else:
LOGGER.error(
'[%s] User creation request failed for email: %s. '
'Could not add row to the DB.' %
(payload['reqid'],
pii_hash(rows['user_id'],payload['pii_salt']))
)
messages.append(
'User account created. Please verify your email address to log in.'
)
return {
'success':False,
'user_email':None,
'user_id':None,
'send_verification':False,
'messages':messages
}
[docs]def delete_user(payload,
raiseonfail=False,
override_authdb_path=None):
'''Deletes a user.
This can only be called by the user themselves or the superuser.
This will also immediately invalidate all sessions corresponding to the
target user.
Superuser accounts cannot be deleted.
Parameters
----------
payload : dict
This is a dict with the following required keys:
- email: str
- user_id: int
- password: str
In addition to these items received from an authnzerver client, the
payload must also include the following keys (usually added in by a
wrapping function):
- reqid: int or str
- pii_salt: str
override_authdb_path : str or None
If given as a str, is the alternative path to the auth DB.
raiseonfail : bool
If True, will raise an Exception if something goes wrong.
Returns
-------
dict
Returns a dict containing a success key indicating if the user was
deleted.
'''
for key in ('reqid','pii_salt'):
if key not in payload:
LOGGER.error(
"Missing %s in payload dict. Can't process this request." % key
)
return {
'success':False,
'email':None,
'user_id':None,
'messages':["Invalid user deletion request."],
}
for key in ('email',
'user_id',
'password'):
if key not in payload:
LOGGER.error(
'[%s] Invalid user deletion request, missing %s.' %
(payload['reqid'], key)
)
return {
'success': False,
'user_id':None,
'email':None,
'messages':["Invalid user deletion request."],
}
# this checks if the database connection is live
currproc = mp.current_process()
engine = getattr(currproc, 'authdb_engine', None)
if override_authdb_path:
currproc.auth_db_path = override_authdb_path
if not engine:
currproc.authdb_engine, currproc.authdb_conn, currproc.authdb_meta = (
authdb.get_auth_db(
currproc.auth_db_path,
echo=raiseonfail
)
)
users = currproc.authdb_meta.tables['users']
sessions = currproc.authdb_meta.tables['sessions']
# check if the incoming email address actually belongs to the user making
# the request
sel = select([
users.c.user_id,
users.c.email,
users.c.password,
users.c.user_role
]).select_from(
users
).where(
users.c.user_id == payload['user_id']
).where(
users.c.email == payload['email']
)
result = currproc.authdb_conn.execute(sel)
row = result.fetchone()
if not row or len(row) == 0:
LOGGER.error(
"[%s] User deletion request failed for "
"email: %s, user_id: %s. "
"The email address provided does not match the one on record." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['user_id'],
payload['pii_salt']))
)
return {
'success': False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':["We could not verify your email address or password."]
}
# check if the user's password is valid and matches the one on record
try:
pass_ok = pass_hasher.verify(row['password'],
payload['password'][:1024])
except Exception as e:
LOGGER.error(
"[%s] User deletion request failed for "
"email: %s, user_id: %s. "
"The password provided does not match "
"the one on record. Exception: %s" %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['user_id'],
payload['pii_salt']), e)
)
pass_ok = False
if not pass_ok:
return {
'success': False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':["We could not verify your email address or password."]
}
if row['user_role'] == 'superuser':
LOGGER.error(
"[%s] User deletion request failed for "
"email: %s, user_id: %s. "
"Superusers can't be deleted." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['user_id'],
payload['pii_salt']))
)
return {
'success': False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':["Can't delete superusers."]
}
# delete the user
delete = users.delete().where(
users.c.user_id == payload['user_id']
).where(
users.c.email == payload['email']
).where(
users.c.user_role != 'superuser'
)
result = currproc.authdb_conn.execute(delete)
result.close()
# don't forget to delete the sessions as well
delete = sessions.delete().where(
sessions.c.user_id == payload['user_id']
)
result = currproc.authdb_conn.execute(delete)
result.close()
sel = select([
users.c.user_id,
users.c.email,
sessions.c.session_token
]).select_from(
users.join(sessions)
).where(
users.c.user_id == payload['user_id']
)
result = currproc.authdb_conn.execute(sel)
rows = result.fetchall()
if rows and len(rows) > 0:
LOGGER.error(
"[%s] User deletion request failed for "
"email: %s, user_id: %s. "
"The database rows for this user could not be deleted." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['user_id'],
payload['pii_salt']))
)
return {
'success': False,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':["Could not delete user from DB."]
}
else:
LOGGER.warning(
"[%s] User deletion request succeeded for "
"email: %s, user_id: %s. " %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['user_id'],
payload['pii_salt']))
)
return {
'success': True,
'user_id':payload['user_id'],
'email':payload['email'],
'messages':["User successfully deleted from DB."]
}
[docs]def verify_password_reset(payload,
raiseonfail=False,
override_authdb_path=None,
min_pass_length=12,
max_similarity=30):
'''
Verifies a password reset request.
Parameters
----------
payload : dict
This is a dict with the following required keys:
- email_address: str
- new_password: str
- session_token: str
In addition to these items received from an authnzerver client, the
payload must also include the following keys (usually added in by a
wrapping function):
- reqid: int or str
- pii_salt: str
raiseonfail : bool
If True, will raise an Exception if something goes wrong.
override_authdb_path : str or None
If given as a str, is the alternative path to the auth DB.
min_pass_length : int
The minimum required character length of the password.
max_similarity : int
The maximum UQRatio required to fuzzy-match the input password against
the server's domain name, the user's email, or their name.
Returns
-------
dict
Returns a dict containing a success key indicating if the user's
password was reset.
'''
for key in ('reqid','pii_salt'):
if key not in payload:
LOGGER.error(
"Missing %s in payload dict. Can't process this request." % key
)
return {
'success':False,
'messages':["Invalid password reset request."],
}
for key in ('email_address',
'new_password',
'session_token'):
if key not in payload:
LOGGER.error(
'[%s] Invalid password reset request, missing %s.' %
(payload['reqid'], key)
)
return {
'success':False,
'messages':["Invalid password reset request. "
"Some required parameters are missing."]
}
# this checks if the database connection is live
currproc = mp.current_process()
engine = getattr(currproc, 'authdb_engine', None)
if override_authdb_path:
currproc.auth_db_path = override_authdb_path
if not engine:
currproc.authdb_engine, currproc.authdb_conn, currproc.authdb_meta = (
authdb.get_auth_db(
currproc.auth_db_path,
echo=raiseonfail
)
)
users = currproc.authdb_meta.tables['users']
# check the session
session_info = auth_session_exists(
{'session_token':payload['session_token'],
'pii_salt':payload['pii_salt'],
'reqid':payload['reqid']},
raiseonfail=raiseonfail,
override_authdb_path=override_authdb_path
)
if not session_info['success']:
LOGGER.error(
"[%s] Password reset request failed for "
"email: %s, session_token: %s. "
"Provided session token was not found in the DB or has expired." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['session_token'],
payload['pii_salt']))
)
return {
'success':False,
'messages':([
"Invalid session token for password reset request."
])
}
sel = select([
users.c.user_id,
users.c.full_name,
users.c.email,
users.c.password,
]).select_from(
users
).where(
users.c.email == payload['email_address']
).where(
users.c.is_active.is_(True)
)
result = currproc.authdb_conn.execute(sel)
user_info = result.fetchone()
result.close()
if not user_info or len(user_info) == 0:
LOGGER.error(
"[%s] Password reset request failed for "
"email: %s, session_token: %s. "
"User email was not found in the DB or the user is inactive." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['session_token'],
payload['pii_salt']))
)
return {
'success':False,
'messages':([
"Invalid user for password reset request."
])
}
# let's hash the new password against the current password
new_password = payload['new_password'][:1024]
try:
pass_same = pass_hasher.verify(
user_info['password'],
new_password,
)
except Exception:
pass_same = False
# don't fail here, but note that the user is re-using the password they
# forgot. FIXME: should we actually fail here?
if pass_same:
LOGGER.warning(
"[%s] Password reset request warning for "
"email: %s, session_token: %s, user_id: %s. "
"User is attempting to reuse the password they supposedly forgot." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['session_token'],
payload['pii_salt']),
pii_hash(user_info['user_id'],
payload['pii_salt']))
)
# hash the user's password
hashed_password = pass_hasher.hash(new_password)
# validate the input password to see if it's OK
# do this here to make sure the password hash completes at least once
passok, messages = validate_input_password(
user_info['full_name'],
payload['email_address'],
new_password,
payload['pii_salt'],
min_length=min_pass_length,
max_match_threshold=max_similarity
)
if not passok:
LOGGER.error(
"[%s] Password reset request failed for "
"email: %s, session_token: %s, user_id: %s. "
"The new password is insecure." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['session_token'],
payload['pii_salt']),
pii_hash(user_info['user_id'],
payload['pii_salt']))
)
return {
'success':False,
'messages':([
"Insecure password for password reset request."
] + messages)
}
# if the password passes validation, hash it and store it
else:
# update the table for this user
upd = users.update(
).where(
users.c.user_id == user_info['user_id']
).where(
users.c.is_active.is_(True)
).where(
users.c.email == payload['email_address']
).values({
'password': hashed_password
})
result = currproc.authdb_conn.execute(upd)
sel = select([
users.c.password,
]).select_from(users).where(
(users.c.email == payload['email_address'])
)
result = currproc.authdb_conn.execute(sel)
rows = result.fetchone()
result.close()
if rows and rows['password'] == hashed_password:
LOGGER.info(
"[%s] Password reset request succeeded for "
"email: %s, session_token: %s, user_id: %s. " %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['session_token'],
payload['pii_salt']),
pii_hash(user_info['user_id'],
payload['pii_salt']))
)
messages.append('Password changed successfully.')
return {
'success':True,
'messages':messages
}
else:
LOGGER.error(
"[%s] Password reset request failed for "
"email: %s, session_token: %s, user_id: %s. "
"The database row for the user could not be updated." %
(payload['reqid'],
pii_hash(payload['email'],
payload['pii_salt']),
pii_hash(payload['session_token'],
payload['pii_salt']),
pii_hash(user_info['user_id'],
payload['pii_salt']))
)
messages.append('Password could not be changed.')
return {
'success':False,
'messages':messages
}