Ejemplo n.º 1
0
def fxa_activity(request):
    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'fxa-activity requires a valid API-key',
                'code': errors.BASKET_AUTH_ERROR,
            }, 401)

    data = json.loads(request.body)
    if 'fxa_id' not in data:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'fxa-activity requires a Firefox Account ID',
                'code': errors.BASKET_USAGE_ERROR,
            }, 401)
    if 'user_agent' not in data:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'fxa-activity requires a device user-agent',
                'code': errors.BASKET_USAGE_ERROR,
            }, 401)

    add_fxa_activity.delay(data)
    return HttpResponseJSON({'status': 'ok'})
Ejemplo n.º 2
0
def send_recovery_message(request):
    """
    Send a recovery message to an email address.

    required form parameter: email

    If email not provided or not syntactically correct, returns 400.
    If email not known, returns 404.
    Otherwise, queues a task to send the message and returns 200.
    """
    email = process_email(request.POST.get("email"))
    if not email:
        return invalid_email_response()

    if email_is_blocked(email):
        # don't let on there's a problem
        return HttpResponseJSON({"status": "ok"})

    try:
        user_data = get_user_data(email=email)
    except NewsletterException as e:
        return newsletter_exception_response(e)

    if not user_data:
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "Email address not known",
                "code": errors.BASKET_UNKNOWN_EMAIL,
            },
            404,
        )  # Note: Bedrock looks for this 404

    send_recovery_message_task.delay(email)
    return HttpResponseJSON({"status": "ok"})
Ejemplo n.º 3
0
def common_voice_goals(request):
    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'requires a valid API-key',
                'code': errors.BASKET_AUTH_ERROR,
            }, 401)

    form = CommonVoiceForm(request.POST)
    if form.is_valid():
        # don't send empty values and use ISO formatted date strings
        data = {
            k: v
            for k, v in form.cleaned_data.items() if not (v == '' or v is None)
        }
        record_common_voice_goals.delay(data)
        return HttpResponseJSON({'status': 'ok'})
    else:
        # form is invalid
        return HttpResponseJSON(
            {
                'status': 'error',
                'errors': format_form_errors(form.errors),
                'errors_by_field': form.errors,
            }, 400)
Ejemplo n.º 4
0
def fxa_concerts_rsvp(request):
    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'requires a valid API-key',
                'code': errors.BASKET_AUTH_ERROR,
            }, 401)

    fields = ('email', 'is_firefox', 'campaign_id')
    data = request.POST.dict()
    if not all(f in data for f in fields):
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'missing required field',
                'code': errors.BASKET_USAGE_ERROR,
            }, 401)

    is_firefox = 'Y' if data['is_firefox'][0].upper() == 'Y' else 'N'
    record_fxa_concerts_rsvp.delay(
        email=data['email'],
        is_firefox=is_firefox,
        campaign_id=data['campaign_id'],
    )
    return HttpResponseJSON({'status': 'ok'})
Ejemplo n.º 5
0
def amo_sync(request, post_type):
    if post_type not in AMO_SYNC_TYPES:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'API URL not found',
                'code': errors.BASKET_USAGE_ERROR,
            }, 404)

    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'requires a valid API-key',
                'code': errors.BASKET_AUTH_ERROR,
            }, 401)

    try:
        data = json.loads(request.body)
    except ValueError:
        statsd.incr(f'amo_sync.{post_type}.message.json_error')
        sentry_client.captureException(
            data={'extra': {
                'request.body': request.body
            }})

        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'JSON error',
                'code': errors.BASKET_USAGE_ERROR,
            }, 400)

    AMO_SYNC_TYPES[post_type].delay(data)
    return HttpResponseJSON({'status': 'ok'})
Ejemplo n.º 6
0
def fxa_concerts_rsvp(request):
    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "requires a valid API-key",
                "code": errors.BASKET_AUTH_ERROR,
            },
            401,
        )

    fields = ("email", "is_firefox", "campaign_id")
    data = request.POST.dict()
    if not all(f in data for f in fields):
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "missing required field",
                "code": errors.BASKET_USAGE_ERROR,
            },
            401,
        )

    is_firefox = "Y" if data["is_firefox"][0].upper() == "Y" else "N"
    record_fxa_concerts_rsvp.delay(
        email=data["email"],
        is_firefox=is_firefox,
        campaign_id=data["campaign_id"],
    )
    return HttpResponseJSON({"status": "ok"})
