Ejemplo n.º 1
0
    def get(self):
        # HANDLE PARAM : search_stirng - Any search string. An empty string (or None) will just return everything!
        search_string = request.args.get('search_string') or ''
        # HANDLE PARAM : params - Standard filter object. Exact same as the ones Metrics uses!
        encoded_filters = request.args.get('params')
        filters = process_transfer_filters(encoded_filters)
        # HANDLE PARAM : order
        # Valid orders types are: `ASC` and `DESC`
        # Default: DESC
        order_arg = request.args.get('order') or 'DESC'
        if order_arg.upper() not in ['ASC', 'DESC']:
            return { 'message': 'Invalid order value \'{}\'. Please use \'ASC\' or \'DESC\''.format(order_arg)}
        order = asc if order_arg.upper()=='ASC' else desc
        # HANDLE PARAM: sort_by
        # Valid orders types are: first_name, last_name, email, date_account_created, rank, balance, status
        # Default: rank
        sort_by_arg = request.args.get('sort_by') or 'rank'

        final_query = generate_search_query(search_string, filters, order, sort_by_arg)
        transfer_accounts, total_items, total_pages, _ = paginate_query(final_query, ignore_last_fetched=True)
        if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN', 'admin'):
            result = transfer_accounts_schema.dump(transfer_accounts)
        elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
            result = view_transfer_accounts_schema.dump(transfer_accounts)

        return {
            'message': 'Successfully Loaded.',
            'items': total_items,
            'pages': total_pages,
            'query_time': datetime.datetime.utcnow(),
            'data': { 'transfer_accounts': result.data }
        }
Ejemplo n.º 2
0
    def put(self, user_id):
        user = User.query.get(user_id)
        reset_tfa = request.args.get('reset_tfa', False) == 'true'
        if user is not None:
            if reset_tfa and AccessControl.has_sufficient_tier(
                    g.user.roles, 'ADMIN', 'sempoadmin'):
                user.reset_TFA()
            if reset_tfa and not AccessControl.has_sufficient_tier(
                    g.user.roles, 'ADMIN', 'sempoadmin'):
                response_object = {
                    'status': 'fail',
                    'message':
                    'Only Sempo admininstrators may reset TFA codes.',
                }
                return make_response(jsonify(response_object)), 403

            user.reset_password()

            response_object = {
                'status': 'success',
                'message': 'Successfully reset password for user.',
                'data': {
                    'user': user_schema.dump(user).data
                }
            }
            return make_response(jsonify(response_object)), 200
        else:
            response_object = {
                'message': 'No user to reset password for',
            }
            return make_response(jsonify(response_object)), 400
Ejemplo n.º 3
0
    def approve_and_disburse(self, initial_disbursement=None):
        from server.utils.access_control import AccessControl
        admin = getattr(g, 'user', None)
        active_org = getattr(g, 'active_organisation', Organisation.master_organisation())
        
        if initial_disbursement is None:
            # initial disbursement defaults to None. If initial_disbursement is set then skip this section.
            # If none, then we want to see if the active_org has a default disbursement amount
            initial_disbursement = active_org.default_disbursement

        # Baseline is NOT is_approved, and do NOT auto_resolve
        self.is_approved = False
        auto_resolve = False
        # If admin role is admin or higher, then auto-approval is contingent on being less than or 
        # equal to the default disbursement
        if (admin and AccessControl.has_sufficient_tier(admin.roles, 'ADMIN', 'admin'))or (
            g.get('auth_type') == 'external' and active_org.auto_approve_externally_created_users
        ):
            self.is_approved = True
            if initial_disbursement <= active_org.default_disbursement:
                auto_resolve = True
                
        # Accounts created by superadmins are all approved, and their disbursements are 
        # auto-resolved no matter how big they are!
        if admin and AccessControl.has_sufficient_tier(admin.roles, 'ADMIN', 'superadmin'):
            self.is_approved = True
            auto_resolve = True

        if self.is_beneficiary:
            # Initial disbursement should be pending if the account is not approved
            disbursement = self._make_initial_disbursement(initial_disbursement, auto_resolve=auto_resolve)
            return disbursement
Ejemplo n.º 4
0
    def post(self):

        post_data = request.get_json()

        email = post_data.get('email', '')
        email = email.lower() if email else ''
        tier = post_data.get('tier')
        organisation_id = post_data.get('organisation_id', None)

        if not (email and tier):
            response_object = {'message': 'No email or tier provided'}
            return make_response(jsonify(response_object)), 400

        if not AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN', tier):
            return make_response(
                jsonify({
                    'message':
                    f'User does not have permission to invite {tier}'
                })), 400

        if organisation_id and not AccessControl.has_sufficient_tier(
                g.user.roles, 'ADMIN', 'sempoadmin'):
            response_object = {
                'message': 'Not Authorised to set organisation ID'
            }
            return make_response(jsonify(response_object)), 401

        target_organisation_id = organisation_id or g.active_organisation.id
        if not target_organisation_id:
            response_object = {
                'message': 'Must provide an organisation to bind user to'
            }
            return make_response(jsonify(response_object)), 400

        organisation = Organisation.query.get(target_organisation_id)
        if not organisation:
            response_object = {'message': 'Organisation Not Found'}
            return make_response(jsonify(response_object)), 404
        email_exists = EmailWhitelist.query.filter(
            func.lower(EmailWhitelist.email) == email).first()

        if email_exists:
            response_object = {'message': 'Email already on whitelist.'}
            return make_response(jsonify(response_object)), 400

        invite = EmailWhitelist(email=email,
                                tier=tier,
                                organisation_id=target_organisation_id)

        db.session.add(invite)

        send_invite_email(invite, organisation)

        db.session.commit()

        response_object = {
            'message': 'An invite has been sent!',
        }

        return make_response(jsonify(attach_host(response_object))), 201
