Пример #1
0
def add_signature_api():
    """add backend signature to transaction"""
    payload = request.get_json(silent=True)
    try:
        user_id, auth_token = extract_headers(request)
        print('calling /user/add-signature for user_id %s ' % user_id)
        id = payload.get('id', None)
        sender_address = payload.get('sender_address', None)
        recipient_address = payload.get('recipient_address', None)
        amount = payload.get('amount', None)
        transaction = payload.get('transaction', None)
        validation_token = payload.get('validation-token', None)
        print(
            '### adding signature with validation token =  %s and transaction:%s'
            % (validation_token, transaction))
        if None in (user_id, id, sender_address, recipient_address, amount,
                    transaction, validation_token):
            log.error('failed input checks on /user/submit_transaction')
            raise InvalidUsage('bad-request')
    except Exception as e:
        print('exception in /user/submit_transaction e=%s' % e)
        raise InvalidUsage('bad-request')

    if not utils.is_valid_client(user_id, validation_token):
        increment_metric('add-signature-invalid-token')
        raise jsonify(status='denied', reason='invalid token')

    auth_status = authorize(user_id)
    if auth_status != 'authorized':
        return jsonify(status='denied', reason=auth_status)

    tx = add_signature(id, sender_address, recipient_address, int(amount),
                       transaction)

    return jsonify(status='ok', tx=tx)
Пример #2
0
def post_backup_restore():
    """restore the user to the one with the previous private address

    this api is protected by the following means:
     - a phone number can only restore if a previous back was performed
     - a phone number can only restore to a previously owned address
    """
    user_id, auth_token = extract_headers(request)
    #TODO consider adding this if it doesn't break anything
    #if config.AUTH_TOKEN_ENFORCED and not validate_auth_token(user_id, auth_token):
    #    abort(403) #
    try:
        payload = request.get_json(silent=True)
        address = payload.get('address', None)
        if address is None:
            raise InvalidUsage('bad-request')
    except Exception as e:
        print(e)
        raise InvalidUsage('bad-request')
    else:
        user_id = restore_user_by_address(user_id, address)
        if user_id:
            increment_metric('restore-success')
            return jsonify(status='ok', user_id=user_id)
        else:
            increment_metric('restore-failure')
            raise InvalidUsage('cant restore user')
Пример #3
0
def send_kin_with_payment_service(public_address, amount, memo=None):
    """send kins to an address using the payment service"""

    #  sanity:
    if public_address in (None, ''):
        log.error('cant send kin to address: %s' % public_address)
        return False, None

    if amount is None or amount < 1:
        log.error('cant send kin amount: %s' % amount)
        return False, None

    print('sending kin to address: %s' % public_address)
    headers = {
        'X-REQUEST-ID': str(random.randint(1, 1000000))
    }  # doesn't actually matter
    payment_payload = {
        'id': memo,
        'amount': amount,
        'app_id': 'TIPC',
        'recipient_address': public_address,
        'callback': "%s/payments/callback" % config.API_SERVER_URL
    }

    try:
        res = requests.post('%s/payments' % config.PAYMENT_SERVICE_URL,
                            headers=headers,
                            json=payment_payload)
        res.raise_for_status()
    except Exception as e:
        increment_metric('send_kin_error')
        print(
            'caught exception sending %s kin to address %s using the payment service'
            % (amount, public_address))
        print(e)
Пример #4
0
def report_p2p_tx_api():
    """endpoint used by the client to report successful p2p txs"""

    if not config.P2P_TRANSFERS_ENABLED:
        # this api is disabled, clients should not have asked for it
        print('/user/transaction/p2p/add api is disabled by server config')
        raise InvalidUsage('api-disabled')

    payload = request.get_json(silent=True)
    try:
        # TODO Should we verify the tx against the blockchain?
        # TODO this api needs to be secured with auth token
        sender_id, auth_token = extract_headers(request)
        tx_hash = payload.get('tx_hash', None)
        destination_address = payload.get('destination_address', None)
        amount = payload.get('amount', None)
        if None in (tx_hash, sender_id, destination_address, amount):
            raise InvalidUsage('invalid params')

    except Exception as e:
        print('exception: %s' % e)
        raise InvalidUsage('bad-request')
    res, tx_dict = add_p2p_tx(tx_hash, sender_id, destination_address, amount)
    if res:
        # send back the dict with the tx details
        increment_metric('p2p-tx-added')
        return jsonify(status='ok', tx=tx_dict)
    else:
        raise InvalidUsage('failed to add p2ptx')
