def test_edit_user(test_client, authed_sempo_admin_user, create_transfer_account_user, user_id_accessor, is_vendor, is_groupaccount, roles, tier, status_code):
    if tier:
        authed_sempo_admin_user.set_held_role('ADMIN', tier)
        auth = get_complete_auth_token(authed_sempo_admin_user)
    else:
        auth = None

    new_phone = fake.msisdn()

    response = test_client.put(
        f"/api/v1/user/{user_id_accessor(create_transfer_account_user)}/",
        headers=dict(
            Authorization=auth,
            Accept='application/json'
        ),
        json={
            'phone': new_phone,
            'account_types': roles
        })

    assert response.status_code == status_code
    if response.status_code == 200:
        data = response.json['data']

        assert isinstance(data['user'], object)
        assert data['user']['phone'] == proccess_phone_number(new_phone)
        assert data['user']['is_vendor'] == is_vendor
        assert data['user']['is_groupaccount'] == is_groupaccount
Beispiel #2
0
def test_proccess_phone_number(proccess_phone_number, phone, region, expected):
    """
    GIVEN proccess_phone_number function
    WHEN called with a phone_number WITHOUT and WITH country code
    THEN check that default country code is added if required
    """
    assert proccess_phone_number(phone, region) == expected
Beispiel #3
0
def find_user_from_public_identifier(*public_identifiers):
    """
    :param public_identifiers: email, phone, public_serial_number, nfc_serial_number or address
    :return: First user found
    """
    user = None
    transfer_card = None

    for public_identifier in list(filter(lambda x: x is not None, public_identifiers)):
        if public_identifier is None:
            continue

        user = User.query.execution_options(show_all=True).filter_by(
            email=str(public_identifier).lower()).first()
        if user:
            break

        try:
            user = User.query.execution_options(show_all=True).filter_by(
                phone=proccess_phone_number(public_identifier)).first()
            if user:
                break
        except NumberParseException:
            pass

        transfer_card = TransferCard.query.execution_options(show_all=True).filter_by(
            public_serial_number=str(public_identifier).lower()).first()
        user = transfer_card and transfer_card.user

        if user:
            break

        transfer_card = TransferCard.query.execution_options(show_all=True).filter_by(
            nfc_serial_number=public_identifier.upper()).first()
        user = transfer_card and transfer_card.user

        if user:
            break

        user = User.query.execution_options(show_all=True).filter_by(
            uuid=public_identifier).first()
        if user:
            break

        try:
            checksummed = to_checksum_address(public_identifier)
            blockchain_address = BlockchainAddress.query.filter_by(
                address=checksummed).first()

            if blockchain_address and blockchain_address.transfer_account:
                user = blockchain_address.transfer_account.primary_user
                if user:
                    break

        except Exception:
            pass

    return user, transfer_card
Beispiel #4
0
def test_create_user(test_client, authed_sempo_admin_user, init_database, create_transfer_account_user,
                     mock_async_set_user_gps_from_location,
                     user_phone_accessor, phone, business_usage_name, referred_by, tier, status_code):

    if tier:
        authed_sempo_admin_user.set_held_role('ADMIN', tier)
        auth = get_complete_auth_token(authed_sempo_admin_user)
    else:
        auth = None

    # create the user who is referring
    create_transfer_account_user.phone = referred_by
    user_phone_accessor(create_transfer_account_user)

    response = test_client.post(
        "/api/v1/user/",
        headers=dict(
            Authorization=auth,
            Accept='application/json'
        ),
        json={
            'first_name': 'John',
            'last_name': 'Smith',
            'bio': 'EasyMart',
            'gender': 'female',
            'phone': phone,
            'is_vendor': False,
            'is_tokenagent': False,
            'is_groupaccount': False,
            'initial_disbursement': 0,
            'location': 'Elwood',
            'business_usage_name': business_usage_name,
            'referred_by': user_phone_accessor(create_transfer_account_user)
        })
    
    assert response.status_code == status_code
    if response.status_code == 200:
        data = response.json['data']
        assert isinstance(data['user'], object)
        assert data['user']['first_name'] == 'John'
        assert data['user']['last_name'] == 'Smith'
        assert data['user']['custom_attributes']['bio'] == 'EasyMart'
        assert data['user']['custom_attributes']['gender'] == 'female'
        assert data['user']['phone'] == proccess_phone_number(phone)
        assert data['user']['is_vendor'] is False
        assert data['user']['is_tokenagent'] is False
        assert data['user']['is_groupaccount'] is False
        assert data['user']['transfer_accounts'][0]['balance'] == 0
        assert data['user']['location'] == 'Elwood'
        assert data['user']['business_usage_id'] == init_database.session.query(TransferUsage)\
            .filter_by(name=business_usage_name).first().id
        assert data['user']['referred_by'] == user_phone_accessor(create_transfer_account_user)

        # Checks that we're calling the gps location fetching job, and passing the right data to it
        # Used in lieu of the test below working
        fn_inputs = mock_async_set_user_gps_from_location
        args, kwargs = fn_inputs[-1]
        assert kwargs == {'user_id': data['user']['id'], 'location': 'Elwood'}
Beispiel #5
0
def get_user_by_phone(phone: str,
                      region: str,
                      should_raise=False) -> Optional[User]:
    user = User.query.execution_options(show_all=True).filter_by(
        phone=proccess_phone_number(phone_number=phone,
                                    region=region)).first()
    if user is not None:
        return user
    else:
        if should_raise:
            raise Exception('User not found.')
        else:
            return None
 def upsell_unregistered_recipient(self, user_input):
     recipient_phone = proccess_phone_number(user_input)
     self.send_sms(
         self.user.phone,
         'upsell_message_sender',
         recipient_phone=recipient_phone,
     )
     self.send_sms(
         recipient_phone,
         'upsell_message_recipient',
         first_name=self.user.first_name,
         last_name=self.user.last_name,
         token_name=default_token(self.user).name
     )
Beispiel #7
0
def find_user_from_public_identifier(*public_identifiers):

    user = None

    for public_identifier in public_identifiers:

        if public_identifier is None:
            continue

        user = models.User.query.filter_by(
            email=str(public_identifier).lower()).first()
        if user:
            continue

        try:
            user = models.User.query.filter_by(
                phone=proccess_phone_number(public_identifier)).first()
            if user:
                continue
        except NumberParseException:
            pass

        user = models.User.query.filter_by(
            public_serial_number=str(public_identifier).lower()).first()
        if user:
            continue

        user = models.User.query.filter_by(
            nfc_serial_number=public_identifier.upper()).first()
        if user:
            continue

        try:
            checksummed = utils.checksum_encode(public_identifier)
            blockchain_address = models.BlockchainAddress.query.filter_by(
                address=checksummed).first()

            if blockchain_address and blockchain_address.transfer_account:
                user = blockchain_address.transfer_account.primary_user
                if user:
                    continue

        except Exception:
            pass

    return user
Beispiel #8
0
    def __init__(self,
                 inbound_phone,
                 inbound_message,
                 message_source=None,
                 provider_message_id=None):

        self.inbound_phone = proccess_phone_number(inbound_phone)
        self.inbound_message = inbound_message.lower()
        self.inbound_user = User.query.filter_by(
            phone=self.inbound_phone).first()

        self.inbound_transfer_account = self.inbound_user.transfer_account

        self.chatbot_state = self.inbound_user.chatbot_state
        self.provider_message_id = provider_message_id

        self.message_source = message_source