Ejemplo n.º 7
0
def subscribe_sms(request):
    mobile = request.POST.get("mobile_number")
    if not mobile:
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "mobile_number is missing",
                "code": errors.BASKET_USAGE_ERROR,
            },
            400,
        )

    country = request.POST.get("country", "us")
    language = request.POST.get("lang", "en-US")
    msg_name = request.POST.get("msg_name", "SMS_Android")
    vendor_id = get_sms_vendor_id(msg_name, country, language)
    if not vendor_id:
        if language != "en-US":
            # if not available in the requested language, try the default
            language = "en-US"
            vendor_id = get_sms_vendor_id(msg_name, country, language)

    if not vendor_id:
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "Invalid msg_name + country + language",
                "code": errors.BASKET_USAGE_ERROR,
            },
            400,
        )

    mobile = parse_phone_number(mobile, country)
    if not mobile:
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "mobile_number is invalid",
                "code": errors.BASKET_USAGE_ERROR,
            },
            400,
        )

    # only rate limit numbers here so we don't rate limit errors.
    if is_ratelimited(
            request,
            group="basket.news.views.subscribe_sms",
            key=lambda x, y: "%s-%s" % (msg_name, mobile),
            rate=PHONE_NUMBER_RATE_LIMIT,
            increment=True,
    ):
        raise Ratelimited()

    optin = request.POST.get("optin", "N") == "Y"

    add_sms_user.delay(msg_name, mobile, optin, vendor_id=vendor_id)
    return HttpResponseJSON({"status": "ok"})
Ejemplo n.º 8
0
def custom_unsub_reason(request):
    """Update the reason field for the user, which logs why the user
    unsubscribed from all newsletters."""

    if 'token' not in request.POST or 'reason' not in request.POST:
        return HttpResponseJSON({
            'status': 'error',
            'desc': 'custom_unsub_reason requires the `token` '
                    'and `reason` POST parameters',
            'code': errors.BASKET_USAGE_ERROR,
        }, 400)

    update_custom_unsub.delay(request.POST['token'], request.POST['reason'])
    return HttpResponseJSON({'status': 'ok'})
Ejemplo n.º 9
0
def subscribe_sms(request):
    mobile = request.POST.get('mobile_number')
    if not mobile:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'mobile_number is missing',
                'code': errors.BASKET_USAGE_ERROR,
            }, 400)

    country = request.POST.get('country', 'us')
    language = request.POST.get('lang', 'en-US')
    msg_name = request.POST.get('msg_name', 'SMS_Android')
    vendor_id = get_sms_vendor_id(msg_name, country, language)
    if not vendor_id:
        if language != 'en-US':
            # if not available in the requested language, try the default
            language = 'en-US'
            vendor_id = get_sms_vendor_id(msg_name, country, language)

    if not vendor_id:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'Invalid msg_name + country + language',
                'code': errors.BASKET_USAGE_ERROR,
            }, 400)

    mobile = parse_phone_number(mobile, country)
    if not mobile:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'mobile_number is invalid',
                'code': errors.BASKET_USAGE_ERROR,
            }, 400)

    # only rate limit numbers here so we don't rate limit errors.
    if is_ratelimited(request,
                      group='basket.news.views.subscribe_sms',
                      key=lambda x, y: '%s-%s' % (msg_name, mobile),
                      rate=PHONE_NUMBER_RATE_LIMIT,
                      increment=True):
        raise Ratelimited()

    optin = request.POST.get('optin', 'N') == 'Y'

    add_sms_user.delay(msg_name, mobile, optin, vendor_id=vendor_id)
    return HttpResponseJSON({'status': 'ok'})
Ejemplo n.º 10
0
def respond_error(request,
                  form,
                  message,
                  code,
                  template_name="news/formerror.html"):
    """
    Return either a JSON or HTML error response

    @param request: the request
    @param form: the bound form object
    @param message: the error message
    @param code: the HTTP status code
    @param template_name: the template name in case of HTML response
    @return: HttpResponse object
    """
    if request.is_ajax():
        return HttpResponseJSON(
            {
                "status": "error",
                "errors": [message],
                "errors_by_field": {
                    NON_FIELD_ERRORS: [message]
                },
            },
            code,
        )
    else:
        form.add_error(None, message)
        return render(request, template_name, {"form": form}, status=code)