Пример #5
0
def create_account(public_address, initial_kin_amount):
    """create an account for the given public address"""
    #TODO all repeating logic?
    print('creating account with balance:%s' % initial_kin_amount)
    try:
        return app.kin_account.create_account(
            public_address, starting_balance=initial_kin_amount, fee=0)
    except Exception as e:
        increment_metric('create_account_error')
        print('caught exception creating account for address %s' %
              public_address)
        print(e)
Пример #6
0
def skip_picture_endpoint():
    """advances current_picture_index"""
    if not config.DEBUG:
        limit_to_localhost()

    try:
        payload = request.get_json(silent=True)
        skip_by = payload.get('skip_by', 1)
    except Exception as e:
        print(e)
        raise InvalidUsage('bad-request')
    else:
        skip_picture_wait(skip_by)

    increment_metric('skip-picture-wait')
    return jsonify(status='ok')
Пример #7
0
def ack_auth_token_api():
    """endpoint used by clients to ack the auth-token they received"""
    payload = request.get_json(silent=True)
    try:
        user_id, auth_token = extract_headers(request)
        token = payload.get('token', None)
        if None in (user_id, token):
            raise InvalidUsage('bad-request: invalid input')
    except Exception as e:
        print(e)
        raise InvalidUsage('bad-request')

    if ack_auth_token(user_id, token):
        increment_metric('auth-token-acked')
        return jsonify(status='ok')
    else:
        return jsonify(status='error',
                       reason='wrong-token'), status.HTTP_400_BAD_REQUEST
Пример #8
0
def create_user(user_id, os_type, device_model, push_token, time_zone,
                device_id, app_ver, package_id):
    """create a new user and commit to the database. should only fail if the user_id is duplicate"""
    def parse_timezone(tz):
        """convert -02:00 to -2 or set reasonable default"""
        try:
            return int(tz[:(tz.find(':'))])
        except Exception as e:
            log.error('failed to parse timezone: %s. using default. e: %s' %
                      (tz, e))
            return int(DEFAULT_TIME_ZONE)

    is_new_user = False
    try:
        user = get_user(user_id)
        log.info('user %s already exists, updating data' % user_id)
    except Exception as e:
        user = User()
        is_new_user = True

    user.user_id = user_id
    user.os_type = os_type
    user.device_model = device_model[:DEVICE_MODEL_MAX_SIZE]
    user.push_token = push_token if push_token is not None else user.push_token
    user.time_zone = parse_timezone(time_zone)
    user.device_id = device_id
    user.auth_token = uuid4() if not user.auth_token else user.auth_token
    user.package_id = package_id
    db.session.add(user)
    db.session.commit()

    if is_new_user:
        user_app_data = UserAppData()
        user_app_data.user_id = user_id
        user_app_data.app_ver = app_ver
        db.session.add(user_app_data)
        db.session.commit()

        # get/create an auth token for this user
        get_token_obj_by_user_id(user_id)
    else:
        increment_metric('reregister')

    return is_new_user
