예제 #1
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
예제 #2
0
def extract_headers(request):
    """extracts the user_id from the request header"""
    try:
        user_id = request.headers.get('X-USERID')
        auth_token = request.headers.get('X-AUTH-TOKEN', None)
    except Exception as e:
        log.error('cant extract user_id from header')
        raise InvalidUsage('bad header')
    return user_id, auth_token
예제 #3
0
def update_user_app_version(user_id, app_ver):
    """update the user app version"""
    try:
        userAppData = UserAppData.query.filter_by(user_id=user_id).first()
        userAppData.app_ver = app_ver
        db.session.add(userAppData)
        db.session.commit()
    except Exception as e:
        print(e)
        raise InvalidUsage('cant set user app data')
예제 #4
0
def skip_picture_wait(skip_by):
    try:
        # stored as string, can be None
        system_config = db.session.query(SystemConfig).one()
        system_config.current_picture_index += int(skip_by)
        db.session.add(system_config)
        db.session.commit()
    except Exception as e:
        print(e)
        raise InvalidUsage('cant set task result ts')
예제 #5
0
def blacklist_user_endpoint():
    """"""
    if not config.DEBUG:
        limit_to_acl()
        limit_to_password()

    try:
        payload = request.get_json(silent=True)
        user_id = payload.get('user_id', None)
        if user_id is None:
            raise InvalidUsage('bad-request')
    except Exception as e:
        print(e)
        raise InvalidUsage('bad-request')
    else:
        if blacklist_phone_by_user_id(user_id):
            return jsonify(status='ok')
        else:
            return jsonify(status='error')
예제 #6
0
def user_phone_number_blacklist_endpoint():
    """blacklist a number"""
    if not config.DEBUG:
        limit_to_localhost()

    try:
        payload = request.get_json(silent=True)
        phone_number = payload.get('phone-number', None)
        if phone_number is None:
            print('user_phone_number_blacklist_endpoint: user_phone: %s' %
                  phone_number)
            raise InvalidUsage('bad-request')
    except Exception as e:
        print(e)
        raise InvalidUsage('bad-request')

    if not blacklist_phone_number(phone_number):
        raise InternalError('cant blacklist number')
    return jsonify(status='ok')
예제 #7
0
def add_picture(picture_json, set_active=True):
    """adds an picture to the db"""
    import uuid, json
    from tippicserver.utils import test_url
    print(picture_json)
        
    picture = Picture()
    try:
        picture.picture_id = str(uuid.uuid4())
        picture.title = picture_json['title']
        picture.image_url = picture_json['image_url']
        picture.author = {
            'user_id': picture_json['user_id'],
            'public_address': get_address_by_userid(picture_json['user_id'])
        }
        picture.picture_order_index = get_current_order_index() + 1

        if get_user(picture_json['user_id']) is None:
            raise InvalidUsage('no such user_id')

        if 'skip_image_test' not in picture_json and not test_url(picture_json['image_url']):
            raise InvalidUsage('image url invalid')


        db.session.add(picture)
        db.session.commit()

        user = get_user(picture_json['user_id'])
        if not user.username:
            if not set_username(picture_json['user_id'], picture_json['username']):
                print("username %s already taken" % picture_json['username'])
        else:
            print("user already has a username: %s. Will not set the username to %s "
                  % (user.username, picture_json['username']))
    except Exception as e:
        print(e)
        print('cant add picture to db with picture_id %s' % picture.picture_id)
        return e
    else:
        if set_active:
            set_picture_active(picture.picture_id, True)
        return True
예제 #8
0
def get_block_clients_below_version(os_type):
    sysconfig = get_system_config()
    if not sysconfig:
        #log.error('cant find value for block-clients-below in the db. using default')
        return '0'

    if os_type == OS_ANDROID:
        return sysconfig.block_clients_below_version_android
    elif os_type == OS_IOS:
        return sysconfig.block_clients_below_version_ios
    raise InvalidUsage('no such os_type: %s' % os_type)