Ejemplo n.º 5
0
def sempo_admin_involved(credit_transfer):
    if credit_transfer.recipient_user and AccessControl.has_sufficient_tier(
            credit_transfer.recipient_user.roles, 'ADMIN', 'sempoadmin'):
        return True

    if credit_transfer.sender_user and AccessControl.has_sufficient_tier(
            credit_transfer.sender_user.roles, 'ADMIN', 'sempoadmin'):
        return True

    return False
Ejemplo n.º 6
0
    def get(self, kyc_application_id):

        user_id = request.args.get('user_id')

        trulioo_countries = request.args.get('trulioo_countries', None)
        trulioo_documents = request.args.get('trulioo_documents', None)
        country = request.args.get('country', None)

        if trulioo_countries:
            trulioo_countries = supported_countries
            return make_response(jsonify({'message': 'Trulioo Countries', 'data': {'kyc_application': {'trulioo_countries': trulioo_countries}}})), 200

        if trulioo_documents:
            trulioo_documents = {country: supported_documents[country]}
            return make_response(jsonify({'message': 'Trulioo Countries', 'data': {'kyc_application': {'trulioo_documents': trulioo_documents}}})), 200

        if AccessControl.has_suffient_role(g.user.roles, {'ADMIN': 'subadmin'}):
            if user_id:
                # user account KYC
                kyc_details = KycApplication.query.filter_by(user_id=user_id).first()
            else:
                # main organisation KYC
                kyc_details = KycApplication.query.filter_by(organisation_id=g.active_organisation.id).first()

            if kyc_details is None:
                response_object = {
                    'message': 'No business verification details found'
                }

                return make_response(jsonify(response_object)), 404

            if user_id and AccessControl.has_suffient_role(g.user.roles, {'ADMIN': 'admin'}):
                response_object = {
                    'message': 'Successfully loaded business verification details',
                    'data': {'kyc_application': kyc_application_schema.dump(kyc_details).data}
                }

                return make_response(jsonify(response_object)), 200

        else:
            # must be an individual (mobile) user account
            kyc_details = KycApplication.query.filter_by(user_id=g.user.id).first()
            if kyc_details is None:
                return make_response(jsonify({'message': 'No KYC object found for user.', 'data': {'kyc_application': {}}}))

        # displays kyc_status and kyc_actions state only.
        response_object = {
            'message': 'Loaded KYC details',
            'data': {'kyc_application': kyc_application_state_schema.dump(kyc_details).data}
        }

        return make_response(jsonify(response_object)), 200
Ejemplo n.º 7
0
def test_update_admin_user_tier(new_sempo_admin_user):
    """
    GIVEN a User model
    WHEN a user tier is updated to superadmin
    THEN check that all lower tiers are True
    """
    new_sempo_admin_user.set_held_role('ADMIN', 'view')

    assert AccessControl.has_any_tier(new_sempo_admin_user.roles, 'ADMIN')
    assert not AccessControl.has_sufficient_tier(new_sempo_admin_user.roles,
                                                 'ADMIN', 'subadmin')
    assert not AccessControl.has_sufficient_tier(new_sempo_admin_user.roles,
                                                 'ADMIN', 'admin')
    assert not AccessControl.has_sufficient_tier(new_sempo_admin_user.roles,
                                                 'ADMIN', 'superadmin')

    # update user tier to super admin
    new_sempo_admin_user.set_held_role('ADMIN', 'superadmin')

    assert AccessControl.has_any_tier(new_sempo_admin_user.roles, 'ADMIN')
    assert AccessControl.has_sufficient_tier(new_sempo_admin_user.roles,
                                             'ADMIN', 'subadmin')
    assert AccessControl.has_sufficient_tier(new_sempo_admin_user.roles,
                                             'ADMIN', 'admin')
    assert AccessControl.has_sufficient_tier(new_sempo_admin_user.roles,
                                             'ADMIN', 'superadmin')
Ejemplo n.º 8
0
    def get(self):

        user = g.user

        if AccessControl.has_suffient_role(user.roles, {'ADMIN': 'subadmin', 'VENDOR': 'supervendor'}):
            # admins and supervendors see all transfers for that transfer account
            transfer_account = user.transfer_account

            transfers_query = CreditTransfer.query.filter(
                or_(CreditTransfer.recipient_transfer_account_id == transfer_account.id,
                    CreditTransfer.sender_transfer_account_id == transfer_account.id))

        else:
            # other users only see transfers involving themselves
            transfers_query = CreditTransfer.query.filter(
                or_(CreditTransfer.recipient_user_id == user.id,
                    CreditTransfer.sender_user_id == user.id))

        transfers, total_items, total_pages = paginate_query(transfers_query, CreditTransfer)

        transfer_list = me_credit_transfers_schema.dump(transfers).data

        response_object = {
            'message': 'Successfully Loaded.',
            'items': total_items,
            'pages': total_pages,
            'data': {
                'credit_transfers': transfer_list,
            }
        }

        return make_response(jsonify(response_object)), 201