Beispiel #9
0
def test_create_user(test_client, authed_sempo_admin_user, init_database,
                     create_transfer_account_user, user_phone_accessor, phone,
                     business_usage_name, referred_by, tier, status_code):
    if tier:
        authed_sempo_admin_user.set_held_role('ADMIN', tier)
        auth = get_complete_auth_token(authed_sempo_admin_user)
    else:
        auth = None

    response = test_client.post(
        "/api/v1/user/",
        headers=dict(Authorization=auth, Accept='application/json'),
        json={
            'first_name': 'John',
            'last_name': 'Smith',
            'bio': 'EasyMart',
            'gender': 'female',
            'phone': phone,
            'is_vendor': False,
            'is_tokenagent': False,
            'is_groupaccount': False,
            'initial_disbursement': 0,
            'location': 'Elwood',
            'business_usage_name': business_usage_name,
            'referred_by':
            user_phone_accessor(create_transfer_account_user
                                )  #create the user who is referring
        })

    assert response.status_code == status_code
    if response.status_code == 200:
        data = response.json['data']
        assert isinstance(data['user'], object)
        assert data['user']['first_name'] == 'John'
        assert data['user']['last_name'] == 'Smith'
        assert data['user']['custom_attributes']['bio'] == 'EasyMart'
        assert data['user']['custom_attributes']['gender'] == 'female'
        assert data['user']['phone'] == proccess_phone_number(phone)
        assert data['user']['is_vendor'] is False
        assert data['user']['is_tokenagent'] is False
        assert data['user']['is_groupaccount'] is False
        assert data['user']['transfer_accounts'][0]['balance'] == 0
        assert data['user']['location'] == 'Elwood'
        assert data['user']['business_usage_id'] == init_database.session.query(TransferUsage)\
            .filter_by(name=business_usage_name).first().id
Beispiel #10
0
def bind_fb_psid_to_account(message, psid):

    if message == "bind demo pin 4563":
        transfer_account = User.query.get(5)

        transfer_account.facebook_psid = str(psid)

        db.session.commit()

        return 'Bound to demo account'

    try:

        index_of_use = message.lower().index('use')

        index_of_pin = message.lower().index('pin')

        phone_number = proccess_phone_number(message[index_of_use +
                                                     3:index_of_pin])

        pin = str(int(message[index_of_pin + 3:]))

    except:
        return 'Please link to your Sempo account first'

    user = User.query.filter_by(phone=phone_number).first()

    if user is None:

        return 'phone number not registered'

    if user.verify_password(pin):

        return 'wrong pin'

    user.facebook_psid = str(psid)

    db.session.commit()

    return 'Sempo account linked'
Beispiel #11
0
    def post(self):

        # get the post data
        post_data = request.get_json()

        old_password = post_data.get('old_password')
        new_password = post_data.get('new_password')
        phone = proccess_phone_number(phone_number=post_data.get('phone'), region=post_data.get('region'))
        one_time_code = post_data.get('one_time_code')

        auth_header = request.headers.get('Authorization')

        # Check authorisation using a one time code
        if phone and one_time_code:
            card = phone[-6:]
            user = (User.query.filter_by(phone=phone).execution_options(show_all=True).first() or
                    User.query.filter_by(public_serial_number=card).execution_options(show_all=True).first()
                    )

            if not user:
                response_object = {
                    'status': 'fail',
                    'message': 'User not found'
                }

                return make_response(jsonify(response_object)), 401

            if user.is_activated:
                response_object = {
                    'status': 'fail',
                    'message': 'Account already activated'
                }

                return make_response(jsonify(response_object)), 401

            if str(one_time_code) != user.one_time_code:
                response_object = {
                    'status': 'fail',
                    'message': 'One time code not valid'
                }

                return make_response(jsonify(response_object)), 401

            user.hash_password(new_password)

            user.is_phone_verified = True
            user.is_activated = True
            user.one_time_code = None

            auth_token = user.encode_auth_token()

            response_object = create_user_response_object(user, auth_token, 'Successfully set pin')

            db.session.commit()

            return make_response(jsonify(response_object)), 200

        # Check authorisation using regular auth
        elif auth_header and auth_header != 'null' and old_password:
            auth_token = auth_header.split(" ")[0]

            resp = User.decode_auth_token(auth_token)

            if isinstance(resp, str):
                response_object = {
                    'status': 'fail',
                    'message': 'Invalid auth token'
                }

                return make_response(jsonify(response_object)), 401

            user = User.query.filter_by(id=resp.get('user_id')).execution_options(show_all=True).first()

            if not user:
                response_object = {
                    'status': 'fail',
                    'message': 'User not found'
                }

                return make_response(jsonify(response_object)), 401

            if not user.verify_password(old_password):
                response_object = {
                    'status': 'fail',
                    'message': 'invalid password'
                }

                return make_response(jsonify(response_object)), 401

        # Check authorisation using a reset token provided via email
        else:

            reset_password_token = post_data.get('reset_password_token')

            if not reset_password_token:
                response_object = {
                    'status': 'fail',
                    'message': 'Missing token.'
                }

                return make_response(jsonify(response_object)), 401

            reset_password_token = reset_password_token.split(" ")[0]

            validity_check = User.decode_single_use_JWS(reset_password_token, 'R')

            if not validity_check['success']:
                response_object = {
                    'status': 'fail',
                    'message': validity_check['message']
                }

                return make_response(jsonify(response_object)), 401

            user = validity_check['user']

            reuse_check = user.check_reset_token_already_used(
                reset_password_token)

            
            if not reuse_check:
                response_object = {
                    'status': 'fail',
                    'message': 'Token already used'
                }

                return make_response(jsonify(response_object)), 401

        if not new_password or len(new_password) < 6:
            response_object = {
                'status': 'fail',
                'message': 'Password must be at least 6 characters long'
            }

            return make_response(jsonify(response_object)), 401

        user.hash_password(new_password)
        user.delete_password_reset_tokens()
        db.session.commit()

        response_object = {
            'status': 'success',
            'message': 'Password changed, please log in'
        }

        return make_response(jsonify(response_object)), 200
Beispiel #12
0
 def phone(self, phone):
     self._phone = proccess_phone_number(phone)
