Esempio n. 1
0
def test_create_transfer_account_user(
        test_client, init_database, create_master_organisation,
        last_name, location, lat, lng, initial_disbursement):
    from flask import g
    g.active_organisation = create_master_organisation

    assert proccess_create_or_modify_user_request(
        attribute_dict=dict(
        first_name='Lilly', last_name=last_name,
        phone=fake.msisdn(),
        location=location, lat=lat, lng=lng,
        initial_disbursement=initial_disbursement))
Esempio n. 2
0
    def put(self, user_id):
        put_data = request.get_json()
        put_data['user_id'] = user_id

        response_object, response_code = UserUtils.proccess_create_or_modify_user_request(
            put_data,
            organisation=g.active_organisation,
            allow_existing_user_modify=True,
            modify_only=True)

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

        return make_response(jsonify(response_object)), response_code
Esempio n. 3
0
    def post(self, user_id):

        post_data = request.get_json()
        organisation = g.get('active_organisation')
        if organisation is None:
            return make_response(
                jsonify({'message': 'Organisation must be set'})), 400

        response_object, response_code = UserUtils.proccess_create_or_modify_user_request(
            post_data, organisation=organisation)

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

        return make_response(jsonify(response_object)), response_code
Esempio n. 4
0
    def post(self, user_id):
        """
        :arg preprocess: whether the data should be cleaned before attempting to create a user - for example converting
        keys such as "Phone" to "phone". Useful for third-party webhooks where we don't necessarily have a lot of
        control over how the user will specify fields.
        :arg allow_as_update: Whether to return an error when the user already exists for the supplied IDs,
        or instead update the existing user as a PUT request would.  Useful for third-party webhooks where
        PUT requests aren't supported.
        :arg: return_raw_on_error: whether to return the raw input data on an error - useful for diagnosing what a
        webhook actually tried to submit
        """

        raw_data = request.get_json()

        preprocess = request.args.get('preprocess',
                                      '').lower() == 'true'  #Defaults to false
        allow_as_update = request.args.get(
            'allow_as_update', '').lower() == 'true'  #Defaults to false
        return_raw_on_error = request.args.get(
            'return_raw_on_error', '').lower() == 'true'  #Defaults to false

        organisation = g.get('active_organisation')
        if organisation is None:
            return make_response(
                jsonify({'message': 'Organisation must be set'})), 400

        if preprocess:
            data = standard_user_preprocess(raw_data, CREATE_USER_SETTINGS)
        else:
            data = raw_data

        response_object, response_code = UserUtils.proccess_create_or_modify_user_request(
            data,
            organisation=organisation,
            allow_existing_user_modify=allow_as_update)

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

        elif return_raw_on_error:
            response_object['raw_data'] = raw_data

        return make_response(jsonify(response_object)), response_code
    def post(self):
        # get the post data
        post_data = request.get_json()

        is_vendor = post_data.get('isVendor', False)

        header_postions = post_data.get('headerPositions')

        self.diagnostics = []

        for datarow in post_data.get('data'):


            attribute_dict = {}

            contains_anything = False
            for key, header_label in header_postions.items():

                attribute = datarow.get(key)

                if attribute:
                    contains_anything = True
                    attribute_dict[header_label] = attribute

            if contains_anything:
                item_response_object, response_code = UserUtils.proccess_create_or_modify_user_request(attribute_dict,
                                                                                                       force_dict_keys_lowercase=True,
                                                                                                       allow_existing_user_modify=True)

                self.diagnostics.append((item_response_object.get('message'), response_code))

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

        response_object = {
            'status': 'success',
            'message': 'Successfully Saved.',
            'diagnostics': self.diagnostics
        }

        return make_response(jsonify(response_object)), 201
Esempio n. 6
0
    def post(self, user_id):
        post_data = request.get_json()

        # Data supplied to the API via integrations such as KoboToolbox can be messy, so clean the data first
        dict_processor = AttributeDictProccessor(post_data)
        dict_processor.force_attribute_dict_keys_to_lowercase()
        dict_processor.strip_kobo_preslashes()
        dict_processor.attempt_to_truthy_dict_values()
        dict_processor.strip_weirdspace_characters()
        dict_processor.insert_settings_from_databse(CREATE_USER_SETTINGS)
        post_data = dict_processor.attribute_dict

        post_data = UserUtils.extract_kobo_custom_attributes(post_data)

        #  TODO: Kobo should have a more intelligent way of specifying the organisation
        response_object, response_code = UserUtils.proccess_create_or_modify_user_request(
            post_data, organisation=g.user.fallback_active_organisation())

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

        return make_response(jsonify(response_object)), response_code
Esempio n. 7
0
    def post(self):
        # get the post data
        post_data = request.get_json()

        email = post_data.get('email') or post_data.get('username')
        password = post_data.get('password')
        phone = post_data.get('phone')
        referral_code = post_data.get('referral_code')

        if phone is not None:
            # this is a registration from a mobile device THUS a vendor or recipient.
            response_object, response_code = UserUtils.proccess_create_or_modify_user_request(
                post_data,
                is_self_sign_up=True,
            )

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

            return make_response(jsonify(response_object)), response_code

        email_ok = False

        whitelisted_emails = EmailWhitelist.query\
            .filter_by(referral_code=referral_code, used=False) \
            .execution_options(show_all=True).all()

        selected_whitelist_item = None
        exact_match = False

        tier = None
        sempoadmin_emails = current_app.config['SEMPOADMIN_EMAILS']

        if sempoadmin_emails != [''] and email in sempoadmin_emails:
            email_ok = True
            tier = 'sempoadmin'

        for whitelisted in whitelisted_emails:
            if whitelisted.allow_partial_match and whitelisted.email in email:
                email_ok = True
                tier = whitelisted.tier
                selected_whitelist_item = whitelisted
                exact_match = False
                continue
            elif whitelisted.email == email:
                email_ok = True

                whitelisted.used = True
                tier = whitelisted.tier
                selected_whitelist_item = whitelisted
                exact_match = True
                continue

        if not email_ok:
            response_object = {
                'status': 'fail',
                'message': 'Invalid email domain.',
            }
            return make_response(jsonify(response_object)), 403

        if len(password) < 7:
            response_object = {
                'status': 'fail',
                'message': 'Password must be at least 6 characters long',
            }
            return make_response(jsonify(response_object)), 403

        # check if user already exists
        user = User.query.filter_by(email=email).execution_options(show_all=True).first()
        if user:
            response_object = {
                'status': 'fail',
                'message': 'User already exists. Please Log in.',
            }
            return make_response(jsonify(response_object)), 403

        if tier is None:
            tier = 'subadmin'

        if selected_whitelist_item:
            organisation = selected_whitelist_item.organisation
        else:
            organisation = Organisation.master_organisation()

        user = User(blockchain_address=organisation.primary_blockchain_address)

        user.create_admin_auth(email, password, tier, organisation)

        # insert the user
        db.session.add(user)

        db.session.flush()

        if exact_match:
            user.is_activated = True

            auth_token = user.encode_auth_token()

            # Possible Outcomes:
            # TFA required, but not set up
            # TFA not required

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

                db.session.commit()  # need this here to commit a created user to the db

                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 activated.')

            db.session.commit()

            return make_response(jsonify(response_object)), 201

        activation_token = user.encode_single_use_JWS('A')

        send_activation_email(activation_token, email)

        db.session.commit()

        # generate the auth token
        response_object = {
            'status': 'success',
            'message': 'Successfully registered. You must activate your email.',
        }

        return make_response(jsonify(response_object)), 201
Esempio n. 8
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
Esempio n. 9
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