Пример #9
0
def extract_tx_payment_data(tx_hash):
    """ensures that the given tx_hash is a valid payment tx,
       and return a dict with the memo, amount and to_address"""
    if tx_hash is None:
        raise InvalidUsage('invalid params')

    # get the tx_hash data. this might take a second,
    # so retry while 'Resource Missing' is recevied
    count = 0
    tx_data = None
    while count < config.STELLAR_TIMEOUT_SEC:
        try:
            tx_data = app.kin_sdk.get_transaction_data(tx_hash)

        except kin.KinErrors.ResourceNotFoundError:
            count = count + 1
            sleep(1)
        else:
            break

    if tx_data is None:
        print('could not get tx_data for tx_hash: %s. waited %s seconds' %
              (tx_hash, count))
        increment_metric('tx_data_timeout')
        return False, {}

    # get the simple op:
    op = tx_data.operation

    # verify op type
    from kin.transactions import OperationTypes
    if op.type != OperationTypes.PAYMENT:
        print('unexpected type: %s' % op.type)
        return False, {}

    # assemble the result dict
    data = {
        'memo': tx_data.memo,
        'amount': op.amount,
        'to_address': op.destination
    }
    return True, data
Пример #10
0
def authorize(user_id):
    if config.AUTH_TOKEN_ENFORCED and not is_user_authenticated(user_id):
        print(
            'user %s is not authenticated. rejecting results submission request'
            % user_id)
        increment_metric('rejected-on-auth')
        return 'auth-failed'

    if config.PHONE_VERIFICATION_REQUIRED and not is_user_phone_verified(
            user_id):
        print('blocking user (%s) results - didnt pass phone_verification' %
              user_id)
        return 'user_phone_not_verified'

    if is_userid_blacklisted(user_id):
        print('blocked user_id %s from booking goods - user_id blacklisted' %
              user_id)
        return 'denied'

    return 'authorized'
Пример #11
0
def email_backup_endpoint():
    """generates an email with the user's backup details and sends it"""
    user_id, auth_token = extract_headers(request)
    if config.AUTH_TOKEN_ENFORCED and not validate_auth_token(
            user_id, auth_token):
        print(
            'received a bad auth token from user_id %s: %s. ignoring for now' %
            (user_id, auth_token))
    if config.AUTH_TOKEN_ENFORCED and not is_user_authenticated(user_id):
        abort(403)
    try:
        payload = request.get_json(silent=True)
        to_address = payload.get('to_address', None)
        enc_key = payload.get('enc_key', None)
        if None in (to_address, enc_key):
            raise InvalidUsage('bad-request')
        # TODO validate email address is legit
    except Exception as e:
        print(e)
        raise InvalidUsage('bad-request')

    #get_template from db, generate email and send with ses
    from .models.email_template import EMAIL_TEMPLATE_BACKUP_NAG_1
    template_dict = get_email_template_by_type(EMAIL_TEMPLATE_BACKUP_NAG_1)
    if not template_dict:
        raise InternalError('cant fetch email template')

    from .send_email import send_mail_with_qr_attachment
    try:
        res = send_mail_with_qr_attachment(template_dict['sent_from'],
                                           [to_address],
                                           template_dict['title'],
                                           template_dict['body'], enc_key)
        print('email result: %s' % res)
        increment_metric('backup-email-sent-success')
    except Exception as e:
        log.error('failed to sent backup email to %s. e:%s' % (to_address, e))
        increment_metric('backup-email-sent-failure')
    #TODO handle errors

    return jsonify(status='ok')
Пример #12
0
def send_kin(public_address, amount, memo=None):
    """send kins to an address"""

    #  sanity:
    if public_address in (None, ''):
        log.error('cant send kin to address: %s' % public_address)
        return False, None

    if amount is None or amount < 1:
        log.error('cant send kin amount: %s' % amount)
        return False, None

    print('sending kin to address: %s' % public_address)
    try:
        return app.kin_account.send_kin(public_address,
                                        amount,
                                        fee=0,
                                        memo_text=memo)
    except Exception as e:
        increment_metric('send_kin_error')
        print('caught exception sending %s kin to address %s' %
              (amount, public_address))
        print(e)