Beispiel #13
0
    def post(self):
        # get the post data

        post_data = request.get_json()

        user = None
        phone = None

        email = post_data.get('username') or post_data.get('email')
        password = post_data.get('password')
        tfa_token = post_data.get('tfa_token')

        # First try to match email
        if email:
            user = User.query.filter_by(email=email).execution_options(show_all=True).first()

        # Now try to match the public serial number (comes in under the phone)
        if not user:
            public_serial_number_or_phone = post_data.get('phone')

            user = User.query.filter_by(public_serial_number=public_serial_number_or_phone).execution_options(
                show_all=True).first()

        # Now try to match the phone
        if not user:
            try:
                phone = proccess_phone_number(post_data.get('phone'), region=post_data.get('region'))
            except NumberParseException as e:
                response_object = {'message': 'Invalid Phone Number: ' + str(e)}
                return make_response(jsonify(response_object)), 401

            if phone:
                user = User.query.filter_by(phone=phone).execution_options(show_all=True).first()

        # mobile user doesn't exist so default to creating a new wallet!
        if user is None and phone:
            # this is a registration from a mobile device THUS a vendor or recipient.
            response_object, response_code = UserUtils.proccess_create_or_modify_user_request(
                dict(phone=phone, deviceInfo=post_data.get('deviceInfo')),
                is_self_sign_up=True,
            )

            if response_code == 200:
                db.session.commit()

            return make_response(jsonify(response_object)), response_code

        if user and user.is_activated and post_data.get('phone') and (password == ''):
            # user already exists, is activated. no password provided, thus request PIN screen.
            # todo: this should check if device exists, if no, resend OTP to verify login is real.
            response_object = {
                'status': 'success',
                'login_with_pin': True,
                'message': 'Login with PIN'
            }
            return make_response(jsonify(response_object)), 200

        if not (email or post_data.get('phone')):
            response_object = {
                'status': 'fail',
                'message': 'No username supplied'
            }
            return make_response(jsonify(response_object)), 401

        if post_data.get('phone') and user and user.one_time_code and not user.is_activated:
            # vendor sign up with one time code or OTP verified
            if user.one_time_code == password:
                response_object = {
                    'status': 'success',
                    'pin_must_be_set': True,
                    'message': 'Please set your pin.'
                }
                return make_response(jsonify(response_object)), 200

            if not user.is_phone_verified:
                if user.is_self_sign_up:
                    # self sign up, resend phone verification code
                    user.set_pin(None, False)  # resets PIN
                    UserUtils.send_one_time_code(phone=phone, user=user)
                db.session.commit()
                response_object = {'message':  'Please verify phone number.', 'otp_verify': True}
                return make_response(jsonify(response_object)), 200

        try:

            if not user or not user.verify_password(password):
                response_object = {
                    'status': 'fail',
                    'message': 'Invalid username or password'
                }

                return make_response(jsonify(response_object)), 401

            if not user.is_activated:
                response_object = {
                    'status': 'fail',
                    'is_activated': False,
                    'message': 'Account has not been activated. Please check your emails.'
                }
                return make_response(jsonify(response_object)), 401

            if post_data.get('deviceInfo'):
                UserUtils.save_device_info(post_data.get('deviceInfo'), user)

            auth_token = user.encode_auth_token()

            if not auth_token:
                response_object = {
                    'status': 'fail',
                    'message': 'Invalid username or password'
                }
                return make_response(jsonify(response_object)), 401

            # Possible Outcomes:
            # TFA required, but not set up
            # TFA enabled, and user does not have valid TFA token
            # TFA enabled, and user has valid TFA token
            # TFA not required

            tfa_response_oject = tfa_logic(user, tfa_token)
            if tfa_response_oject:
                tfa_response_oject['auth_token'] = auth_token.decode()

                return make_response(jsonify(tfa_response_oject)), 401

            # Update the last_seen TS for this user
            user.update_last_seen_ts()

            response_object = create_user_response_object(user, auth_token, 'Successfully logged in.')

            db.session.commit()

            return make_response(jsonify(response_object)), 200

        except Exception as e:
            sentry_sdk.capture_exception(e)
            raise e