Ejemplo n.º 9
0
    def resolve_as_complete(self, batch_uuid=None):
        if self.transfer_status not in [None, TransferStatusEnum.PENDING]:
            raise Exception(f'Resolve called multiple times for transfer {self.id}')
        try:
            self.check_sender_transfer_limits()
        except TransferLimitError as e:
            # Sempo admins can always bypass limits, allowing for things like emergency moving of funds etc
            if hasattr(g, 'user') and AccessControl.has_suffient_role(g.user.roles, {'ADMIN': 'sempoadmin'}):
                self.add_message(f'Warning: {e}')
            else:
                raise e

        self.resolved_date = datetime.datetime.utcnow()
        self.transfer_status = TransferStatusEnum.COMPLETE
        self.update_balances()

        if self.transfer_type == TransferTypeEnum.PAYMENT and self.transfer_subtype == TransferSubTypeEnum.DISBURSEMENT:
            if self.recipient_user and self.recipient_user.transfer_card:
                self.recipient_user.transfer_card.update_transfer_card()

        if batch_uuid:
            self.batch_uuid = batch_uuid

        if self.fiat_ramp and self.transfer_type in [TransferTypeEnum.DEPOSIT, TransferTypeEnum.WITHDRAWAL]:
            self.fiat_ramp.resolve_as_complete()
Ejemplo n.º 10
0
    def approve_and_disburse(self, initial_disbursement=None):
        from server.utils.access_control import AccessControl

        active_org = getattr(g, 'active_organisation', self.primary_user.default_organisation)
        admin = getattr(g, 'user', None)
        auto_resolve = initial_disbursement == active_org.default_disbursement

        if not self.is_approved and admin and AccessControl.has_sufficient_tier(admin.roles, 'ADMIN', 'admin'):
            self.is_approved = True

        if self.is_beneficiary:
            # TODO: make this more robust
            # approve_and_disburse might be called for a second time to disburse
            # so first check that no credit transfer have already been received
            if len(self.credit_receives) < 1:
                # make initial disbursement
                disbursement = self._make_initial_disbursement(initial_disbursement, auto_resolve)
                return disbursement

            elif len(self.credit_receives) == 1:
                # else likely initial disbursement received, check if DISBURSEMENT and PENDING and resolve if default

                disbursement = self.credit_receives[0]
                if disbursement.transfer_subtype == TransferSubTypeEnum.DISBURSEMENT and disbursement.transfer_status == TransferStatusEnum.PENDING and auto_resolve:
                    disbursement.resolve_as_completed()
                    return disbursement
Ejemplo n.º 11
0
    def post(self):
        reference = None
        kyc_application_id = None

        if 'kyc_application_id' in request.form:
            kyc_application_id = request.form['kyc_application_id']

        if 'document' not in request.files:
            return make_response(jsonify({'message': 'No File'})), 400

        document = request.files['document']
        filename = document.filename

        if kyc_application_id is None:
            return make_response(jsonify({'message': 'You must append documents to a business profile'})), 400

        business_details = KycApplication.query.filter_by(id=kyc_application_id).first()

        if not business_details:
            return make_response(jsonify({'message': 'Cannot find kyc for id {}'.format(kyc_application_id)})), 404

        if business_details.organisation_id and AccessControl.has_suffient_role(g.user.roles, {'ADMIN': 'superadmin'}) is not True:
            return make_response(jsonify({'message': 'Must be a superadmin to edit admin org KYC object'})), 401

        if filename == '':
            return make_response(jsonify({'message': 'No File'})), 400

        if not allowed_file(filename):
            return make_response(jsonify({'message': 'Must be JPG, JPEG, PNG or PDF'})), 400

        file_type = filename.rsplit('.', 1)[1].lower()
        new_filename = generate_new_filename(filename, file_type)

        saved_document = UploadedResource.query.filter_by(filename=new_filename).first()

        if saved_document:
            return make_response(jsonify({'message': 'Document already exists'})), 400

        save_to_s3_from_document(document=document, new_filename=new_filename)

        if 'reference' in request.form:
            reference = request.form['reference']

        uploaded_document = UploadedResource(filename=new_filename, file_type=file_type,
                                             reference=reference, user_filename=filename)
        db.session.add(uploaded_document)

        # tie document to kyc application
        uploaded_document.kyc_application_id = business_details.id

        response_object = {
            'message': 'Document uploaded',
            'data': {'kyc_application': kyc_application_schema.dump(business_details).data}
        }

        return make_response(jsonify(response_object)), 201
Ejemplo n.º 12
0
    def _setup_org_transfer_account(self):
        transfer_account = server.models.transfer_account.TransferAccount(
            bound_entity=self, is_approved=True)
        db.session.add(transfer_account)
        self.org_level_transfer_account = transfer_account

        # Back setup for delayed organisation transfer account instantiation
        for user in self.users:
            if AccessControl.has_any_tier(user.roles, 'ADMIN'):
                user.transfer_accounts.append(self.org_level_transfer_account)
    def put(self, bank_account_id):

        put_data = request.get_json()

        kyc_application_id = put_data.get('kyc_application_id')

        bank_country = put_data.get('bank_country')
        routing_number = put_data.get('routing_number')
        account_number = put_data.get('account_number')
        currency = put_data.get('currency')

        if bank_account_id is None:
            return make_response(
                jsonify({'message':
                         'You need to provide a bank account ID'})), 400

        bank_account = BankAccount.query.filter_by(id=bank_account_id).first()

        if kyc_application_id is None:
            kyc_application_id = bank_account.kyc_application_id

        business_details = KycApplication.query.filter_by(
            id=kyc_application_id).first()

        if not business_details:
            return make_response(
                jsonify({
                    'message':
                    'Cannot find kyc for id {}'.format(kyc_application_id)
                })), 404

        if business_details.organisation_id and AccessControl.has_suffient_role(
                g.user.roles, {'ADMIN': 'superadmin'}) is not True:
            return make_response(
                jsonify({
                    'message':
                    'Must be a superadmin to edit admin org KYC object'
                })), 401

        if bank_account:
            bank_account.kyc_application_id = kyc_application_id
            bank_account.bank_country = bank_country
            bank_account.routing_number = routing_number
            bank_account.account_number = account_number
            bank_account.currency = currency

        response_object = {
            'message': 'Bank account edited',
            'data': {
                'kyc_application':
                kyc_application_schema.dump(business_details).data
            }
        }

        return make_response(jsonify(response_object)), 200