예제 #9
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')
예제 #10
0
def update_available_below_version(os_type):
    sysconfig = get_system_config()
    if not sysconfig:
        log.warning(
            'cant find value for update-available-below in the db. using default'
        )
        return '0'

    if os_type == OS_ANDROID:
        return sysconfig.update_available_below_version_android
    elif os_type == OS_IOS:
        return sysconfig.update_available_below_version_ios
    raise InvalidUsage('no such os_type: %s' % os_type)
예제 #11
0
def add_pictures_endpoint():
    """used to add pictures to the db"""
    if not config.DEBUG:
        limit_to_acl()
        limit_to_password()

    payload = request.get_json(silent=True)
    try:
        pictures = payload.get('pictures', None)

    except Exception as e:
        print('exception: %s' % e)
        raise InvalidUsage('bad-request')

    for picture in pictures:
        status = add_picture(picture)
        if status is not True:
            raise InvalidUsage(message='error',
                               payload={
                                   'error field': str(status),
                                   'picture': picture
                               })
    return jsonify(status='ok')
예제 #12
0
def nuke_user_api():
    """internal endpoint used to nuke a user's task and tx data. use with care"""
    if not config.DEBUG:
        limit_to_acl()
        limit_to_password()

    try:
        payload = request.get_json(silent=True)
        phone_number = payload.get('phone_number', None)
        nuke_all = payload.get('nuke_all', False) == True
        if None in (phone_number, ):
            raise InvalidUsage('bad-request')
    except Exception as e:
        print(e)
        raise InvalidUsage('bad-request')

    user_ids = nuke_user_data(phone_number, nuke_all)
    if user_ids is None:
        print('could not find any user with this number: %s' % phone_number)
        return jsonify(status='error', reason='no_user')
    else:
        print('nuked users with phone number: %s and user_ids %s' %
              (phone_number, user_ids))
        return jsonify(status='ok', user_id=user_ids)
예제 #13
0
def set_picture_active(picture_id, is_active):
    """enable/disable picture by offer_id"""
    picture = Picture.query.filter_by(picture_id=picture_id).first()
    if not picture:
        raise InvalidUsage('no such picture_id')
    picture.is_active = is_active
    try:
        db.session.add(picture)
        db.session.commit()
    except Exception as e:
        print(e)
        print('cant set_picture_active with picture_id %s' % picture_id)
        return False

    return True
예제 #14
0
def get_pictures_summery_endpoint():
    """ return a list of shown pictures and tips sum for each"""
    user_id, auth_token = extract_headers(request)
    if user_id is None:
        raise InvalidUsage('invalid payload')

    print('getting picture-summery for userid %s' % user_id)

    # dont serve users with no phone number
    if config.PHONE_VERIFICATION_REQUIRED and not is_user_phone_verified(
            user_id):
        print('blocking user %s from getting tasks: phone not verified' %
              user_id)
        return jsonify(status='denied'), status.HTTP_403_FORBIDDEN
    return jsonify(summery=get_pictures_summery(user_id))
예제 #15
0
def get_block_user_endpoint():
    """ return user's block list """
    user_id, auth_token = extract_headers(request)
    if user_id is None:
        raise InvalidUsage('invalid payload')

    print('getting block for userid %s' % user_id)

    # dont serve users with no phone number
    if config.PHONE_VERIFICATION_REQUIRED and not is_user_phone_verified(
            user_id):
        print('blocking user %s from getting tasks: phone not verified' %
              user_id)
        return jsonify(status='denied'), status.HTTP_403_FORBIDDEN

    return jsonify(get_user_blocked_users(user_id))
예제 #16
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')
예제 #17
0
def unblock_user(user_id, user_id_to_unblock):
    """ add user_id_to_block to user's blocked list """
    try:
        user_app_data = get_user_app_data(user_id)
        # check if user-to-be-block is valid
        user_to_block = get_user(user_id_to_unblock)
        if None in (user_to_block, user_app_data):
            raise InvalidUsage('no such user_id')
    except Exception:
        return False
    else:
        user_app_data.blocked_users.remove(user_id_to_unblock)
        flag_modified(user_app_data, "blocked_users")
        db.session.add(user_app_data)
        db.session.commit()
        return True