Beispiel #14
0
def proccess_create_or_modify_user_request(attribute_dict,
                                           organisation=None,
                                           allow_existing_user_modify=False,
                                           is_self_sign_up=False,
                                           modify_only=False):
    """
    Takes a create or modify user request and determines the response. Normally what's in the top level API function,
    but here it's one layer down because there's multiple entry points for 'create user':
    - The admin api
    - The register api

    :param attribute_dict: attributes that can be supplied by the request maker
    :param organisation:  what organisation the request maker belongs to. The created user is bound to the same org
    :param allow_existing_user_modify: whether to return an error when the user already exists for the supplied IDs
    :param is_self_sign_up: does the request come from the register api?
    :param modify_only: whether to allow the creation of a  new user
    :return: An http response
    """

    if not attribute_dict.get('custom_attributes'):
        attribute_dict['custom_attributes'] = {}

    user_id = attribute_dict.get('user_id')

    email = attribute_dict.get('email')
    phone = attribute_dict.get('phone')

    account_types = attribute_dict.get('account_types', [])
    if isinstance(account_types, str):
        account_types = account_types.split(',')

    referred_by = attribute_dict.get('referred_by')

    blockchain_address = attribute_dict.get('blockchain_address')

    provided_public_serial_number = attribute_dict.get('public_serial_number')

    uuid = attribute_dict.get('uuid')

    require_identifier = attribute_dict.get('require_identifier', True)

    if not user_id:
        # Extract ID from Combined User ID and Name String if it exists
        try:
            user_id_name_string = attribute_dict.get('user_id_name_string')

            user_id_str = user_id_name_string and user_id_name_string.split(
                ':')[0]

            if user_id_str:
                user_id = int(user_id_str)

        except SyntaxError:
            pass

    if not blockchain_address and provided_public_serial_number:

        try:
            blockchain_address = to_checksum_address(
                provided_public_serial_number)

            # Since it's actually an ethereum address set the provided public serial number to None
            # so it doesn't get used as a transfer card
            provided_public_serial_number = None
        except Exception:
            pass

    require_transfer_card_exists = attribute_dict.get(
        'require_transfer_card_exists',
        g.active_organisation.require_transfer_card)

    public_serial_number = (provided_public_serial_number
                            or attribute_dict.get('payment_card_qr_code')
                            or attribute_dict.get('payment_card_barcode'))

    location = attribute_dict.get('location')  # address location

    # Yes, we know "GPS" refers to a technology, but "gps_location" is less ambiguous for end users than "geo_location"
    gps_location = attribute_dict.get(
        'gps_location')  # geo location as str of lat, lng

    use_precreated_pin = attribute_dict.get('use_precreated_pin')
    use_last_4_digits_of_id_as_initial_pin = attribute_dict.get(
        'use_last_4_digits_of_id_as_initial_pin')

    transfer_account_name = attribute_dict.get('transfer_account_name')
    first_name = attribute_dict.get('first_name')
    last_name = attribute_dict.get('last_name')

    business_usage_name = attribute_dict.get('business_usage_name')
    business_usage_id = None
    if business_usage_name:
        usage = TransferUsage.find_or_create(business_usage_name)
        business_usage_id = usage.id

    preferred_language = attribute_dict.get('preferred_language')

    primary_user_identifier = attribute_dict.get('primary_user_identifier')
    primary_user_pin = attribute_dict.get('primary_user_pin')

    initial_disbursement = attribute_dict.get('initial_disbursement', None)
    if not account_types:
        account_types = ['beneficiary']
    roles_to_set = []
    for at in account_types:
        if at not in g.active_organisation.valid_roles:
            raise Exception(
                f'{at} not a valid role for this organisation. Please choose one of the following: {g.active_organisation.valid_roles}'
            )
        roles_to_set.append((ASSIGNABLE_TIERS[at], at))

    chain = get_chain()
    if current_app.config['CHAINS'][chain]['IS_USING_BITCOIN']:
        try:
            base58.b58decode_check(blockchain_address)
        except ValueError:
            response_object = {
                'message':
                'Blockchain Address {} Not Valid'.format(blockchain_address)
            }
            return response_object, 400

    if isinstance(phone, bool):
        phone = None

    if phone and not is_self_sign_up:
        # phone has already been parsed if self sign up
        try:
            phone = proccess_phone_number(phone)
        except NumberParseException as e:
            response_object = {'message': 'Invalid Phone Number: ' + str(e)}
            return response_object, 400

    # Work out if there's an existing transfer account to bind to
    existing_transfer_account = None
    if primary_user_identifier:
        primary_user, _ = find_user_from_public_identifier(
            primary_user_identifier)

        if not primary_user or not primary_user.verify_password(
                primary_user_pin):
            response_object = {'message': 'Primary User not Found'}
            return response_object, 400

        if not primary_user.verify_password(primary_user_pin):
            response_object = {'message': 'Invalid PIN for Primary User'}
            return response_object, 400

        primary_user_transfer_account = primary_user.transfer_account

        if not primary_user_transfer_account:
            response_object = {
                'message': 'Primary User has no transfer account'
            }
            return response_object, 400

    if not (phone or email or public_serial_number or blockchain_address
            or user_id or uuid or not require_identifier):
        response_object = {'message': 'Must provide a unique identifier'}
        return response_object, 400

    if use_precreated_pin and not public_serial_number:
        response_object = {
            'message':
            'Must provide public serial number to use a transfer card or pre-created pin'
        }
        return response_object, 400

    if public_serial_number:
        public_serial_number = str(public_serial_number)

        if use_precreated_pin or require_transfer_card_exists:
            transfer_card = TransferCard.query.filter_by(
                public_serial_number=public_serial_number).first()

            if not transfer_card:
                response_object = {'message': 'Transfer card not found'}
                return response_object, 400

    business_usage = None
    if business_usage_id:
        business_usage = TransferUsage.query.get(business_usage_id)
        if not business_usage:
            response_object = {
                'message':
                f'Business Usage not found for id {business_usage_id}'
            }
            return response_object, 400

    referred_by_user, _ = find_user_from_public_identifier(referred_by)

    if referred_by and not referred_by_user:
        response_object = {
            'message':
            f'Referrer user not found for public identifier {referred_by}'
        }
        return response_object, 400

    existing_user, _ = find_user_from_public_identifier(
        email, phone, public_serial_number, blockchain_address, uuid)

    if not existing_user and user_id:
        existing_user = User.query.get(user_id)

    if modify_only and existing_user is None:
        response_object = {'message': 'User not found'}
        return response_object, 404

    if existing_user:
        if not allow_existing_user_modify:
            response_object = {'message': 'User already exists for Identifier'}
            return response_object, 400

        try:

            user = update_transfer_account_user(
                existing_user,
                first_name=first_name,
                last_name=last_name,
                preferred_language=preferred_language,
                phone=phone,
                email=email,
                public_serial_number=public_serial_number,
                use_precreated_pin=use_precreated_pin,
                existing_transfer_account=existing_transfer_account,
                roles=roles_to_set,
                business_usage=business_usage)

            set_location_conditionally(user, location, gps_location)

            if referred_by_user:
                user.referred_by.clear(
                )  # otherwise prior referrals will remain...
                user.referred_by.append(referred_by_user)

            set_custom_attributes(attribute_dict, user)
            flag_modified(user, "custom_attributes")

            db.session.commit()

            response_object = {
                'message': 'User Updated',
                'data': {
                    'user': user_schema.dump(user).data
                }
            }

            return response_object, 200

        except Exception as e:
            response_object = {'message': str(e)}

            return response_object, 400

    user = create_transfer_account_user(
        first_name=first_name,
        last_name=last_name,
        preferred_language=preferred_language,
        phone=phone,
        email=email,
        public_serial_number=public_serial_number,
        uuid=uuid,
        organisation=organisation,
        blockchain_address=blockchain_address,
        transfer_account_name=transfer_account_name,
        use_precreated_pin=use_precreated_pin,
        use_last_4_digits_of_id_as_initial_pin=
        use_last_4_digits_of_id_as_initial_pin,
        existing_transfer_account=existing_transfer_account,
        roles=roles_to_set,
        is_self_sign_up=is_self_sign_up,
        business_usage=business_usage,
        initial_disbursement=initial_disbursement)

    set_location_conditionally(user, location, gps_location)

    if referred_by_user:
        user.referred_by.append(referred_by_user)

    if attribute_dict.get('gender'):
        attribute_dict['custom_attributes']['gender'] = attribute_dict.get(
            'gender')

    if attribute_dict.get('bio'):
        attribute_dict['custom_attributes']['bio'] = attribute_dict.get('bio')

    set_custom_attributes(attribute_dict, user)

    if is_self_sign_up and attribute_dict.get('deviceInfo', None) is not None:
        save_device_info(device_info=attribute_dict.get('deviceInfo'),
                         user=user)
    send_onboarding_sms_messages(user)
    # Location fires an async task that needs to know user ID
    db.session.flush()

    if phone:
        if is_self_sign_up:
            send_one_time_code(phone=phone, user=user)
            return {
                'message': 'User Created. Please verify phone number.',
                'otp_verify': True
            }, 200

        elif current_app.config['ONBOARDING_SMS']:
            try:
                send_onboarding_sms_messages(user)
            except Exception as e:
                print(e)
                sentry_sdk.capture_exception(e)
                pass

    response_object = {
        'message': 'User Created',
        'data': {
            'user': user_schema.dump(user).data
        }
    }

    return response_object, 200
Beispiel #15
0
    def post(self):
        # get the post data

        post_data = request.get_json()

        user = None

        email = post_data.get('username') or post_data.get('email')
        password = post_data.get('password')
        tfa_token = post_data.get('tfa_token')

        # First try to match email
        if email:
            user = User.query.filter_by(email=email).first()

        #Now try to match the public serial number (comes in under the phone)
        if not user:
            public_serial_number_or_phone = post_data.get('phone')

            user = User.query.filter_by(
                public_serial_number=public_serial_number_or_phone).first()

        #Now try to match the phone
        if not user:
            phone = proccess_phone_number(post_data.get('phone'))

            if phone:

                user = User.query.filter_by(phone=phone).first()

        if not (email or post_data.get('phone')):
            responseObject = {
                'status': 'fail',
                'message': 'No username supplied'
            }
            return make_response(jsonify(responseObject)), 401

        if post_data.get(
                'phone'
        ) and user and user.one_time_code and not user.is_activated:
            if user.one_time_code == password:
                responseObject = {
                    'status': 'success',
                    'pin_must_be_set': True,
                    'message': 'Please set your pin.'
                }
                return make_response(jsonify(responseObject)), 200

        try:

            if not user or not user.verify_password(post_data.get('password')):

                responseObject = {
                    'status': 'fail',
                    'message': 'Invalid username or password'
                }

                return make_response(jsonify(responseObject)), 401

            if not user.is_activated:

                responseObject = {
                    'status':
                    'fail',
                    'is_activated':
                    False,
                    'message':
                    'Account has not been activated. Please check your emails.'
                }
                return make_response(jsonify(responseObject)), 401

            if post_data.get('deviceInfo'):

                save_device_info(post_data.get('deviceInfo'), user)

                db.session.commit()

            auth_token = user.encode_auth_token()

            if not auth_token:

                responseObject = {
                    'status': 'fail',
                    'message': 'Invalid username or password'
                }
                return make_response(jsonify(responseObject)), 401

            # Possible Outcomes:
            # TFA required, but not set up
            # TFA enabled, and user does not have valid TFA token
            # TFA enabled, and user has valid TFA token
            # TFA not required

            tfa_response_oject = tfa_logic(user, tfa_token)
            if tfa_response_oject:

                tfa_response_oject['auth_token'] = auth_token.decode()

                return make_response(jsonify(tfa_response_oject)), 401

            #Update the last_seen TS for this user
            user.update_last_seen_ts()

            responseObject = create_user_response_object(
                user, auth_token, 'Successfully logged in.')

            return make_response(jsonify(responseObject)), 200

        except Exception as e:

            sentry.captureException()

            raise e