Ejemplo n.º 11
0
def user_meta(request, token):
    """Only update user metadata, not newsletters"""
    form = UpdateUserMeta(request.POST)
    if form.is_valid():
        # don't send empty values
        data = {k: v for k, v in form.cleaned_data.items() if v}
        # don't change subscriber status
        data['_set_subscriber'] = False
        update_user_meta.delay(token, data)
        return HttpResponseJSON({'status': 'ok'})

    return HttpResponseJSON({
        'status': 'error',
        'desc': 'data is invalid',
        'code': errors.BASKET_USAGE_ERROR,
    }, 400)
Ejemplo n.º 12
0
def confirm(request, token):
    if is_ratelimited(request, group='basket.news.views.confirm',
                      key=lambda x, y: token,
                      rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True):
        raise Ratelimited()
    confirm_user.delay(token, start_time=time())
    return HttpResponseJSON({'status': 'ok'})
Ejemplo n.º 13
0
def debug_user(request):
    return HttpResponseJSON(
        {
            'status': 'error',
            'desc': 'method removed. use lookup-user and an API key.',
            'code': errors.BASKET_USAGE_ERROR,
        }, 404)
Ejemplo n.º 14
0
def custom_unsub_reason(request):
    """Update the reason field for the user, which logs why the user
    unsubscribed from all newsletters."""

    if "token" not in request.POST or "reason" not in request.POST:
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "custom_unsub_reason requires the `token` "
                "and `reason` POST parameters",
                "code": errors.BASKET_USAGE_ERROR,
            },
            400,
        )

    update_custom_unsub.delay(request.POST["token"], request.POST["reason"])
    return HttpResponseJSON({"status": "ok"})
Ejemplo n.º 15
0
def invalid_email_response():
    resp_data = {
        'status': 'error',
        'code': errors.BASKET_INVALID_EMAIL,
        'desc': 'Invalid email address',
    }
    statsd.incr('news.views.invalid_email_response')
    return HttpResponseJSON(resp_data, 400)
Ejemplo n.º 16
0
def invalid_email_response():
    resp_data = {
        "status": "error",
        "code": errors.BASKET_INVALID_EMAIL,
        "desc": "Invalid email address",
    }
    statsd.incr("news.views.invalid_email_response")
    return HttpResponseJSON(resp_data, 400)
Ejemplo n.º 17
0
def debug_user(request):
    return HttpResponseJSON(
        {
            "status": "error",
            "desc": "method removed. use lookup-user and an API key.",
            "code": errors.BASKET_USAGE_ERROR,
        },
        404,
    )
Ejemplo n.º 18
0
def subscribe_main(request):
    """Subscription view for use with client side JS"""
    form = SubscribeForm(request.POST)
    if form.is_valid():
        data = form.cleaned_data

        if email_is_blocked(data['email']):
            statsd.incr('news.views.subscribe_main.email_blocked')
            # don't let on there's a problem
            return respond_ok(request, data)

        data['format'] = data.pop('fmt') or 'H'

        if data['lang']:
            if not language_code_is_valid(data['lang']):
                data['lang'] = 'en'
        # if lang not provided get the best one from the accept-language header
        else:
            lang = get_best_request_lang(request)
            if lang:
                data['lang'] = lang
            else:
                del data['lang']

        # if source_url not provided we should store the referrer header
        # NOTE this is not a typo; Referrer is misspelled in the HTTP spec
        # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.36
        if not data['source_url'] and request.META.get('HTTP_REFERER'):
            referrer = request.META['HTTP_REFERER']
            if SOURCE_URL_RE.match(referrer):
                statsd.incr('news.views.subscribe_main.use_referrer')
                data['source_url'] = referrer

        if is_ratelimited(request, group='basket.news.views.subscribe_main',
                          key=lambda x, y: '%s-%s' % (':'.join(data['newsletters']), data['email']),
                          rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True):
            statsd.incr('subscribe.ratelimited')
            return respond_error(request, form, 'Rate limit reached', 429)

        try:
            upsert_user.delay(SUBSCRIBE, data, start_time=time())
        except Exception:
            return respond_error(request, form, 'Unknown error', 500)

        return respond_ok(request, data)

    else:
        # form is invalid
        if request.is_ajax():
            return HttpResponseJSON({
                'status': 'error',
                'errors': format_form_errors(form.errors),
                'errors_by_field': form.errors,
            }, 400)
        else:
            return render(request, 'news/formerror.html', {'form': form}, status=400)