Пример #13
0
def limit_to_acl(return_bool=False):
    """aborts unauthorized requests for sensitive APIs (nginx specific). allow on DEBUG

    the optional 'return_bool' flag governs whether the function aborts the request (default) or
    just returns a boolean.
    """
    source_ip = request.headers.get('X-Forwarded-For', None)
    if not source_ip:
        print('missing expected header')
        if return_bool:
            return False
        increment_metric('not-in-acl')
        abort(403)

    if not is_in_acl(source_ip):
        if return_bool:
            return False
        print('%s is not in ACL, rejecting' % source_ip)
        increment_metric('not-in-acl')
        abort(403)

    if return_bool:
        return True
Пример #14
0
def add_signature(id, sender_address, recipient_address, amount, transaction):
    """add backend signature to transaction"""

    print('adding whitelisted signature for transaction from %s to: %s' %
          (sender_address, recipient_address))
    headers = {
        'X-REQUEST-ID': str(random.randint(1, 1000000))
    }  # doesn't actually matter
    payment_payload = {
        'id': id,
        'sender_address': sender_address,
        'recipient_address': recipient_address,
        'amount': amount,
        'transaction': transaction,
        'app_id': 'TIPC',
        'network_id': config.STELLAR_NETWORK
    }

    try:
        print('posting %s/tx/whitelist payload: %s' %
              (config.PAYMENT_SERVICE_URL, payment_payload))
        result = requests.post('%s/tx/whitelist' % config.PAYMENT_SERVICE_URL,
                               headers=headers,
                               json=payment_payload)
        result.raise_for_status()
        print('result %s ' % result.text)
        print('result %s ' % result.content)
        tx_json = json.loads(result.content.decode("utf-8"))
        print('tx_json %s ' % tx_json)
        print('returning tx= %s ' % tx_json['tx'])
        return tx_json['tx']
    except Exception as e:
        increment_metric('whitelist_error')
        print('caught exception while whitelisting transaction from %s to %s' %
              (sender_address, recipient_address))
        print(e)
Пример #15
0
def set_user_phone_number_endpoint():
    """get the firebase id token and extract the phone number from it"""
    payload = request.get_json(silent=True)
    try:
        user_id, auth_token = extract_headers(request)
        token = payload.get('token', None)
        unverified_phone_number = payload.get('phone_number',
                                              None)  # only used in tests
        if None in (user_id, token):
            raise InvalidUsage('bad-request')

    except Exception as e:
        print(e)
        raise InvalidUsage('bad-request')
    if not config.DEBUG:
        print('extracting verified phone number fom firebase id token...')
        verified_number = extract_phone_number_from_firebase_id_token(token)

        if verified_number is None:
            print('bad id-token: %s' % token)
            return jsonify(status='error',
                           reason='bad_token'), status.HTTP_404_NOT_FOUND

        # reject blacklisted phone prefixes
        for prefix in app.blocked_phone_prefixes:
            if verified_number.find(prefix) == 0:
                os_type = get_user_os_type(user_id)
                print(
                    'found blocked phone prefix (%s) in verified phone number (%s), userid (%s), OS (%s): aborting'
                    % (prefix, verified_number, user_id, os_type))
                abort(403)

        phone = verified_number
    else:  #DEBUG
        # for tests, you can use the unverified number if no token was given
        if token:
            phone = extract_phone_number_from_firebase_id_token(token)

        if not phone:
            print('using un-verified phone number in debug')
            phone = unverified_phone_number.strip().replace('-', '')

        if not phone:
            print('could not extract phone in debug')
            return jsonify(status='error', reason='no_phone_number')

    # limit the number of registrations a single phone number can do, unless they come from the ACL
    if not limit_to_acl(
            return_bool=True) and count_registrations_for_phone_number(
                phone) > int(config.MAX_NUM_REGISTRATIONS_PER_NUMBER) - 1:
        print(
            'rejecting registration from user_id %s and phone number %s - too many re-registrations'
            % (user_id, phone))
        increment_metric("reject-too-many_registrations")
        abort(403)

    print('updating phone number for user %s' % user_id)
    set_user_phone_number(user_id, phone)
    increment_metric('user-phone-verified')

    # return success and the backup hint, if they exist
    hints = get_backup_hints(user_id)
    if config.DEBUG:
        print('restore hints for user_id, phone: %s: %s: %s' %
              (user_id, phone, hints))
    return jsonify(status='ok', hints=hints)