Ejemplo n.º 14
0
    def post(self, bank_account_id):
        post_data = request.get_json()

        kyc_application_id = post_data.get('kyc_application_id')

        bank_country = post_data.get('bank_country')
        routing_number = post_data.get('routing_number')
        account_number = post_data.get('account_number')
        currency = post_data.get('currency')

        business_details = KycApplication.query.filter_by(id=kyc_application_id).first()

        if not business_details:
            return make_response(jsonify({'message': 'Cannot find kyc for id {}'.format(kyc_application_id)})), 404

        if business_details.organisation_id and AccessControl.has_suffient_role(g.user.roles,
                                                                                {'ADMIN': 'superadmin'}) is not True:
            return make_response(jsonify({'message': 'Must be a superadmin to edit admin org KYC object'})), 401

        if routing_number is None or account_number is None or bank_country is None or currency is None or kyc_application_id is None:
            response_object = {
                'message': 'Need routing_number, account_number, bank_country, currency and business profile id',
            }

            return make_response(jsonify(response_object)), 400

        # can't create a duplicate bank account at present
        bank_account = BankAccount.query.filter_by(routing_number=routing_number, account_number=account_number).first()

        if bank_account:
            response_object = {
                'message': 'Bank account already exists',
            }

            return make_response(jsonify(response_object)), 400

        # create new bank account
        create_bank_account = BankAccount(
            bank_country=bank_country,
            routing_number=routing_number,
            account_number=account_number,
            currency=currency,
        )

        create_bank_account.kyc_application = business_details

        db.session.add(create_bank_account)

        response_object = {
            'message': 'Bank account added',
            'data': {'kyc_application': kyc_application_schema.dump(business_details).data}
        }

        return make_response(jsonify(response_object)), 201
Ejemplo n.º 15
0
def test_new_sempo_admin_user(new_sempo_admin_user):
    """
    GIVEN a User model
    WHEN a new admin User is created
    THEN check the email, password is hashed, not authenticated, and role fields are defined correctly
    """
    assert new_sempo_admin_user.email == '*****@*****.**'
    assert new_sempo_admin_user.password_hash is not None
    assert new_sempo_admin_user.password_hash != 'TestPassword'
    assert not new_sempo_admin_user.is_activated
    assert AccessControl.has_any_tier(new_sempo_admin_user.roles, 'ADMIN')
    assert isinstance(new_sempo_admin_user.secret, str)
Ejemplo n.º 16
0
 def check_if_fully_approved(self):
     # Checks if the credit transfer is approved and ready to be resolved as complete
     if current_app.config[
             'REQUIRE_MULTIPLE_APPROVALS'] and not AccessControl.has_sufficient_tier(
                 g.user.roles, 'ADMIN', 'sempoadmin'):
         if len(self.approvers) <= 1:
             return False
         else:
             # If there's an `ALLOWED_APPROVERS` list, one of the approvers has to be in it
             if current_app.config['ALLOWED_APPROVERS']:
                 # approve if email in list
                 for user in self.approvers:
                     if user.email in current_app.config[
                             'ALLOWED_APPROVERS']:
                         return True
             # If there's not an `ALLOWED_APPROVERS` list, it just has to be approved by more than one person
             else:
                 return True
     else:
         return True
Ejemplo n.º 17
0
 def check_if_approved(self):
     if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN',
                                          'sempoadmin'):
         return True
     if current_app.config['REQUIRE_MULTIPLE_APPROVALS']:
         # It always has to be approved by at least two people
         if len(self.approvers) <= 1:
             return False
         # If there's an `ALLOWED_APPROVERS` list, one of the approvers has to be in it
         if current_app.config['ALLOWED_APPROVERS']:
             # approve if email in list
             for user in self.approvers:
                 if user.email in current_app.config['ALLOWED_APPROVERS']:
                     return True
         # If there's not an `ALLOWED_APPROVERS` list, it just has to be approved by more than one person
         else:
             return True
     else:
         # Multi-approval is off, so it's approved by default
         return True
Ejemplo n.º 18
0
    def approve_initial_disbursement(self):
        from server.utils.access_control import AccessControl

        admin = getattr(g, 'user', None)
        active_org = getattr(g, 'active_organisation', Organisation.master_organisation())

        initial_disbursement = db.session.query(server.models.credit_transfer.CreditTransfer)\
            .filter(server.models.credit_transfer.CreditTransfer.recipient_user == self.primary_user)\
            .filter(server.models.credit_transfer.CreditTransfer.is_initial_disbursement == True)\
            .first()
        
        if initial_disbursement and initial_disbursement.transfer_status == TransferStatusEnum.PENDING:
            # Must be superadmin to auto-resolve something over default disbursement
            if initial_disbursement.transfer_amount > active_org.default_disbursement:
                if admin and AccessControl.has_sufficient_tier(admin.roles, 'ADMIN', 'superadmin'):
                    return initial_disbursement.resolve_as_complete_and_trigger_blockchain(queue='high-priority')
                else:
                    return False
            else:
                return initial_disbursement.resolve_as_complete_and_trigger_blockchain(queue='high-priority')
Ejemplo n.º 19
0
 def is_TFA_required(self):
     for tier in current_app.config['TFA_REQUIRED_ROLES']:
         if AccessControl.has_exact_role(self.roles, 'ADMIN', tier):
             return True
     else:
         return False
Ejemplo n.º 20
0
 def is_supervendor(self):
     return AccessControl.has_sufficient_tier(self.roles, 'VENDOR',
                                              'supervendor')