예제 #18
0
def set_should_solve_captcha(user_id, value=0):
    """sets the should solve captcha. note that client version isn't checked here

    normally, you would set this value to zero, to show captcha on the next (not current) task.
    setting it to 1 might cause a (temporary) client error, when the client attempts to submit
    a (stale) task w/o a captcha, although one is required. it'll be fine on the next attempt.
    """
    try:

        userAppData = UserAppData.query.filter_by(user_id=user_id).first()
        userAppData.should_solve_captcha_ternary = value
        db.session.add(userAppData)
        db.session.commit()
    except Exception as e:
        print(e)
        raise InvalidUsage('cant set user should_solve_captcha_ternary')
    else:
        return True
예제 #19
0
def app_launch():
    """called whenever the app is launched

        updates the user's last-login time,
        also forwards some config items to the client
    """
    payload = request.get_json(silent=True)
    app_ver, user_id = None, None
    try:
        user_id, auth_token = extract_headers(request)
        app_ver = payload.get('app_ver', None)
    except Exception as e:
        raise InvalidUsage('bad-request')

    update_ip_address(user_id, get_source_ip(request))

    update_user_app_version(user_id, app_ver)

    return jsonify(status='ok', config=get_user_config(user_id))
예제 #20
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
예제 #21
0
def update_ip_address(user_id, ip_address):
    try:
        userAppData = UserAppData.query.filter_by(user_id=user_id).first()
        if userAppData.ip_address == ip_address:
            # nothing to update
            return

        userAppData.ip_address = ip_address
        try:
            userAppData.country_iso_code = app.geoip_reader.get(
                ip_address)['country']['iso_code']
        except Exception as e:
            log.error('could not calc country iso code for %s' % ip_address)
        db.session.add(userAppData)
        db.session.commit()
    except Exception as e:
        log.error('update_ip_address: e: %s' % e)
        raise InvalidUsage('cant set user ip address')
    else:
        log.info('updated user %s ip to %s' % (user_id, ip_address))
예제 #22
0
def get_next_picture():
    """returns current picture for user"""
    user_id, auth_token = extract_headers(request)
    if user_id is None:
        raise InvalidUsage('invalid payload')

    print('getting picture for userid %s' % user_id)

    # dont serve users with no phone number
    if config.PHONE_VERIFICATION_REQUIRED and not is_user_phone_verified(
            user_id):
        print('blocking user %s from getting tasks: phone not verified' %
              user_id)
        return jsonify(reason='denied'), status.HTTP_403_FORBIDDEN

    picture = get_picture_for_user(user_id)
    print('picture returned for user %s: %s' % (user_id, picture))
    if picture.get('blocked', False):
        return jsonify(error="blocked_user")
    return jsonify(picture=picture)
예제 #23
0
def set_user_phone_number(user_id, number):
    """sets a phone number to the user's entry"""
    try:
        user = get_user(user_id)
        encrypted_number = app.encryption.encrypt(number)
        # allow (and ignore) re-submissions of the SAME number, but reject new numbers
        if user.enc_phone_number is not None:
            if user.enc_phone_number == encrypted_number:
                return  # all good, do nothing
            else:
                log.error('refusing to overwrite phone number for user_id %s' %
                          user_id)
                raise InvalidUsage(
                    'trying to overwrite an existing phone number with a different one'
                )
        else:
            user.enc_phone_number = encrypted_number
            db.session.add(user)
            db.session.commit()

    except Exception as e:
        log.error('cant add phone number %s to user_id: %s. Exception: %s' %
                  (number, user_id, e))
        raise
예제 #24
0
def get_user_backup_hints_by_enc_phone(enc_phone_number):
    """return the user backup hints object for the given enc_phone_number or throws exception"""
    ubh = PhoneBackupHints.query.filter_by(enc_phone_number=enc_phone_number).first()
    if not ubh:
        raise InvalidUsage('no such enc_phone_number')
    return ubh
예제 #25
0
def get_user(user_id):
    user = User.query.filter_by(user_id=user_id).first()
    if not user:
        raise InvalidUsage('no such user_id')
    return user
예제 #26
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')
예제 #27
0
def get_user_app_data(user_id):
    user_app_data = UserAppData.query.filter_by(user_id=user_id).first()
    if not user_app_data:
        raise InvalidUsage('no such user_id')
    return user_app_data
예제 #28
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)
예제 #29
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)