cic-internal-integration/apps/cic-ussd/cic_ussd/state_machine/logic/pin.py

139 lines
5.8 KiB
Python
Raw Permalink Normal View History

2021-02-06 16:13:47 +01:00
"""This module defines functions responsible for creation, validation, reset and any other manipulations on the
user's pin.
"""
# standard imports
import logging
import re
from typing import Tuple
# third party imports
2021-06-29 12:49:25 +02:00
from sqlalchemy.orm.session import Session
2021-02-06 16:13:47 +01:00
# local imports
2021-06-29 12:49:25 +02:00
from cic_ussd.db.models.account import Account
from cic_ussd.db.models.base import SessionBase
from cic_ussd.db.enum import AccountStatus
from cic_ussd.encoder import create_password_hash, check_password_hash
2021-11-29 16:04:50 +01:00
from cic_ussd.processor.util import wait_for_session_data
2021-08-06 18:29:01 +02:00
from cic_ussd.session.ussd_session import create_or_update_session, persist_ussd_session
2021-02-06 16:13:47 +01:00
logg = logging.getLogger(__file__)
2021-06-29 12:49:25 +02:00
def is_valid_pin(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-02-06 16:13:47 +01:00
"""This function checks a pin's validity by ensuring it has a length of for characters and the characters are
numeric.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: A pin's validity
:rtype: bool
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
2021-02-06 16:13:47 +01:00
matcher = r'^\d{4}$'
2021-11-29 16:04:50 +01:00
return bool(re.match(matcher, user_input))
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def is_authorized_pin(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-02-06 16:13:47 +01:00
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: A match between two pin values.
:rtype: bool
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
is_verified_password = account.verify_password(password=user_input)
if not is_verified_password:
account.failed_pin_attempts += 1
return is_verified_password
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def is_locked_account(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-02-06 16:13:47 +01:00
"""This function checks whether a user's account is locked due to too many failed attempts.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: A match between two pin values.
:rtype: bool
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
return account.get_status(session) == AccountStatus.LOCKED.name
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, Account, Session]):
2021-02-06 16:13:47 +01:00
"""This function hashes a pin and stores it in session data.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
"""
2021-11-29 16:04:50 +01:00
user_input, ussd_session, account, session = state_machine_data
2021-02-06 16:13:47 +01:00
initial_pin = create_password_hash(user_input)
2021-08-06 18:29:01 +02:00
if ussd_session.get('data'):
data = ussd_session.get('data')
data['initial_pin'] = initial_pin
2021-06-03 15:40:51 +02:00
else:
2021-08-06 18:29:01 +02:00
data = {
2021-06-03 15:40:51 +02:00
'initial_pin': initial_pin
}
2021-08-06 18:29:01 +02:00
external_session_id = ussd_session.get('external_session_id')
2021-02-06 16:13:47 +01:00
create_or_update_session(
external_session_id=external_session_id,
2021-08-06 18:29:01 +02:00
msisdn=ussd_session.get('msisdn'),
service_code=ussd_session.get('service_code'),
2021-02-06 16:13:47 +01:00
user_input=user_input,
2021-08-06 18:29:01 +02:00
state=ussd_session.get('state'),
2021-06-29 12:49:25 +02:00
session=session,
2021-08-06 18:29:01 +02:00
data=data
2021-02-06 16:13:47 +01:00
)
2021-08-06 18:29:01 +02:00
persist_ussd_session(external_session_id, 'cic-ussd')
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def pins_match(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-02-06 16:13:47 +01:00
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: A match between two pin values.
:rtype: bool
"""
2021-11-29 16:04:50 +01:00
user_input, ussd_session, account, session = state_machine_data
wait_for_session_data('Initial pin', session_data_key='initial_pin', ussd_session=ussd_session)
2021-08-06 18:29:01 +02:00
initial_pin = ussd_session.get('data').get('initial_pin')
2021-06-03 15:40:51 +02:00
return check_password_hash(user_input, initial_pin)
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def complete_pin_change(state_machine_data: Tuple[str, dict, Account, Session]):
2021-02-06 16:13:47 +01:00
"""This function persists the user's pin to the database
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
"""
2021-11-29 16:04:50 +01:00
user_input, ussd_session, account, session = state_machine_data
2021-06-29 12:49:25 +02:00
session = SessionBase.bind_session(session=session)
2021-11-29 16:04:50 +01:00
wait_for_session_data('Initial pin', session_data_key='initial_pin', ussd_session=ussd_session)
2021-08-06 18:29:01 +02:00
password_hash = ussd_session.get('data').get('initial_pin')
2021-11-29 16:04:50 +01:00
account.password_hash = password_hash
session.add(account)
2021-06-29 12:49:25 +02:00
session.flush()
SessionBase.release_session(session=session)
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def is_blocked_pin(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-02-06 16:13:47 +01:00
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: A match between two pin values.
:rtype: bool
"""
2021-08-06 18:29:01 +02:00
user_input, ussd_session, account, session = state_machine_data
return account.get_status(session) == AccountStatus.LOCKED.name
2021-02-06 16:13:47 +01:00
2021-06-29 12:49:25 +02:00
def is_valid_new_pin(state_machine_data: Tuple[str, dict, Account, Session]) -> bool:
2021-02-06 16:13:47 +01:00
"""This function checks whether the user's new pin is a valid pin and that it isn't the same as the old one.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: A match between two pin values.
:rtype: bool
"""
2021-11-29 16:04:50 +01:00
user_input, ussd_session, account, session = state_machine_data
is_old_pin = account.verify_password(password=user_input)
2021-02-06 16:13:47 +01:00
return is_valid_pin(state_machine_data=state_machine_data) and not is_old_pin