Ejemplo n.º 21
0
 def has_group_account_role(self):
     return AccessControl.has_any_tier(self.roles, 'GROUP_ACCOUNT')
Ejemplo n.º 22
0
 def has_token_agent_role(self):
     return AccessControl.has_any_tier(self.roles, 'TOKEN_AGENT')
Ejemplo n.º 23
0
 def has_beneficiary_role(self):
     return AccessControl.has_any_tier(self.roles, 'BENEFICIARY')
Ejemplo n.º 24
0
 def has_vendor_role(self):
     return AccessControl.has_any_tier(self.roles, 'VENDOR')
Ejemplo n.º 25
0
 def has_admin_role(self):
     return AccessControl.has_any_tier(self.roles, 'ADMIN')
Ejemplo n.º 26
0
    def get(self):
        # HANDLE PARAM : search_stirng - Any search string. An empty string (or None) will just return everything!
        search_string = request.args.get('search_string') or ''
        # HANDLE PARAM : params - Standard filter object. Exact same as the ones Metrics uses!
        encoded_filters = request.args.get('params')
        filters = process_transfer_filters(encoded_filters)
        # HANDLE PARAM : order
        # Valid orders types are: `ASC` and `DESC`
        # Default: DESC
        order_arg = request.args.get('order') or 'DESC'
        if order_arg.upper() not in ['ASC', 'DESC']:
            return {
                'message':
                'Invalid order value \'{}\'. Please use \'ASC\' or \'DESC\''.
                format(order_arg)
            }
        order = asc if order_arg.upper() == 'ASC' else desc
        # HANDLE PARAM: sort_by
        # Valid orders types are: first_name, last_name, email, date_account_created, rank, balance, status
        # Default: rank
        sort_types_to_database_types = {
            'first_name': User.first_name,
            'last_name': User.last_name,
            'email': User.email,
            'date_account_created': User.created,
            'rank': 'rank',
            'balance': TransferAccount._balance_wei,
            'status': TransferAccount.is_approved,
        }
        sort_by_arg = request.args.get('sort_by') or 'rank'
        if sort_by_arg not in sort_types_to_database_types:
            return {
                'message': f'Invalid sort_by value {sort_by_arg}. Please use one of the following: {sort_types_to_database_types.keys()}'\
            }

        # To add new searchable column, simply add a new SearchableColumn object!
        # And don't forget to add a trigram index on that column too-- see migration 33df5e72fca4 for reference
        user_search_columns = [
            SearchableColumn('first_name', User.first_name, rank=1.5),
            SearchableColumn('last_name', User.last_name, rank=1.5),
            SearchableColumn('phone', User.phone, rank=2),
            SearchableColumn('public_serial_number',
                             User.public_serial_number,
                             rank=2),
            SearchableColumn('location', User.location, rank=1),
            SearchableColumn('primary_blockchain_address',
                             User.primary_blockchain_address,
                             rank=2),
        ]

        sum_search = reduce(lambda x, y: x + y, [
            sc.get_similarity_query(search_string)
            for sc in user_search_columns
        ])
        sort_by = sum_search if sort_by_arg == 'rank' else sort_types_to_database_types[
            sort_by_arg]
        # If there's no search string, the process is the same, just sort by account creation date
        sort_by = sort_types_to_database_types[
            'date_account_created'] if sort_by == 'rank' and not search_string else sort_by

        final_query = db.session.query(TransferAccount, User, sum_search)\
            .with_entities(TransferAccount, sum_search)\
            .outerjoin(TransferAccount, User.default_transfer_account_id == TransferAccount.id)\
            .filter(TransferAccount.is_ghost != True)\
            .order_by(order(sort_by))
        # If there is a search string, we only want to return ranked results!
        final_query = final_query.filter(
            sum_search != 0) if search_string else final_query

        final_query = apply_filters(final_query, filters, User)
        transfer_accounts, total_items, total_pages, _ = paginate_query(
            final_query, ignore_last_fetched=True)
        accounts = [resultTuple[0] for resultTuple in transfer_accounts]
        if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN', 'admin'):
            result = transfer_accounts_schema.dump(accounts)
        elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
            result = view_transfer_accounts_schema.dump(accounts)

        return {
            'message': 'Successfully Loaded.',
            'items': total_items,
            'pages': total_pages,
            'query_time': datetime.datetime.utcnow(),
            'data': {
                'transfer_accounts': result.data
            }
        }