Пример #16
0
def onboard_user():
    """creates a wallet for the user and deposits some xlms there"""
    # input sanity
    try:
        user_id, auth_token = extract_headers(request)
        public_address = request.get_json(silent=True).get(
            'public_address', None)
        if None in (public_address, user_id):
            raise InvalidUsage('bad-request')
    except Exception as e:
        raise InvalidUsage('bad-request')

    # block users with an older version from onboarding. and send them a push message
    if should_block_user_by_client_version(user_id):
        print(
            'blocking + deactivating user %s on onboarding with older version'
            % user_id)
        # send_please_upgrade_push_2([user_id])
        # and also, deactivate the user
        deactivate_user(user_id)

        abort(403)
    elif config.PHONE_VERIFICATION_REQUIRED and not is_user_phone_verified(
            user_id):
        raise InvalidUsage('user isnt phone verified')

    onboarded = is_onboarded(user_id)
    if onboarded is True:
        raise InvalidUsage('user already has an account and has been awarded')
    elif onboarded is None:
        raise InvalidUsage('no such user exists')
    else:
        # create an account, provided none is already being created
        lock = redis_lock.Lock(app.redis, 'address:%s' % public_address)
        if lock.acquire(blocking=False):
            try:
                if not active_account_exists(public_address):
                    print('creating account with address %s and amount %s' %
                          (public_address,
                           config.STELLAR_INITIAL_ACCOUNT_BALANCE))
                    tx_id = create_account(
                        public_address, config.STELLAR_INITIAL_ACCOUNT_BALANCE)
                    if not tx_id:
                        raise InternalError('failed to create account at %s' %
                                            public_address)
                    elif not award_user(user_id, public_address):
                        raise InternalError(
                            'unable to award user with %d Kin' %
                            get_initial_reward())
                elif not award_user(user_id, public_address):
                    raise InternalError('unable to award user with %d Kin' %
                                        get_initial_reward())
            except Exception as e:
                print('exception trying to create account:%s' % e)
                raise InternalError('unable to create account')
            finally:
                lock.release()
        else:
            raise InvalidUsage(
                'already creating account for user_id: %s and address: %s' %
                (user_id, public_address))

        increment_metric('user_onboarded')
        return jsonify(status='ok')
Пример #17
0
def register_api():
    """ register a user to the system
    called once by every client until 200OK is received from the server.
    the payload may contain a optional push token.

    this function may be called by the client multiple times to update fields
    """
    payload = request.get_json(silent=True)
    try:
        # add redis lock here?
        user_id = payload.get('user_id', None)
        os = payload.get('os', None)
        device_model = payload.get('device_model', None)

        time_zone = payload.get('time_zone', None)
        device_id = payload.get('device_id', None)
        app_ver = payload.get('app_ver', None)
        # optionals
        token = payload.get('token', None)
        package_id = payload.get('package_id', None)
        if None in (
                user_id, os, device_model, time_zone, app_ver
        ):  # token is optional, device-id is required but may be None
            raise InvalidUsage('bad-request')

        if os not in (utils.OS_ANDROID, utils.OS_IOS):
            raise InvalidUsage('bad-request')

        if 'Genymotion'.upper() in device_model.upper(
        ):  # block android emulator
            print('refusing to register Genymotion devices. user_id %s' %
                  user_id)
            raise InvalidUsage('bad-request')

        user_id = UUID(user_id)  # throws exception on invalid uuid
    except Exception as e:
        raise InvalidUsage('bad-request')
    else:
        try:
            new_user_created = create_user(user_id, os, device_model, token,
                                           time_zone, device_id, app_ver,
                                           package_id)
        except InvalidUsage as e:
            raise InvalidUsage('duplicate-userid')
        else:
            if new_user_created:
                print('created user with user_id %s' % user_id)
                increment_metric('user_registered')
            else:
                print('updated userid %s data' % user_id)

            #TODO find a way to dry up this code which is redundant with get_user_config()

            # turn off phone verfication for older clients:
            disable_phone_verification = False
            disable_backup_nag = False
            if os == OS_ANDROID and LooseVersion(app_ver) <= LooseVersion(
                    config.BLOCK_ONBOARDING_ANDROID_VERSION):
                disable_phone_verification = True
                disable_backup_nag = True
            elif os == OS_IOS and LooseVersion(app_ver) <= LooseVersion(
                    config.BLOCK_ONBOARDING_IOS_VERSION):
                disable_phone_verification = True
                disable_backup_nag = True

            global_config = get_global_config()
            if disable_phone_verification:
                print(
                    'disabling phone verification for registering userid %s' %
                    user_id)
                global_config['phone_verification_enabled'] = False
            if disable_backup_nag:
                print('disabling backup nag for registering userid %s' %
                      user_id)
                global_config['backup_nag'] = False

            # if should_force_update(os, app_ver):
            #     global_config['force_update'] = True

            # if is_update_available(os, app_ver):
            #     global_config['is_update_available'] = True

            # return global config - the user doesn't have user-specific config (yet)
            return jsonify(status='ok', config=global_config)
