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
Beispiel #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
Beispiel #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
Beispiel #4
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
Beispiel #5
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 }
        }
    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
Beispiel #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')
Beispiel #8
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')
Beispiel #9
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
Beispiel #10
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
Beispiel #11
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_for_org = EmailWhitelist.query.filter(func.lower(EmailWhitelist.email)==email).first()
        if email_exists_for_org:
            response_object = {'message': 'Email already on organisation whitelist.'}
            return make_response(jsonify(response_object)), 400

        email_exists = EmailWhitelist.query.filter(func.lower(EmailWhitelist.email)==email)\
            .execution_options(show_all=True).first()
        if email_exists and not email_exists.used:
            response_object = {'message': 'Email already on another organisation whitelist. '
                                          'Please ask user to create an account first. '
                                          'Contact support if issue persists.'}
            return make_response(jsonify(response_object)), 400

        user = User.query.filter(func.lower(User.email)==email).execution_options(show_all=True).first()
        if user:
            user.add_user_to_organisation(organisation, is_admin=True)
            send_invite_email_to_existing_user(organisation, user.email)
            db.session.commit()
            response_object = {
                'message': 'An invite has been sent to an existing user!',
            }

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

        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
Beispiel #12
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
                }
            }
Beispiel #13
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
    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)
    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 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')

        transfer_type = post_data.get('transfer_type')
        transfer_amount = abs(
            round(float(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')

        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')
        credit_transfers = []
        response_list = []
        is_bulk = False

        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:
            response_object = {
                'message': 'Transfer amount must be positive',
            }
            return make_response(jsonify(response_object)), 400

        if recipient_transfer_accounts_ids:
            is_bulk = True

            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 = []
            for transfer_account_id in recipient_transfer_accounts_ids:
                try:
                    individual_sender_user = None
                    individual_recipient_user = find_user_with_transfer_account_from_identifiers(
                        None, None, transfer_account_id)

                    transfer_user_list.append(
                        (individual_sender_user, individual_recipient_user))

                except (NoTransferAccountError, UserNotFoundError) as e:
                    response_list.append({'status': 400, 'message': str(e)})

        else:
            try:
                individual_sender_user = 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)]

            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 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,
                        uuid=uuid,
                        automatically_resolve_complete=auto_resolve)

                elif transfer_type == 'RECLAMATION':
                    transfer = make_payment_transfer(
                        transfer_amount,
                        token=token,
                        send_user=sender_user,
                        uuid=uuid,
                        transfer_subtype=TransferSubTypeEnum.RECLAMATION,
                        require_recipient_approved=False,
                        automatically_resolve_complete=auto_resolve)

                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,
                        automatically_resolve_complete=auto_resolve)

                elif transfer_type == 'BALANCE':
                    transfer = make_target_balance_transfer(
                        target_balance,
                        recipient_user,
                        uuid=uuid,
                        automatically_resolve_complete=auto_resolve)

            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
    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
            }
        }
Beispiel #18
0
    def put(self, disbursement_id):
        put_data = request.get_json()
        action = put_data.get('action', '').upper()
        notes = put_data.get('notes') or ''

        if not disbursement_id:
            return {'message': 'Please provide a disbursement_id'}, 400
        if not action:
            return {'message': 'Please provide an action'}, 400
        if action not in ['APPROVE',
                          'REJECT']:  # We might want more actions later!
            return {'message': f'{action} not a valid action'}, 400

        # Ensure it's impossible to have two threads operating on the same disbursement
        with red.lock(name=f'Disbursemnt{disbursement_id}',
                      timeout=10,
                      blocking_timeout=20):

            disbursement = Disbursement.query.filter(Disbursement.id == disbursement_id)\
                .options(joinedload(Disbursement.credit_transfers))\
                .first()
            disbursement.notes = notes
            if not disbursement:
                return {
                    'message':
                    f'Disbursement with ID \'{disbursement_id}\' not found'
                }, 400

            if disbursement.state in ['APPROVED', 'REJECTED']:
                return {
                    'message':
                    f'Disbursement with ID \'{disbursement_id}\' has already been set to {disbursement.state.lower()}!'
                }, 400

            if action == 'APPROVE':
                disbursement.approve()
                db.session.commit()
                auto_resolve = False
                if current_app.config[
                        'REQUIRE_MULTIPLE_APPROVALS'] or AccessControl.has_sufficient_tier(
                            g.user.roles, 'ADMIN', 'superadmin'):
                    auto_resolve = True
                # A disbursement isn't necessarily approved after approve() is called, since we can require multiple approvers
                task_uuid = None
                if disbursement.state == 'APPROVED':
                    task_uuid = add_after_request_checkable_executor_job(
                        make_transfers,
                        kwargs={
                            'disbursement_id': disbursement.id,
                            'auto_resolve': auto_resolve
                        })

                data = disbursement_schema.dump(disbursement).data
                return {
                    'status': 'success',
                    'data': {
                        'disbursement': data
                    },
                    'task_uuid': task_uuid
                }, 200

            if action == 'REJECT':
                disbursement.reject()
                db.session.commit()

                data = disbursement_schema.dump(disbursement).data

                return {
                    'status': 'success',
                    'data': {
                        'disbursement': data
                    },
                }, 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:

            base_query = TransferAccount.query.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)
Beispiel #20
0
 def is_supervendor(self):
     return AccessControl.has_sufficient_tier(self.roles, 'VENDOR',
                                              'supervendor')
    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 = 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

            transfer_stats = {
                'total_distributed':
                '15300.0000000000000000',
                'total_spent':
                0,
                'total_exchanged':
                0,
                'has_transferred_count':
                0,
                'zero_balance_count':
                0,
                'total_beneficiaries':
                3,
                'total_users':
                3,
                'master_wallet_balance':
                0,
                'daily_transaction_volume': [{
                    'date': '2020-04-29T07:05:01.771621',
                    'volume': 0
                }],
                'daily_disbursement_volume': [{
                    'date':
                    '2020-04-29T00:00:00',
                    'volume': ('300.0000000000000000')
                }, {
                    'date':
                    '2020-04-28T00:00:00',
                    'volume':
                    '15000.0000000000000000'
                }],
                'transfer_use_breakdown': [],
                'last_day_volume': {
                    'date': '2020-04-29T07:05:01.771617',
                    'volume': 0
                },
                'filter_active':
                False
            }

            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