Ejemplo n.º 27
0
    def get(self, credit_transfer_id):
        transfer_account_ids = request.args.get('transfer_account_ids')
        transfer_type = request.args.get('transfer_type', 'ALL')
        get_transfer_stats = request.args.get('get_stats', False)

        transfer_list = None

        if transfer_type:
            transfer_type = transfer_type.upper()

        if credit_transfer_id:

            credit_transfer = CreditTransfer.query.get(credit_transfer_id)

            if credit_transfer is None:
                return make_response(
                    jsonify({'message': 'Credit transfer not found'})), 404

            if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN',
                                                 'admin'):
                transfer_list = credit_transfers_schema.dump([credit_transfer
                                                              ]).data
            elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
                transfer_list = view_credit_transfers_schema.dump(
                    [credit_transfer]).data

            transfer_stats = []

            response_object = {
                'status': 'success',
                'message': 'Successfully Loaded.',
                'data': {
                    'credit_transfer': transfer_list,
                    'transfer_stats': transfer_stats
                }
            }

            return make_response(jsonify(response_object)), 200

        else:

            query = CreditTransfer.query
            transfer_list = None

            if transfer_type != 'ALL':
                try:
                    transfer_type_enum = TransferTypeEnum[transfer_type]
                    query = query.filter(
                        CreditTransfer.transfer_type == transfer_type_enum)
                except KeyError:
                    response_object = {
                        'message': 'Invalid Filter: Transfer Type ',
                    }
                    return make_response(jsonify(response_object)), 400

            if transfer_account_ids:
                # We're getting a list of transfer accounts - parse
                try:
                    parsed_transfer_account_ids = list(
                        map(lambda x: int(x),
                            filter(None, transfer_account_ids.split(','))))

                except ValueError:
                    response_object = {
                        'message': 'Invalid Filter: Transfer Account IDs ',
                    }
                    return make_response(jsonify(response_object)), 400

                if parsed_transfer_account_ids:

                    query = query.filter(
                        or_(
                            CreditTransfer.recipient_transfer_account_id.in_(
                                parsed_transfer_account_ids),
                            CreditTransfer.sender_transfer_account_id.in_(
                                parsed_transfer_account_ids)))

            transfers, total_items, total_pages = paginate_query(
                query, CreditTransfer)

            if get_transfer_stats:
                transfer_stats = calculate_transfer_stats(
                    total_time_series=True)
            else:
                transfer_stats = None

            if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN',
                                                 'admin'):
                transfer_list = credit_transfers_schema.dump(transfers).data
            elif AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN',
                                                   'view'):
                transfer_list = view_credit_transfers_schema.dump(
                    transfers).data

            response_object = {
                'status': 'success',
                'message': 'Successfully Loaded.',
                'items': total_items,
                'pages': total_pages,
                'data': {
                    'credit_transfers': transfer_list,
                    'transfer_stats': transfer_stats
                }
            }

            return make_response(jsonify(response_object)), 200
    def get(self, transfer_account_id):

        account_type_filter = request.args.get('account_type')
        result = None

        if transfer_account_id:
            transfer_account = TransferAccount.query.get(transfer_account_id)

            if transfer_account is None:
                response_object = {
                    'message':
                    'No such transfer account: {}'.format(transfer_account_id),
                }

                return make_response(jsonify(response_object)), 400

            if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN',
                                                 'admin'):
                result = transfer_account_schema.dump(transfer_account)
            elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
                result = view_transfer_account_schema.dump(transfer_account)

            response_object = {
                'message': 'Successfully Loaded.',
                'data': {
                    'transfer_account': result.data,
                }
            }
            return make_response(jsonify(response_object)), 201

        else:

            search_string = request.args.get('search_string') or ''
            # HANDLE PARAM : params - Standard filter object. Exact same as the ones Metrics uses!
            encoded_filters = request.args.get('params')
            filters = process_transfer_filters(encoded_filters)
            # HANDLE PARAM : order
            # Valid orders types are: `ASC` and `DESC`
            # Default: DESC
            order_arg = request.args.get('order') or 'DESC'
            if order_arg.upper() not in ['ASC', 'DESC']:
                return {
                    'message':
                    'Invalid order value \'{}\'. Please use \'ASC\' or \'DESC\''
                    .format(order_arg)
                }
            order = asc if order_arg.upper() == 'ASC' else desc
            # HANDLE PARAM: sort_by
            # Valid orders types are: first_name, last_name, email, date_account_created, rank, balance, status
            # Default: rank
            sort_by_arg = request.args.get('sort_by') or 'rank'

            base_query = generate_search_query(
                search_string, filters, order,
                sort_by_arg).filter(TransferAccount.is_ghost != True)

            if account_type_filter == 'vendor':
                transfer_accounts_query = base_query.filter_by(is_vendor=True)
            elif account_type_filter == 'beneficiary':
                transfer_accounts_query = base_query.filter_by(
                    is_beneficiary=True)
            else:
                pass
                # Filter Contract, Float and Organisation Transfer Accounts
                transfer_accounts_query = (base_query.filter(
                    TransferAccount.account_type == TransferAccountType.USER))

            transfer_accounts, total_items, total_pages, new_last_fetched = paginate_query(
                transfer_accounts_query)

            if transfer_accounts is None:
                response_object = {
                    'message': 'No transfer accounts',
                }

                return make_response(jsonify(response_object)), 400

            if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN',
                                                 'admin'):
                result = transfer_accounts_schema.dump(transfer_accounts)
            elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
                result = view_transfer_accounts_schema.dump(transfer_accounts)

            response_object = {
                'message':
                'Successfully Loaded.',
                'items':
                total_items,
                'pages':
                total_pages,
                'last_fetched':
                new_last_fetched,
                'query_time':
                datetime.datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S"),
                'data': {
                    'transfer_accounts': result.data
                }
            }
            return make_response(json.dumps(response_object), 200)