Пример #18
0
def payment_service_callback_endpoint():
    """an endpoint for the payment service."""
    payload = request.get_json(silent=True)
    print(payload)  #TODO remove eventually

    try:
        action = payload.get('action', None)
        obj = payload.get('object', None)
        state = payload.get('state', None)
        val = payload.get('value', None)

        if None in (action, obj, state, val):
            print(
                'should never happen: cant process payment service callback: %s'
                % payload)
            increment_metric('payment-callback-error')
            return jsonify(status='error', reason='internal_error')

        #  process payment:
        if action == 'send' and obj == 'payment':
            if state == 'success':
                memo = val.get('id', None)
                tx_hash = val.get('transaction_id', None)
                amount = val.get('amount', None)
                payment_ts = payload.get('timestamp', None)
                public_address = val.get('sender_address')
                if None in (memo, tx_hash, amount):
                    print(
                        'should never happen: cant process successful payment callback: %s'
                        % payload)
                    increment_metric('payment-callback-error')
                    return jsonify(status='error', reason='internal_error')

                # retrieve the user_id and task_id from the cache
                user_id, task_id, request_timestamp, send_push = read_payment_data_from_cache(
                    memo)

                # compare the timestamp from the callback with the one from the original request, and
                # post as a gauge  metric for tracking
                try:
                    request_duration_sec = arrow.get(payment_ts) - arrow.get(
                        request_timestamp)
                    request_duration_sec = int(
                        request_duration_sec.total_seconds())
                    print('payment request for tx_hash: %s took %s seconds' %
                          (tx_hash, request_duration_sec))
                    gauge_metric('payment-req-dur', request_duration_sec)
                except Exception as e:
                    log.error(
                        'failed to calculate payment request duration. e=%s' %
                        e)

                create_tx(tx_hash, user_id, public_address, False, amount, {
                    'task_id': task_id,
                    'memo': memo
                })
                increment_metric('payment-callback-success')
                #
                # if tx_hash and send_push:
                #     send_push_tx_completed(user_id, tx_hash, amount, task_id, memo)

                try:
                    redis_lock.Lock(app.redis,
                                    get_payment_lock_name(user_id,
                                                          task_id)).release()
                except Exception as e:
                    log.error(
                        'failed to release payment lock for user_id %s and task_id %s'
                        % (user_id, task_id))

            else:
                print('received failed tx from the payment service: %s' %
                      payload)
                #TODO implement some retry mechanism here
                increment_metric('payment-callback-failed')
        else:
            print(
                'should never happen: unhandled callback from the payment service: %s'
                % payload)

    except Exception as e:

        increment_metric('payment-callback-error')
        log.error('failed processing the payment service callback')
        print(e)
        return jsonify(status='error', reason='internal_error')

    return jsonify(status='ok')