Beispiel #16
0
def proccess_create_or_modify_user_request(
    attribute_dict,
    organisation=None,
    allow_existing_user_modify=False,
    is_self_sign_up=False,
    modify_only=False,
):
    """
    Takes a create or modify user request and determines the response. Normally what's in the top level API function,
    but here it's one layer down because there's multiple entry points for 'create user':
    - The admin api
    - The register api

    :param attribute_dict: attributes that can be supplied by the request maker
    :param organisation:  what organisation the request maker belongs to. The created user is bound to the same org
    :param allow_existing_user_modify: whether to return and error when the user already exists for the supplied IDs
    :param is_self_sign_up: does the request come from the register api?
    :return: An http response
    """

    if not attribute_dict.get('custom_attributes'):
        attribute_dict['custom_attributes'] = {}

    user_id = attribute_dict.get('user_id')

    email = attribute_dict.get('email')
    phone = attribute_dict.get('phone')

    referred_by = attribute_dict.get('referred_by')

    blockchain_address = attribute_dict.get('blockchain_address')

    provided_public_serial_number = attribute_dict.get('public_serial_number')

    if not blockchain_address and provided_public_serial_number:

        try:
            blockchain_address = to_checksum_address(
                provided_public_serial_number)

            # Since it's actually an ethereum address set the provided public serial number to None
            # so it doesn't get used as a transfer card
            provided_public_serial_number = None
        except Exception:
            pass

    require_transfer_card_exists = attribute_dict.get(
        'require_transfer_card_exists',
        g.active_organisation.require_transfer_card)

    public_serial_number = (provided_public_serial_number
                            or attribute_dict.get('payment_card_qr_code')
                            or attribute_dict.get('payment_card_barcode'))

    location = attribute_dict.get('location')  # address location
    geo_location = attribute_dict.get(
        'geo_location')  # geo location as str of lat, lng

    if geo_location:
        geo = geo_location.split(' ')
        lat = geo[0]
        lng = geo[1]
    else:
        # TODO: Work out how this passed tests when this wasn't definied properly!?!
        lat = None
        lng = None

    use_precreated_pin = attribute_dict.get('use_precreated_pin')
    use_last_4_digits_of_id_as_initial_pin = attribute_dict.get(
        'use_last_4_digits_of_id_as_initial_pin')

    transfer_account_name = attribute_dict.get('transfer_account_name')
    first_name = attribute_dict.get('first_name')
    last_name = attribute_dict.get('last_name')

    business_usage_name = attribute_dict.get('business_usage_name')
    business_usage_id = None
    if business_usage_name:
        usage = TransferUsage.find_or_create(business_usage_name)
        business_usage_id = usage.id

    preferred_language = attribute_dict.get('preferred_language')

    primary_user_identifier = attribute_dict.get('primary_user_identifier')
    primary_user_pin = attribute_dict.get('primary_user_pin')

    initial_disbursement = attribute_dict.get('initial_disbursement', None)

    is_vendor = attribute_dict.get('is_vendor', None)
    if is_vendor is None:
        is_vendor = attribute_dict.get('vendor', False)

    is_tokenagent = attribute_dict.get('is_tokenagent', False)
    is_groupaccount = attribute_dict.get('is_groupaccount', False)

    # is_beneficiary defaults to the opposite of is_vendor
    is_beneficiary = attribute_dict.get(
        'is_beneficiary', not is_vendor and not is_tokenagent
        and not is_groupaccount)

    if current_app.config['IS_USING_BITCOIN']:
        try:
            base58.b58decode_check(blockchain_address)
        except ValueError:
            response_object = {
                'message':
                'Blockchain Address {} Not Valid'.format(blockchain_address)
            }
            return response_object, 400

    if isinstance(phone, bool):
        phone = None

    if phone and not is_self_sign_up:
        # phone has already been parsed if self sign up
        try:
            phone = proccess_phone_number(phone)
        except NumberParseException as e:
            response_object = {'message': 'Invalid Phone Number: ' + str(e)}
            return response_object, 400

    # Work out if there's an existing transfer account to bind to
    existing_transfer_account = None
    if primary_user_identifier:
        primary_user = find_user_from_public_identifier(
            primary_user_identifier)

        if not primary_user or not primary_user.verify_password(
                primary_user_pin):
            response_object = {'message': 'Primary User not Found'}
            return response_object, 400

        if not primary_user.verify_password(primary_user_pin):
            response_object = {'message': 'Invalid PIN for Primary User'}
            return response_object, 400

        primary_user_transfer_account = primary_user.transfer_account

        if not primary_user_transfer_account:
            response_object = {
                'message': 'Primary User has no transfer account'
            }
            return response_object, 400

    if not (phone or email or public_serial_number or blockchain_address):
        response_object = {'message': 'Must provide a unique identifier'}
        return response_object, 400

    if use_precreated_pin and not public_serial_number:
        response_object = {
            'message':
            'Must provide public serial number to use a transfer card or pre-created pin'
        }
        return response_object, 400

    if public_serial_number:
        public_serial_number = str(public_serial_number)

        if use_precreated_pin or require_transfer_card_exists:
            transfer_card = TransferCard.query.filter_by(
                public_serial_number=public_serial_number).first()

            if not transfer_card:
                response_object = {'message': 'Transfer card not found'}
                return response_object, 400

    business_usage = None
    if business_usage_id:
        business_usage = TransferUsage.query.get(business_usage_id)
        if not business_usage:
            response_object = {
                'message':
                f'Business Usage not found for id {business_usage_id}'
            }
            return response_object, 400

    referred_by_user = find_user_from_public_identifier(referred_by)

    if referred_by and not referred_by_user:
        response_object = {
            'message':
            f'Referrer user not found for public identifier {referred_by}'
        }
        return response_object, 400

    existing_user = find_user_from_public_identifier(email, phone,
                                                     public_serial_number,
                                                     blockchain_address)

    if modify_only:
        existing_user = User.query.get(user_id)

    if modify_only and existing_user is None:
        response_object = {'message': 'User not found'}
        return response_object, 404

    if existing_user:
        if not allow_existing_user_modify:
            response_object = {'message': 'User already exists for Identifier'}
            return response_object, 400

        try:

            user = update_transfer_account_user(
                existing_user,
                first_name=first_name,
                last_name=last_name,
                preferred_language=preferred_language,
                phone=phone,
                email=email,
                location=location,
                public_serial_number=public_serial_number,
                use_precreated_pin=use_precreated_pin,
                existing_transfer_account=existing_transfer_account,
                is_beneficiary=is_beneficiary,
                is_vendor=is_vendor,
                is_tokenagent=is_tokenagent,
                is_groupaccount=is_groupaccount,
                business_usage=business_usage)

            if referred_by_user:
                user.referred_by.clear(
                )  # otherwise prior referrals will remain...
                user.referred_by.append(referred_by_user)

            set_custom_attributes(attribute_dict, user)
            flag_modified(user, "custom_attributes")

            db.session.commit()

            response_object = {
                'message': 'User Updated',
                'data': {
                    'user': user_schema.dump(user).data
                }
            }

            return response_object, 200

        except Exception as e:
            response_object = {'message': str(e)}

            return response_object, 400

    user = create_transfer_account_user(
        first_name=first_name,
        last_name=last_name,
        preferred_language=preferred_language,
        phone=phone,
        email=email,
        public_serial_number=public_serial_number,
        organisation=organisation,
        blockchain_address=blockchain_address,
        transfer_account_name=transfer_account_name,
        lat=lat,
        lng=lng,
        use_precreated_pin=use_precreated_pin,
        use_last_4_digits_of_id_as_initial_pin=
        use_last_4_digits_of_id_as_initial_pin,
        existing_transfer_account=existing_transfer_account,
        is_beneficiary=is_beneficiary,
        is_vendor=is_vendor,
        is_tokenagent=is_tokenagent,
        is_groupaccount=is_groupaccount,
        is_self_sign_up=is_self_sign_up,
        business_usage=business_usage,
        initial_disbursement=initial_disbursement)

    if referred_by_user:
        user.referred_by.append(referred_by_user)

    if attribute_dict.get('gender'):
        attribute_dict['custom_attributes']['gender'] = attribute_dict.get(
            'gender')

    if attribute_dict.get('bio'):
        attribute_dict['custom_attributes']['bio'] = attribute_dict.get('bio')

    set_custom_attributes(attribute_dict, user)

    if is_self_sign_up and attribute_dict.get('deviceInfo', None) is not None:
        save_device_info(device_info=attribute_dict.get('deviceInfo'),
                         user=user)
    send_onboarding_sms_messages(user)
    # Location fires an async task that needs to know user ID
    db.session.flush()

    if location:
        user.location = location

    if phone:
        if is_self_sign_up:
            send_one_time_code(phone=phone, user=user)
            return {
                'message': 'User Created. Please verify phone number.',
                'otp_verify': True
            }, 200

        elif current_app.config['ONBOARDING_SMS']:
            try:
                send_onboarding_sms_messages(user)
            except Exception as e:
                print(e)
                sentry_sdk.capture_exception(e)
                pass

    response_object = {
        'message': 'User Created',
        'data': {
            'user': user_schema.dump(user).data
        }
    }

    return response_object, 200