Ejemplo n.º 19
0
def ratelimited(request, e):
    parts = [x.strip() for x in request.path.split('/') if x.strip()]
    # strip out tokens in the urls
    parts = [x for x in parts if not is_token(x)]
    statsd.incr('.'.join(parts + ['ratelimited']))
    return HttpResponseJSON({
        'status': 'error',
        'desc': 'rate limit reached',
        'code': errors.BASKET_USAGE_ERROR,
    }, 429)
Ejemplo n.º 20
0
def common_voice_goals(request):
    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "requires a valid API-key",
                "code": errors.BASKET_AUTH_ERROR,
            },
            401,
        )

    form = CommonVoiceForm(request.POST)
    if form.is_valid():
        # don't send empty values and use ISO formatted date strings
        data = {
            k: v
            for k, v in form.cleaned_data.items() if not (v == "" or v is None)
        }
        if settings.COMMON_VOICE_BATCH_UPDATES:
            if settings.READ_ONLY_MODE:
                api_key = request.META["HTTP_X_API_KEY"]
                # forward to basket with r/w DB
                requests.post(
                    f"{settings.BASKET_RW_URL}/news/common-voice-goals/",
                    data=request.POST,
                    headers={"x-api-key": api_key},
                )
            else:
                CommonVoiceUpdate.objects.create(data=data)
        else:
            record_common_voice_update.delay(data)

        return HttpResponseJSON({"status": "ok"})
    else:
        # form is invalid
        return HttpResponseJSON(
            {
                "status": "error",
                "errors": format_form_errors(form.errors),
                "errors_by_field": form.errors,
            },
            400,
        )
Ejemplo n.º 21
0
def newsletters(request):
    # Get the newsletters as a dictionary of dictionaries that are
    # easily jsonified
    result = {}
    for newsletter in Newsletter.objects.all().values():
        newsletter["languages"] = newsletter["languages"].split(",")
        result[newsletter["slug"]] = newsletter
        del newsletter["id"]  # caller doesn't need to know our pkey
        del newsletter["slug"]  # or our slug

    return HttpResponseJSON({"status": "ok", "newsletters": result})
Ejemplo n.º 22
0
def user_meta(request, token):
    """Only update user metadata, not newsletters"""
    token = str(token)
    form = UpdateUserMeta(request.POST)
    if form.is_valid():
        # don't send empty values
        data = {k: v for k, v in form.cleaned_data.items() if v}
        # don't change subscriber status
        data["_set_subscriber"] = False
        update_user_meta.delay(token, data)
        return HttpResponseJSON({"status": "ok"})

    return HttpResponseJSON(
        {
            "status": "error",
            "desc": "data is invalid",
            "code": errors.BASKET_USAGE_ERROR,
        },
        400,
    )
Ejemplo n.º 23
0
def confirm(request, token):
    token = str(token)
    if is_ratelimited(
            request,
            group="basket.news.views.confirm",
            key=lambda x, y: token,
            rate=EMAIL_SUBSCRIBE_RATE_LIMIT,
            increment=True,
    ):
        raise Ratelimited()
    confirm_user.delay(token, start_time=time())
    return HttpResponseJSON({"status": "ok"})
Ejemplo n.º 24
0
def fxa_register(request):
    if settings.FXA_EVENTS_QUEUE_ENABLE:
        # When this setting is true these requests will be handled by
        # a queue via which we receive various events from FxA. See process_fxa_queue.py.
        # This is still here to avoid errors during the transition to said queue.
        # TODO remove after complete transistion to queue
        return HttpResponseJSON({'status': 'ok'})

    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'fxa-register requires a valid API-key',
                'code': errors.BASKET_AUTH_ERROR,
            }, 401)

    data = request.POST.dict()
    if 'email' not in data:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'fxa-register requires an email address',
                'code': errors.BASKET_USAGE_ERROR,
            }, 401)

    email = process_email(data['email'])
    if not email:
        return invalid_email_response()

    if 'fxa_id' not in data:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'fxa-register requires a Firefox Account ID',
                'code': errors.BASKET_USAGE_ERROR,
            }, 401)
    if 'accept_lang' not in data:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'fxa-register requires accept_lang',
                'code': errors.BASKET_USAGE_ERROR,
            }, 401)

    lang = get_best_language(get_accept_languages(data['accept_lang']))
    if lang is None:
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'invalid language',
                'code': errors.BASKET_INVALID_LANGUAGE,
            }, 400)

    update_fxa_info.delay(email, lang, data['fxa_id'])
    return HttpResponseJSON({'status': 'ok'})