Ejemplo n.º 29
0
    def get(self, credit_transfer_id):
        transfer_account_ids = request.args.get('transfer_account_ids')
        # Handle search parameters
        # HANDLE PARAM : search_string - Any search string. An empty string (or None) will just return everything!
        search_string = request.args.get('search_string') or ''
        # HANDLE PARAM : params - Standard filter object. Exact same as the ones Metrics uses!
        encoded_filters = request.args.get('params')
        filters = process_transfer_filters(encoded_filters)
        # HANDLE PARAM : order
        # Valid orders types are: `ASC` and `DESC`
        # Default: DESC
        order_arg = request.args.get('order') or 'DESC'
        if order_arg.upper() not in ['ASC', 'DESC']:
            return {
                'message':
                'Invalid order value \'{}\'. Please use \'ASC\' or \'DESC\''.
                format(order_arg)
            }
        order = asc if order_arg.upper() == 'ASC' else desc
        # HANDLE PARAM: sort_by
        # Valid orders types are: first_name, last_name, email, date_account_created, rank, balance, status
        # Default: rank
        sort_by_arg = request.args.get('sort_by') or 'rank'

        if credit_transfer_id:
            credit_transfer = CreditTransfer.query.get(credit_transfer_id)

            if credit_transfer is None:
                return make_response(
                    jsonify({'message': 'Credit transfer not found'})), 404

            if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN',
                                                 'admin'):
                result = credit_transfer_schema.dump(credit_transfer).data
            elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
                result = view_credit_transfer_schema.dump(credit_transfer).data

            transfer_stats = []

            response_object = {
                'status': 'success',
                'message': 'Successfully Loaded.',
                'data': {
                    'credit_transfer': result,
                    'transfer_stats': transfer_stats
                }
            }

            return make_response(jsonify(response_object)), 200

        else:
            if transfer_account_ids:
                # We're getting a list of transfer accounts - parse
                try:
                    parsed_transfer_account_ids = list(
                        map(lambda x: int(x),
                            filter(None, transfer_account_ids.split(','))))
                except Exception as e:
                    response_object = {'status': 'fail', 'message': str(e)}
                    return make_response(jsonify(response_object)), 400

                if parsed_transfer_account_ids:
                    try:
                        query = generate_search_query(
                            search_string,
                            filters,
                            order,
                            sort_by_arg,
                            search_type=CREDIT_TRANSFER)
                    except Exception as e:
                        response_object = {'status': 'fail', 'message': str(e)}
                        return make_response(jsonify(response_object)), 200

                    final_query = query.filter(
                        or_(
                            CreditTransfer.recipient_transfer_account_id.in_(
                                parsed_transfer_account_ids),
                            CreditTransfer.sender_transfer_account_id.in_(
                                parsed_transfer_account_ids)))

                    credit_transfers, total_items, total_pages, new_last_fetched = paginate_query(
                        final_query, ignore_last_fetched=True)
                    if AccessControl.has_sufficient_tier(
                            g.user.roles, 'ADMIN', 'admin'):
                        result = credit_transfers_schema.dump(credit_transfers)
                    elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
                        result = view_credit_transfers_schema.dump(
                            credit_transfers)

                    return {
                        'status': 'success',
                        'message': 'Successfully Loaded.',
                        'items': total_items,
                        'pages': total_pages,
                        'last_fetched': new_last_fetched,
                        'query_time': datetime.datetime.utcnow(),
                        'data': {
                            'credit_transfers': result.data
                        }
                    }

            else:
                try:
                    final_query = generate_search_query(
                        search_string,
                        filters,
                        order,
                        sort_by_arg,
                        search_type=CREDIT_TRANSFER)
                except Exception as e:
                    response_object = {'status': 'fail', 'message': str(e)}
                    return make_response(jsonify(response_object)), 200

            credit_transfers, total_items, total_pages, new_last_fetched = paginate_query(
                final_query, ignore_last_fetched=True)
            if AccessControl.has_sufficient_tier(g.user.roles, 'ADMIN',
                                                 'admin'):
                result = credit_transfers_schema.dump(credit_transfers)
            elif AccessControl.has_any_tier(g.user.roles, 'ADMIN'):
                result = view_credit_transfers_schema.dump(credit_transfers)

            return {
                'status': 'success',
                'message': 'Successfully Loaded.',
                'items': total_items,
                'pages': total_pages,
                'last_fetched': new_last_fetched,
                'query_time': datetime.datetime.utcnow(),
                'data': {
                    'credit_transfers': result.data
                }
            }