def test_create_user(test_client, authed_sempo_admin_user, init_database, create_transfer_account_user,
                     mock_async_set_user_gps_from_location, user_phone_accessor, phone, use_card,
                     business_usage_name, referred_by, gps_location, initial_disbursement, tier, status_code):

    if tier:
        authed_sempo_admin_user.set_held_role('ADMIN', tier)
        auth = get_complete_auth_token(authed_sempo_admin_user)
    else:
        auth = None

    # create the user who is referring
    create_transfer_account_user.phone = referred_by
    user_phone_accessor(create_transfer_account_user)

    payload = {
            'first_name': 'John',
            'last_name': 'Smith',
            'bio': 'EasyMart',
            'gender': 'female',
            'phone': phone,
            'initial_disbursement': initial_disbursement,
            'location': 'Elwood',
            'business_usage_name': business_usage_name,
            'referred_by': user_phone_accessor(create_transfer_account_user)
        }

    if gps_location:
        payload['gps_location'] = gps_location

    if use_card:
        public_serial_number = f'{randint(0,999999):06}'
        new_card = TransferCard(public_serial_number=public_serial_number)

        init_database.session.add(new_card)

        init_database.session.commit()

        payload['public_serial_number'] = public_serial_number

    response = test_client.post(
        "/api/v1/user/",
        headers=dict(
            Authorization=auth,
            Accept='application/json'
        ),
        json=payload)
    
    assert response.status_code == status_code
    if response.status_code == 200:
        data = response.json['data']
        assert isinstance(data['user'], object)
        assert data['user']['first_name'] == 'John'
        assert data['user']['last_name'] == 'Smith'
        assert data['user']['custom_attributes']['bio'] == 'EasyMart'
        assert data['user']['custom_attributes']['gender'] == 'female'
        assert data['user']['phone'] == proccess_phone_number(phone)
        assert data['user']['is_vendor'] is False
        assert data['user']['is_tokenagent'] is False
        assert data['user']['is_groupaccount'] is False
        assert data['user']['location'] == 'Elwood'
        assert data['user']['business_usage_id'] == init_database.session.query(TransferUsage)\
            .filter_by(name=business_usage_name).first().id
        assert data['user']['referred_by'] == user_phone_accessor(create_transfer_account_user)

        if initial_disbursement is not None:
            assert data['user']['transfer_accounts'][0]['balance'] == initial_disbursement
        else:
            db_user = init_database.session.query(User).get(data['user']['id'])
            assert data['user']['transfer_accounts'][0]['balance'] == db_user.default_organisation.default_disbursement

        # Checks that we're calling the gps location fetching job, and passing the right data to it
        fn_inputs = mock_async_set_user_gps_from_location

        if gps_location:
            assert data['user']['lat'] == 12.02
            assert data['user']['lng'] == -15.04
            assert len(fn_inputs) == 0

        else:
            args, kwargs = fn_inputs[-1]
            assert kwargs == {'user_id': data['user']['id'], 'location': 'Elwood'}