Ejemplo n.º 25
0
def amo_sync(request, post_type):
    if post_type not in AMO_SYNC_TYPES:
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "API URL not found",
                "code": errors.BASKET_USAGE_ERROR,
            },
            404,
        )

    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "requires a valid API-key",
                "code": errors.BASKET_AUTH_ERROR,
            },
            401,
        )

    try:
        data = json.loads(request.body)
    except ValueError:
        statsd.incr(f"amo_sync.{post_type}.message.json_error")
        with sentry_sdk.configure_scope() as scope:
            scope.set_extra("request.body", request.body)
            sentry_sdk.capture_exception()

        return HttpResponseJSON(
            {
                "status": "error",
                "desc": "JSON error",
                "code": errors.BASKET_USAGE_ERROR,
            },
            400,
        )

    AMO_SYNC_TYPES[post_type].delay(data)
    return HttpResponseJSON({"status": "ok"})
Ejemplo n.º 26
0
def respond_ok(request, data, template_name="news/thankyou.html"):
    """
    Return either a JSON or HTML success response

    @param request: the request
    @param data: the incoming request data
    @param template_name: the template name in case of HTML response
    @return: HttpResponse object
    """
    if request.is_ajax():
        return HttpResponseJSON({"status": "ok"})
    else:
        return render(request, template_name, data)
Ejemplo n.º 27
0
def ratelimited(request, e):
    parts = [x.strip() for x in request.path.split("/") if x.strip()]
    # strip out tokens in the urls
    parts = [x for x in parts if not is_token(x)]
    statsd.incr(".".join(parts + ["ratelimited"]))
    return HttpResponseJSON(
        {
            "status": "error",
            "desc": "rate limit reached",
            "code": errors.BASKET_USAGE_ERROR,
        },
        429,
    )
Ejemplo n.º 28
0
def subhub_post(request):
    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'requires a valid API-key',
                'code': errors.BASKET_AUTH_ERROR,
            }, 401)

    try:
        data = json.loads(request.body)
    except ValueError:
        statsd.incr('subhub_post.message.json_error')
        sentry_client.captureException(
            data={'extra': {
                'request.body': request.body
            }})

        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'JSON error',
                'code': errors.BASKET_USAGE_ERROR,
            }, 400)
    else:
        etype = data['event_type']
        processor = SUBHUB_EVENT_TYPES.get(etype)

        if processor:
            processor.delay(data)
            return HttpResponseJSON({'status': 'ok'})
        else:
            return HttpResponseJSON(
                {
                    'desc': 'unknown event type',
                    'status': 'error',
                    'code': errors.BASKET_USAGE_ERROR
                }, 400)
Ejemplo n.º 29
0
def common_voice_goals(request):
    if not has_valid_api_key(request):
        return HttpResponseJSON(
            {
                'status': 'error',
                'desc': 'requires a valid API-key',
                'code': errors.BASKET_AUTH_ERROR,
            }, 401)

    form = CommonVoiceForm(request.POST)
    if form.is_valid():
        # don't send empty values and use ISO formatted date strings
        data = {
            k: v
            for k, v in form.cleaned_data.items() if not (v == '' or v is None)
        }
        if settings.COMMON_VOICE_BATCH_UPDATES:
            if settings.READ_ONLY_MODE:
                api_key = request.META['HTTP_X_API_KEY']
                # forward to basket with r/w DB
                requests.post(
                    f'{settings.BASKET_RW_URL}/news/common-voice-goals/',
                    data=request.POST,
                    headers={'x-api-key': api_key})
            else:
                CommonVoiceUpdate.objects.create(data=data)
        else:
            record_common_voice_update.delay(data)

        return HttpResponseJSON({'status': 'ok'})
    else:
        # form is invalid
        return HttpResponseJSON(
            {
                'status': 'error',
                'errors': format_form_errors(form.errors),
                'errors_by_field': form.errors,
            }, 400)
Ejemplo n.º 30
0
def newsletters(request):
    # Get the newsletters as a dictionary of dictionaries that are
    # easily jsonified
    result = {}
    for newsletter in Newsletter.objects.all().values():
        newsletter['languages'] = newsletter['languages'].split(",")
        result[newsletter['slug']] = newsletter
        del newsletter['id']  # caller doesn't need to know our pkey
        del newsletter['slug']  # or our slug

    return HttpResponseJSON({
        'status': 'ok',
        'newsletters': result,
    })