Ejemplo n.º 30
0
    def post(self, credit_transfer_id):

        auto_resolve = AccessControl.has_sufficient_tier(
            g.user.roles, 'ADMIN', 'superadmin')

        post_data = request.get_json()

        uuid = post_data.get('uuid')

        queue = 'low-priority'

        transfer_type = post_data.get('transfer_type')
        transfer_amount = abs(
            round(Decimal(post_data.get('transfer_amount') or 0), 6))
        token_id = post_data.get('token_id')
        target_balance = post_data.get('target_balance')

        transfer_use = post_data.get('transfer_use')
        try:
            use_ids = transfer_use.split(',')  # passed as '3,4' etc.
        except AttributeError:
            use_ids = transfer_use

        sender_user_id = post_data.get('sender_user_id')
        recipient_user_id = post_data.get('recipient_user_id')

        # These could be phone numbers, email, nfc serial numbers, card numbers etc
        sender_public_identifier = post_data.get('sender_public_identifier')
        recipient_public_identifier = post_data.get(
            'recipient_public_identifier')

        sender_transfer_account_id = post_data.get(
            'sender_transfer_account_id')
        recipient_transfer_account_id = post_data.get(
            'recipient_transfer_account_id')

        recipient_transfer_accounts_ids = post_data.get(
            'recipient_transfer_accounts_ids')

        # invert_recipient_list will send to everyone _except_ for the users in recipient_transfer_accounts_ids
        invert_recipient_list = post_data.get('invert_recipient_list', False)
        invert_recipient_list = False if invert_recipient_list == False else True

        credit_transfers = []
        response_list = []
        is_bulk = False
        transfer_card = None

        if uuid:
            existing_transfer = CreditTransfer.query.filter_by(
                uuid=uuid).first()

            # We return a 201 here so that the client removes the uuid from the cache
            response_object = {
                'message': 'Transfer Successful',
                'data': {
                    'credit_transfer':
                    credit_transfer_schema.dump(existing_transfer).data,
                }
            }
            return make_response(jsonify(response_object)), 201

        if transfer_amount <= 0 and not target_balance and not (
                transfer_amount == 0 and transfer_type == "BALANCE"):
            response_object = {
                'message': 'Transfer amount must be positive',
            }
            return make_response(jsonify(response_object)), 400

        if recipient_transfer_accounts_ids:
            is_bulk = True
            batch_uuid = str(uuid4())

            if transfer_type not in ["DISBURSEMENT", "BALANCE"]:
                response_object = {
                    'message':
                    'Bulk transfer must be either disbursement or balance',
                }
                return make_response(jsonify(response_object)), 400

            transfer_user_list = []
            individual_sender_user = None

            if invert_recipient_list:
                all_accounts_query = TransferAccount.query.filter(
                    TransferAccount.is_ghost != True).filter_by(
                        organisation_id=g.active_organisation.id)
                all_user_accounts_query = (all_accounts_query.filter(
                    TransferAccount.account_type == TransferAccountType.USER))
                all_accounts_except_selected_query = all_user_accounts_query.filter(
                    not_(
                        TransferAccount.id.in_(
                            recipient_transfer_accounts_ids)))
                for individual_recipient_user in all_accounts_except_selected_query.all(
                ):
                    transfer_user_list.append(
                        (individual_sender_user,
                         individual_recipient_user.primary_user, None))
            else:
                for transfer_account_id in recipient_transfer_accounts_ids:
                    try:
                        individual_recipient_user, transfer_card = find_user_with_transfer_account_from_identifiers(
                            None, None, transfer_account_id)
                        transfer_user_list.append(
                            (individual_sender_user, individual_recipient_user,
                             transfer_card))
                    except (NoTransferAccountError, UserNotFoundError) as e:
                        response_list.append({
                            'status': 400,
                            'message': str(e)
                        })

        else:
            batch_uuid = None
            try:
                individual_sender_user, transfer_card = find_user_with_transfer_account_from_identifiers(
                    sender_user_id, sender_public_identifier,
                    sender_transfer_account_id)

                individual_recipient_user, _ = find_user_with_transfer_account_from_identifiers(
                    recipient_user_id, recipient_public_identifier,
                    recipient_transfer_account_id)
                transfer_user_list = [
                    (individual_sender_user, individual_recipient_user,
                     transfer_card)
                ]

            except Exception as e:
                response_object = {
                    'message': str(e),
                }
                return make_response(jsonify(response_object)), 400

        if token_id:
            token = Token.query.get(token_id)
            if not token:
                response_object = {'message': 'Token not found'}
                return make_response(jsonify(response_object)), 404
        else:
            active_organisation = g.active_organisation
            if active_organisation is None:
                response_object = {'message': 'Must provide token_id'}
                return make_response(jsonify(response_object)), 400
            else:
                token = active_organisation.token

        for sender_user, recipient_user, transfer_card in transfer_user_list:
            try:
                if transfer_type == 'PAYMENT':
                    transfer = make_payment_transfer(
                        transfer_amount,
                        token=token,
                        send_user=sender_user,
                        receive_user=recipient_user,
                        transfer_use=transfer_use,
                        transfer_mode=TransferModeEnum.WEB,
                        uuid=uuid,
                        automatically_resolve_complete=False,
                        queue=queue,
                        batch_uuid=batch_uuid,
                        transfer_card=transfer_card)

                elif transfer_type == 'RECLAMATION':
                    transfer = make_payment_transfer(
                        transfer_amount,
                        token=token,
                        send_user=sender_user,
                        uuid=uuid,
                        transfer_subtype=TransferSubTypeEnum.RECLAMATION,
                        transfer_mode=TransferModeEnum.WEB,
                        require_recipient_approved=False,
                        automatically_resolve_complete=False,
                        queue=queue,
                        batch_uuid=batch_uuid)

                elif transfer_type == 'DISBURSEMENT':
                    transfer = make_payment_transfer(
                        transfer_amount,
                        token=token,
                        send_user=g.user,
                        receive_user=recipient_user,
                        uuid=uuid,
                        transfer_subtype=TransferSubTypeEnum.DISBURSEMENT,
                        transfer_mode=TransferModeEnum.WEB,
                        automatically_resolve_complete=False,
                        queue=queue,
                        batch_uuid=batch_uuid)

                elif transfer_type == 'BALANCE':
                    transfer = make_target_balance_transfer(
                        target_balance,
                        recipient_user,
                        uuid=uuid,
                        automatically_resolve_complete=False,
                        transfer_mode=TransferModeEnum.WEB,
                        queue=queue,
                    )
                if auto_resolve:
                    transfer.add_approver_and_resolve_as_completed()

            except (InsufficientBalanceError, AccountNotApprovedError,
                    InvalidTargetBalanceError, BlockchainError,
                    Exception) as e:

                if is_bulk:
                    response_list.append({'status': 400, 'message': str(e)})

                else:
                    db.session.commit()
                    response_object = {'message': str(e)}
                    return make_response(jsonify(response_object)), 400

            else:
                message = 'Transfer Successful' if auto_resolve else 'Transfer Pending. Must be approved.'
                if is_bulk:
                    credit_transfers.append(transfer)
                    response_list.append({'status': 201, 'message': message})

                else:
                    db.session.flush()
                    credit_transfer = credit_transfer_schema.dump(
                        transfer).data

                    response_object = {
                        'message': message,
                        'is_create': True,
                        'data': {
                            'credit_transfer': credit_transfer,
                        }
                    }

                    return make_response(jsonify(response_object)), 201

        db.session.flush()

        message = 'Bulk Transfer Creation Successful' if auto_resolve else 'Bulk Transfer Pending. Must be approved.'
        response_object = {
            'message': message,
            'bulk_responses': response_list,
            'data': {
                'credit_transfers':
                credit_transfers_schema.dump(credit_transfers).data
            }
        }

        return make_response(jsonify(response_object)), 201