Beispiel #18
0
    def insert_user(self, ge_user):

        phone_number = None if 'DELETED' in ge_user['phone'] else ge_user[
            'phone']

        if not phone_number:
            print("Phone Deleted, Skipping")
            return

        if ge_user['status'] == 'Deleted':
            print("User Deleted, Skipping")
            return

        processed_phone = proccess_phone_number(phone_number)
        existing_user = User.query.filter_by(
            phone=processed_phone).execution_options(show_all=True).first()
        if existing_user:
            print(f'User already exists for phone {processed_phone}')
            return

        business_usage = None
        if ge_user.get('business_type') is not None:

            sempo_category = GE_BUSINESS_CATEGORY_MAPPINGS.get(
                ge_user['business_type'])

            if sempo_category:
                business_usage = TransferUsage.query.filter_by(
                    name=sempo_category).first()

        organsation = db.session.query(Organisation).get(
            self.sempo_organisation_id)

        try:
            sempo_user = create_transfer_account_user(
                first_name=ge_user['first_name'],
                last_name=ge_user['last_name'],
                organisation=organsation,
                phone=phone_number,
                preferred_language=ge_user['preferred_language'],
                location=ge_user['location'],
                business_usage=business_usage)

            sempo_user.pin_hash = ge_user['encrypted_pin']
            sempo_user.is_activated = ge_user[
                'status'] == 'Active'  # Is this the correct way to find this out?
            sempo_user.default_transfer_account.is_approved = True
            sempo_user.is_disabled = False
            sempo_user.is_phone_verified = True
            sempo_user.is_self_sign_up = False
            sempo_user.terms_accepted = False
            sempo_user.created = ge_user['created_at']
            sempo_user.is_market_enabled = int(ge_user['market_enabled']) == 1
            sempo_user.custom_attributes = self.create_custom_attributes(
                ge_user)

            if ge_user['token_agents.id'] is not None:
                sempo_user.set_held_role('TOKEN_AGENT',
                                         'grassroots_token_agent')
            else:
                # Is this the correct way to find this out or can a benificiary also be a token agent
                # Or is there some field where you can find this out?
                sempo_user.set_held_role('BENEFICIARY', 'beneficiary')

            if ge_user['group_accounts.id'] is not None:
                sempo_user.set_held_role('GROUP_ACCOUNT',
                                         'grassroots_group_account')

            db.session.flush()

            return sempo_user

        except (IntegrityError, InvalidRequestError) as e:
            print(e)
            db.session().rollback()
Beispiel #19
0
    def post(self):
        # There is an unique case where users are using their mobile number from the App to either login or register
        # The app uses g.active_organisation to reference user.transfer_account to send an SMS to the User.
        # This means that g.active_organisation should default to the master_organisation
        # For admin users, it doesn't matter as this endpoint is unauthed.
        g.active_organisation = Organisation.master_organisation()

        post_data = request.get_json()
        user = None
        phone = None
        email = post_data.get('username', '') or post_data.get('email', '')
        email = email.lower() if email else ''
        password = post_data.get('password')
        # Default pin to password as fallback for old android versions
        pin = post_data.get('pin', password)
        tfa_token = post_data.get('tfa_token')

        password_empty = password == '' or password is None
        pin_empty = pin == '' or pin is None

        ratelimit_key = email or post_data.get('phone')
        if ratelimit_key:
            limit = rate_limit("login_"+ratelimit_key, 25)
            if limit:
                response_object = {
                    'status': 'fail',
                    'message': f'Please try again in {limit} minutes'
                }
                return make_response(jsonify(response_object)), 403

        # First try to match email
        if email:
            user = User.query.filter(func.lower(User.email)==email).execution_options(show_all=True).first()

        # Now try to match the public serial number (comes in under the phone)
        if not user:
            public_serial_number_or_phone = post_data.get('phone')

            user = User.query.filter_by(public_serial_number=public_serial_number_or_phone).execution_options(
                show_all=True).first()

        # Now try to match the phone
        if not user:
            try:
                phone = proccess_phone_number(post_data.get('phone'), region=post_data.get('region'))
            except NumberParseException as e:
                response_object = {'message': 'Invalid Phone Number: ' + str(e)}
                return make_response(jsonify(response_object)), 401

            if phone:
                user = User.query.filter_by(phone=phone).execution_options(show_all=True).first()

        # mobile user doesn't exist so default to creating a new wallet!
        if user is None and phone and current_app.config['ALLOW_SELF_SIGN_UP']:
            # this is a registration from a mobile device THUS a vendor or recipient.
            response_object, response_code = UserUtils.proccess_create_or_modify_user_request(
                dict(phone=phone, deviceInfo=post_data.get('deviceInfo')),
                is_self_sign_up=True,
            )

            if response_code == 200:
                db.session.commit()

            return make_response(jsonify(response_object)), response_code
        no_password_or_pin_hash = user and not user.password_hash and not user.pin_hash
        if post_data.get('phone') and user and user.one_time_code and (not user.is_activated or not user.pin_hash):
            # vendor sign up with one time code or OTP verified
            if user.one_time_code == pin:
                response_object = {
                    'status': 'success',
                    'pin_must_be_set': True,
                    'message': 'Please set your pin.'
                }
                return make_response(jsonify(attach_host(response_object))), 200

            if not user.is_phone_verified or no_password_or_pin_hash:
                if user.is_self_sign_up:
                    # self sign up, resend phone verification code
                    user.set_pin(None, False)  # resets PIN
                    UserUtils.send_one_time_code(phone=phone, user=user)
                db.session.commit()

                if not password_empty:
                    # The user provided a password, so probably not going through incremental login
                    # This is a hacky way to get past the incremental-login multi-org split
                    response_object = {
                        'status': 'fail',
                        'otp_verify': True,
                        'message': 'Please verify phone number.',
                        'error_message': 'Incorrect One Time Code.'
                    }
                    return make_response(jsonify(attach_host(response_object))), 200

                response_object = {'message':  'Please verify phone number.', 'otp_verify': True}
                return make_response(jsonify(attach_host(response_object))), 200

        if user and user.is_activated and post_data.get('phone') and (password_empty and pin_empty):
            # user already exists, is activated. no password or pin provided, thus request PIN screen.
            # todo: this should check if device exists, if no, resend OTP to verify login is real.
            response_object = {
                'status': 'success',
                'login_with_pin': True,
                'message': 'Login with PIN'
            }
            return make_response(jsonify(attach_host(response_object))), 200

        if not (email or post_data.get('phone')):
            response_object = {
                'status': 'fail',
                'message': 'No username supplied'
            }
            return make_response(jsonify(response_object)), 401
    

        try:
            if not (user and (pin and user.verify_pin(pin) or password and user.verify_password(password))):
                response_object = {
                    'status': 'fail',
                    'message': 'Invalid username or password'
                }

                return make_response(jsonify(response_object)), 401

            if not user.is_activated:
                response_object = {
                    'status': 'fail',
                    'is_activated': False,
                    'message': 'Account has not been activated. Please check your emails.'
                }
                return make_response(jsonify(response_object)), 401
            if post_data.get('deviceInfo'):
                deviceInfo = post_data.get('deviceInfo')
                UserUtils.save_device_info(deviceInfo, user)

            auth_token = user.encode_auth_token()

            if not auth_token:
                response_object = {
                    'status': 'fail',
                    'message': 'Invalid username or password'
                }
                return make_response(jsonify(response_object)), 401

            # Possible Outcomes:
            # TFA required, but not set up
            # TFA enabled, and user does not have valid TFA token
            # TFA enabled, and user has valid TFA token
            # TFA not required

            tfa_response_oject = tfa_logic(user, tfa_token)
            if tfa_response_oject:
                tfa_response_oject['auth_token'] = auth_token.decode()

                return make_response(jsonify(tfa_response_oject)), 401

            # Update the last_seen TS for this user
            user.update_last_seen_ts()

            response_object = create_user_response_object(user, auth_token, 'Successfully logged in.')

            db.session.commit()

            return make_response(jsonify(attach_host(response_object))), 200

        except Exception as e:
            sentry_sdk.capture_exception(e)
            raise e
Beispiel #20
0
def proccess_attribute_dict(attribute_dict,
                            force_dict_keys_lowercase=False,
                            allow_existing_user_modify=False,
                            require_transfer_card_exists=False):
    elapsed_time('1.0 Start')

    if force_dict_keys_lowercase:
        attribute_dict = force_attribute_dict_keys_to_lowercase(attribute_dict)

    attribute_dict = strip_kobo_preslashes(attribute_dict)

    attribute_dict = apply_settings(attribute_dict)

    attribute_dict = truthy_all_dict_values(attribute_dict)

    attribute_dict = strip_whitespace_characters(attribute_dict)

    elapsed_time('2.0 Post Processing')

    email = attribute_dict.get('email')
    phone = attribute_dict.get('phone')

    blockchain_address = attribute_dict.get('blockchain_address')

    provided_public_serial_number = attribute_dict.get('public_serial_number')

    if not blockchain_address and provided_public_serial_number:

        try:
            blockchain_address = utils.checksum_encode(
                provided_public_serial_number)

            # Since it's actually an ethereum address set the provided public serial number to None
            # so it doesn't get used as a transfer card
            provided_public_serial_number = None
        except Exception:
            pass

    public_serial_number = (provided_public_serial_number
                            or attribute_dict.get('payment_card_qr_code')
                            or attribute_dict.get('payment_card_barcode'))

    location = attribute_dict.get('location')

    use_precreated_pin = attribute_dict.get('use_precreated_pin')
    use_last_4_digits_of_id_as_initial_pin = attribute_dict.get(
        'use_last_4_digits_of_id_as_initial_pin')

    transfer_account_name = attribute_dict.get('transfer_account_name')
    first_name = attribute_dict.get('first_name')
    last_name = attribute_dict.get('last_name')

    primary_user_identifier = attribute_dict.get('primary_user_identifier')
    primary_user_pin = attribute_dict.get('primary_user_pin')

    custom_initial_disbursement = attribute_dict.get(
        'custom_initial_disbursement', None)

    is_vendor = attribute_dict.get('is_vendor', None)
    if is_vendor is None:
        is_vendor = attribute_dict.get('vendor', False)

    # is_beneficiary defaults to the opposite of is_vendor
    is_beneficiary = attribute_dict.get('is_beneficiary', not is_vendor)

    if current_app.config['IS_USING_BITCOIN']:
        try:
            base58.b58decode_check(blockchain_address)
        except ValueError:
            response_object = {
                'message':
                'Blockchain Address {} Not Valid'.format(blockchain_address)
            }
            return response_object, 400

    if isinstance(phone, bool):
        phone = None

    if phone:
        try:
            phone = proccess_phone_number(phone)
        except NumberParseException as e:
            response_object = {'message': 'Invalid Phone Number: ' + str(e)}
            return response_object, 400

    # Work out if there's an existing transfer account to bind to
    existing_transfer_account = None
    if primary_user_identifier:

        primary_user = find_user_from_public_identifier(
            primary_user_identifier)

        if not primary_user or not primary_user.verify_password(
                primary_user_pin):
            response_object = {'message': 'Primary User not Found'}
            return response_object, 400

        if not primary_user.verify_password(primary_user_pin):

            response_object = {'message': 'Invalid PIN for Primary User'}
            return response_object, 400

        primary_user_transfer_account = primary_user.transfer_account

        if not primary_user_transfer_account:
            response_object = {
                'message': 'Primary User has no transfer account'
            }
            return response_object, 400

    if not (phone or email or public_serial_number or blockchain_address):
        response_object = {'message': 'Must provide a unique identifier'}
        return response_object, 400

    if use_precreated_pin and not public_serial_number:
        response_object = {
            'message':
            'Must provide public serial number to use a transfer card or pre-created pin'
        }
        return response_object, 400

    if public_serial_number:
        public_serial_number = str(public_serial_number)

        if use_precreated_pin or require_transfer_card_exists:
            transfer_card = models.TransferCard.query.filter_by(
                public_serial_number=public_serial_number).first()

            if not transfer_card:
                response_object = {'message': 'Transfer card not found'}
                return response_object, 400

    if custom_initial_disbursement and not custom_initial_disbursement <= current_app.config[
            'MAXIMUM_CUSTOM_INITIAL_DISBURSEMENT']:
        response_object = {
            'message':
            'Disbursement more than maximum allowed amount ({} {})'.format(
                current_app.config['MAXIMUM_CUSTOM_INITIAL_DISBURSEMENT'] /
                100, current_app.config['CURRENCY_NAME'])
        }
        return response_object, 400

    existing_user = find_user_from_public_identifier(email, phone,
                                                     public_serial_number,
                                                     blockchain_address)
    if existing_user:

        if not allow_existing_user_modify:
            response_object = {'message': 'User already exists for Identifier'}
            return response_object, 400

        user = update_transfer_account_user(
            existing_user,
            first_name=first_name,
            last_name=last_name,
            phone=phone,
            email=email,
            public_serial_number=public_serial_number,
            use_precreated_pin=use_precreated_pin,
            existing_transfer_account=existing_transfer_account,
            is_beneficiary=is_beneficiary,
            is_vendor=is_vendor)

        default_attributes, custom_attributes = set_custom_attributes(
            attribute_dict, user)
        flag_modified(user, "custom_attributes")

        db.session.commit()

        response_object = {
            'message': 'User Updated',
            'data': {
                'user': user_schema.dump(user).data
            }
        }

        return response_object, 200

    elapsed_time('3.0 Ready to create')

    user = create_transfer_account_user(
        first_name=first_name,
        last_name=last_name,
        phone=phone,
        email=email,
        public_serial_number=public_serial_number,
        blockchain_address=blockchain_address,
        transfer_account_name=transfer_account_name,
        location=location,
        use_precreated_pin=use_precreated_pin,
        use_last_4_digits_of_id_as_initial_pin=
        use_last_4_digits_of_id_as_initial_pin,
        existing_transfer_account=existing_transfer_account,
        is_beneficiary=is_beneficiary,
        is_vendor=is_vendor)

    elapsed_time('4.0 Created')

    default_attributes, custom_attributes = set_custom_attributes(
        attribute_dict, user)

    if custom_initial_disbursement:
        try:
            disbursement = CreditTransferUtils.make_disbursement_transfer(
                custom_initial_disbursement, user)
        except Exception as e:
            response_object = {'message': str(e)}
            return response_object, 400

    elapsed_time('5.0 Disbursement done')

    db.session.flush()

    if location:
        user.location = location

    if phone and current_app.config['ONBOARDING_SMS']:
        try:
            balance = user.transfer_account.balance
            if isinstance(balance, int):
                balance = balance / 100

            send_onboarding_message(first_name=user.first_name,
                                    to_phone=phone,
                                    credits=balance,
                                    one_time_code=user.one_time_code)
        except Exception as e:
            print(e)
            pass

    if user.one_time_code:
        response_object = {
            'message': 'User Created',
            'data': {
                'user': user_schema.dump(user).data
            }
        }

    else:
        response_object = {
            'message': 'User Created',
            'data': {
                'user': user_schema.dump(user).data
            }
        }

    elapsed_time('6.0 Complete')

    return response_object, 200