Example #1
0
    def test_et_error(self, look_for_user_mock):
        # Error calling Exact Target, return error code
        err_msg = 'Stuffs broke yo.'
        look_for_user_mock.side_effect = NewsletterException(err_msg)
        with self.assertRaises(NewsletterException) as exc_manager:
            get_user_data()

        exc = exc_manager.exception
        self.assertEqual(str(exc), err_msg)
        self.assertEqual(exc.error_code, errors.BASKET_NETWORK_FAILURE)
        self.assertEqual(exc.status_code, 400)
Example #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.
    """
    try:
        validate_email(request.POST.get('email'))
    except EmailValidationError as e:
        return invalid_email_response(e)

    email = request.POST.get('email')
    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'})
Example #3
0
    def check_get_user(self, master, optin, confirm, error, expected_result):
        """
        Call get_user_data with the given conditions and verify
        that the return value matches the expected result.
        The expected result can include `ANY` values for don't-cares.

        :param master: What should be returned if we query the master DB
        :param optin: What should be returned if we query the opt-in DB
        :param confirm: What should be returned if we query the confirmed DB
        :param error: Exception to raise
        :param expected_result: Expected return value of get_user_data, or
            expected exception raised if any
        """

        # Use this method to mock look_for_user so that we can return
        # different values given the input arguments
        def mock_look_for_user(database, email, token, fields):
            if error:
                raise error
            if database == settings.EXACTTARGET_DATA:
                return master
            elif database == settings.EXACTTARGET_OPTIN_STAGE:
                return optin
            elif database == settings.EXACTTARGET_CONFIRMATION:
                return optin
            else:
                raise Exception("INVALID INPUT TO mock_look_for_user - "
                                "database %r unknown" % database)

        with patch('news.utils.look_for_user') as look_for_user_mock:
            look_for_user_mock.side_effect = mock_look_for_user
            result = get_user_data()

        self.assertEqual(expected_result, result)
Example #4
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'})
Example #5
0
 def test_look_for_user_called_correctly_confirmation(self, mock_look_for_user):
     """
     If user is looked for by email, is not in master
     but is in opt_in, make sure confirmation is called
     correctly, and that if not in confirmation return a "pending" state.
     """
     mock_look_for_user.side_effect = [None, {"token": "dude"}, None]
     result = get_user_data(email="*****@*****.**")
     self.assertTrue(result["pending"])
     self.assertFalse(result["master"])
     self.assertFalse(result["confirmed"])
     # must be called with token returned from previous call only
     mock_look_for_user.assert_called_with(settings.EXACTTARGET_CONFIRMATION, None, "dude", ["Token"])
Example #6
0
def upsert_user(api_call_type, data):
    """
    Update or insert (upsert) a contact record in SFDC

    @param int api_call_type: What kind of API call it was. Could be
        SUBSCRIBE, UNSUBSCRIBE, or SET.
    @param dict data: POST data from the form submission
    @return:
    """
    key = data.get('email') or data.get('token')
    get_lock(key)
    upsert_contact(api_call_type, data,
                   get_user_data(data.get('token'), data.get('email'),
                                 extra_fields=['id']))
Example #7
0
def send_recovery_message_task(email):
    user_data = get_user_data(email=email)
    if not user_data:
        log.debug("In send_recovery_message_task, email not known: %s" % email)
        return

    # make sure we have a language and format, no matter what ET returned
    lang = user_data.get('lang', 'en') or 'en'
    format = user_data.get('format', 'H') or 'H'

    if lang not in settings.RECOVER_MSG_LANGS:
        lang = 'en'

    message_id = mogrify_message_id(RECOVERY_MESSAGE_ID, lang, format)
    send_message.delay(message_id, email, user_data['token'], format)
Example #8
0
def send_recovery_message_task(email):
    user_data = get_user_data(email=email, extra_fields=['id'])
    if not user_data:
        log.debug("In send_recovery_message_task, email not known: %s" % email)
        return

    # make sure we have a language and format, no matter what ET returned
    lang = user_data.get('lang', 'en') or 'en'
    format = user_data.get('format', 'H') or 'H'

    if lang not in settings.RECOVER_MSG_LANGS:
        lang = 'en'

    message_id = mogrify_message_id(RECOVERY_MESSAGE_ID, lang, format)
    send_message.delay(message_id, email, user_data['id'], token=user_data['token'])
Example #9
0
def update_phonebook(data, token):
    user_data = get_user_data(token=token)
    if not user_data:
        # no user with that token
        return

    record = {
        'EMAIL_ADDRESS': user_data['email'],
        'TOKEN': token,
    }
    if 'city' in data:
        record['CITY'] = data['city']
    if 'country' in data:
        record['COUNTRY'] = data['country']

    record.update((k, v) for k, v in data.items() if k in PHONEBOOK_GROUPS)

    sfmc.update_row('PHONEBOOK', record)
Example #10
0
def send_recovery_message_task(email):
    # Have to import here to avoid circular import - that means that for
    # testing, this can't be mocked. Mock look_for_user instead.
    from news.views import get_user_data

    # We should check ET so we can get format and lang if they exist.
    # If they don't exist, then we can create a basket subscriber.

    user_data = get_user_data(email=email, sync_data=True)
    if not user_data:
        log.warn("In send_recovery_message_task, email not known: %s" % email)
        return

    # make sure we have a language and format, no matter what ET returned
    lang = user_data.get('lang', 'en') or 'en'
    format = user_data.get('format', 'H') or 'H'

    message_id = mogrify_message_id(RECOVERY_MESSAGE_ID, lang, format)
    send_message.delay(message_id, email, user_data['token'], format)
Example #11
0
def sfdc_add_update(update_data, user_data=None):
    # for use with maintenance mode only
    # TODO remove after maintenance is over and queue is processed
    if user_data:
        sfdc.update(user_data, update_data)
    else:
        try:
            sfdc.add(update_data)
        except sfapi.SalesforceMalformedRequest as e:  # noqa
            # possibly a duplicate email. try the update below.
            user_data = get_user_data(email=update_data['email'], extra_fields=['id'])
            if user_data:
                # we have a user, delete generated token
                # and continue with an update
                update_data.pop('token', None)
                sfdc.update(user_data, update_data)
            else:
                # still no user, try the add one more time
                sfdc.add(update_data)
Example #12
0
def confirm_user(token):
    """
    Confirm any pending subscriptions for the user with this token.

    If any of the subscribed newsletters have welcome messages,
    send them.

    :param token: User's token
    :param user_data: Dictionary with user's data from Exact Target,
        as returned by get_user_data(), or None if that wasn't available
        when this was called.
    :raises: BasketError for fatal errors, NewsletterException for retryable
        errors.
    """
    get_lock(token)
    user_data = get_user_data(token=token)

    if user_data is None:
        user = get_sfmc_doi_user(token)
        if user and user.get('email'):
            get_lock(user['email'])
            user['optin'] = True
            try:
                sfdc.add(user)
            except sfapi.SalesforceMalformedRequest:
                # probably already know the email address
                sfdc.update({'email': user['email']}, user)
            statsd.incr('news.tasks.confirm_user.moved_from_sfmc')
        else:
            statsd.incr('news.tasks.confirm_user.confirm_user_not_found')

        return

    if user_data['optin']:
        # already confirmed
        return

    if not ('email' in user_data and user_data['email']):
        raise BasketError('token has no email in ET')

    sfdc.update(user_data, {'optin': True})
Example #13
0
def debug_user(request):
    if 'email' not in request.GET or 'supertoken' not in request.GET:
        return HttpResponseJSON({
            'status': 'error',
            'desc': 'Using debug_user, you need to pass the '
                    '`email` and `supertoken` GET parameters',
            'code': errors.BASKET_USAGE_ERROR,
        }, 400)

    if request.GET['supertoken'] != settings.SUPERTOKEN:
        return HttpResponseJSON({'status': 'error',
                                 'desc': 'Bad supertoken',
                                 'code': errors.BASKET_AUTH_ERROR},
                                401)

    email = request.GET['email']
    try:
        user_data = get_user_data(email=email)
    except NewsletterException as e:
        return newsletter_exception_response(e)

    return HttpResponseJSON(user_data, 200)
Example #14
0
def confirm_user(token, user_data):
    """
    Confirm any pending subscriptions for the user with this token.

    If any of the subscribed newsletters have welcome messages,
    send them.

    :param token: User's token
    :param user_data: Dictionary with user's data from Exact Target,
        as returned by get_user_data(), or None if that wasn't available
        when this was called.
    :raises: BasketError for fatal errors, NewsletterException for retryable
        errors.
    """
    # Get user data if we don't already have it
    if user_data is None:
        from .utils import get_user_data   # Avoid circular import
        user_data = get_user_data(token=token)

    if user_data is None:
        raise BasketError(MSG_USER_NOT_FOUND)

    if user_data['confirmed']:
        log.info('In confirm_user, user with token %s '
                 'is already confirmed' % token)
        return

    if not ('email' in user_data and user_data['email']):
        raise BasketError('token has no email in ET')

    # Add user's token to the confirmation database at ET. A nightly
    # task will somehow do something about it.
    apply_updates(settings.EXACTTARGET_CONFIRMATION, {'TOKEN': token})

    # Now, if they're subscribed to any newsletters with confirmation
    # welcome messages, send those.
    send_welcomes(user_data, user_data['newsletters'],
                  user_data.get('format', 'H'))
Example #15
0
def process_donation(data):
    timestamp = data['timestamp']
    data = data['data']
    get_lock(data['email'])
    # tells the backend to leave the "subscriber" flag alone
    contact_data = {'_set_subscriber': False}
    if 'first_name' in data:
        if data.get('first_name'):
            contact_data['first_name'] = data['first_name']
        if data.get('last_name'):
            contact_data['last_name'] = data['last_name']
    elif data.get('last_name'):
        names = data['last_name'].rsplit(None, 1)
        if len(names) == 2:
            first, last = names
        else:
            first, last = '', names[0]
        if first:
            contact_data['first_name'] = first
        if last:
            contact_data['last_name'] = last

    user_data = get_user_data(email=data['email'],
                              extra_fields=['id'])
    if user_data:
        if contact_data and (
                ('first_name' in contact_data and contact_data['first_name'] != user_data['first_name']) or
                ('last_name' in contact_data and contact_data['last_name'] != user_data['last_name'])):
            sfdc.update(user_data, contact_data)
    else:
        contact_data['token'] = generate_token()
        contact_data['email'] = data['email']
        contact_data['record_type'] = settings.DONATE_CONTACT_RECORD_TYPE

        sfdc.add(contact_data)
        # fetch again to get ID
        user_data = get_user_data(email=data.get('email'),
                                  extra_fields=['id'])
        if not user_data:
            # retry here to make sure we associate the donation data with the proper account
            raise RetryTask('User not yet available')

    # add opportunity
    donation = {
        'RecordTypeId': settings.DONATE_OPP_RECORD_TYPE,
        'Name': 'Foundation Donation',
        'Donation_Contact__c': user_data['id'],
        'StageName': 'Closed Won',
        'CloseDate': timestamp,
        'Amount': float(data['donation_amount']),
        'Currency__c': data['currency'].upper(),
        'Payment_Source__c': data['service'],
        'PMT_Transaction_ID__c': data['transaction_id'],
        'Payment_Type__c': 'Recurring' if data['recurring'] else 'One-Time',
    }
    for dest_name, source_name in DONATION_OPTIONAL_FIELDS.items():
        value = data.get(source_name)
        if value:
            donation[dest_name] = value

    sfdc.opportunity.create(donation)
Example #16
0
def update_user_task(request, api_call_type, data=None, optin=False, sync=False):
    """Call the update_user task async with the right parameters.

    If sync==True, be sure to include the token in the response.
    Otherwise, basket can just do everything in the background.
    """
    data = data or request.POST.dict()

    newsletters = parse_newsletters_csv(data.get('newsletters'))
    if newsletters:
        if api_call_type == SUBSCRIBE:
            all_newsletters = newsletter_and_group_slugs() + get_transactional_message_ids()
        else:
            all_newsletters = newsletter_slugs()

        private_newsletters = newsletter_private_slugs()

        for nl in newsletters:
            if nl not in all_newsletters:
                return HttpResponseJSON({
                    'status': 'error',
                    'desc': 'invalid newsletter',
                    'code': errors.BASKET_INVALID_NEWSLETTER,
                }, 400)

            if api_call_type != UNSUBSCRIBE and nl in private_newsletters:
                if not request.is_secure():
                    return HttpResponseJSON({
                        'status': 'error',
                        'desc': 'private newsletter subscription requires SSL',
                        'code': errors.BASKET_SSL_REQUIRED,
                    }, 401)

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

    if 'lang' in data:
        if not language_code_is_valid(data['lang']):
            data['lang'] = 'en'
    elif 'accept_lang' in data:
        lang = get_best_language(get_accept_languages(data['accept_lang']))
        if lang:
            data['lang'] = lang
            del data['accept_lang']
        else:
            data['lang'] = 'en'

    email = data.get('email')
    token = data.get('token')
    if not (email or token):
        return HttpResponseJSON({
            'status': 'error',
            'desc': MSG_EMAIL_OR_TOKEN_REQUIRED,
            'code': errors.BASKET_USAGE_ERROR,
        }, 400)

    if optin:
        data['optin'] = True

    if api_call_type == SUBSCRIBE and email and data.get('newsletters'):
        # only rate limit here so we don't rate limit errors.
        if is_ratelimited(request, group='news.views.update_user_task.subscribe',
                          key=lambda x, y: '%s-%s' % (data['newsletters'], email),
                          rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True):
            raise Ratelimited()

    if api_call_type == SET and token and data.get('newsletters'):
        # only rate limit here so we don't rate limit errors.
        if is_ratelimited(request, group='news.views.update_user_task.set',
                          key=lambda x, y: '%s-%s' % (data['newsletters'], token),
                          rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True):
            raise Ratelimited()

    if sync:
        statsd.incr('news.views.subscribe.sync')
        if settings.MAINTENANCE_MODE and not settings.MAINTENANCE_READ_ONLY:
            # save what we can
            upsert_user.delay(api_call_type, data, start_time=time())
            # have to error since we can't return a token
            return HttpResponseJSON({
                'status': 'error',
                'desc': 'sync is not available in maintenance mode',
                'code': errors.BASKET_NETWORK_FAILURE,
            }, 400)

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

        if not user_data:
            if not email:
                # must have email to create a user
                return HttpResponseJSON({
                    'status': 'error',
                    'desc': MSG_EMAIL_OR_TOKEN_REQUIRED,
                    'code': errors.BASKET_USAGE_ERROR,
                }, 400)

        token, created = upsert_contact(api_call_type, data, user_data)
        return HttpResponseJSON({
            'status': 'ok',
            'token': token,
            'created': created,
        })
    else:
        upsert_user.delay(api_call_type, data, start_time=time())
        return HttpResponseJSON({
            'status': 'ok',
        })
Example #17
0
def update_get_involved(interest_id, lang, name, email, country, email_format,
                        subscribe, message, source_url):
    """Record a users interest and details for contribution."""
    try:
        interest = Interest.objects.get(interest_id=interest_id)
    except Interest.DoesNotExist:
        # invalid request; no need to raise exception and retry
        return

    email_format = 'T' if email_format.upper().startswith('T') else 'H'

    # Get the user's current settings from ET, if any
    user = get_user_data(email=email)

    record = {
        'EMAIL_ADDRESS_': email,
        'MODIFIED_DATE_': gmttime(),
        'LANGUAGE_ISO2': lang,
        'COUNTRY_': country,
        'GET_INVOLVED_FLG': 'Y',
    }
    if user:
        token = user['token']
        Subscriber.objects.get_and_sync(email, token)
        if 'get-involved' not in user.get('newsletters', []):
            record['GET_INVOLVED_DATE'] = gmttime()
    else:
        sub, created = Subscriber.objects.get_or_create(email=email)
        token = sub.token
        record['EMAIL_FORMAT_'] = email_format
        record['GET_INVOLVED_DATE'] = gmttime()
        # only want source url for first contact
        if source_url:
            record['SOURCE_URL'] = source_url

    record['TOKEN'] = token
    if subscribe:
        # TODO: 'get-involved' not added to ET yet, so can't use it yet.
        # will go in this list when ready.
        newsletters = ['about-mozilla']
        if user:
            cur_newsletters = user.get('newsletters', None)
            if cur_newsletters is not None:
                cur_newsletters = set(cur_newsletters)
        else:
            cur_newsletters = None

        # Set the newsletter flags in the record by comparing to their
        # current subscriptions.
        to_subscribe, _ = parse_newsletters(record, SUBSCRIBE, newsletters, cur_newsletters)
    else:
        to_subscribe = None

    apply_updates(settings.EXACTTARGET_DATA, record)
    apply_updates(settings.EXACTTARGET_INTERESTS, {
        'TOKEN': token,
        'INTEREST': interest_id,
    })
    welcome_id = mogrify_message_id(interest.welcome_id, lang, email_format)
    send_message.delay(welcome_id, email, token, email_format)
    interest.notify_stewards(name, email, lang, message)

    if to_subscribe:
        if not user:
            user = {
                'email': email,
                'token': token,
                'lang': lang,
            }
        send_welcomes(user, to_subscribe, email_format)
Example #18
0
def update_user(data, email, token, api_call_type, optin):
    """Task for updating user's preferences and newsletters.

    :param dict data: POST data from the form submission
    :param string email: User's email address
    :param string token: User's token. If None, the token will be
        looked up, and if no token is found, one will be created for the
        given email.
    :param int api_call_type: What kind of API call it was. Could be
        SUBSCRIBE, UNSUBSCRIBE, or SET.
    :param boolean optin: Whether the user should go through the
        double-optin process or not. If ``optin`` is ``True`` then
        the user should bypass the double-optin process.

    :returns: One of the return codes UU_ALREADY_CONFIRMED,
        etc. (see code) to indicate what case we figured out we were
        doing.  (These are primarily for tests to use.)
    :raises: NewsletterException if there are any errors that would be
        worth retrying. Our task wrapper will retry in that case.
    """
    # If token is missing, find it or generate it.
    if not token:
        sub, user_data, created = lookup_subscriber(email=email)
        token = sub.token

    # Parse the parameters
    # `record` will contain the data we send to ET in the format they want.
    record = {
        'EMAIL_ADDRESS_': email,
        'TOKEN': token,
        'EMAIL_PERMISSION_STATUS_': 'I',
        'MODIFIED_DATE_': gmttime(),
    }

    extra_fields = {
        'country': 'COUNTRY_',
        'lang': 'LANGUAGE_ISO2',
        'source_url': 'SOURCE_URL',
    }

    # Optionally add more fields
    for field in extra_fields:
        if field in data:
            record[extra_fields[field]] = data[field]

    lang = record.get('LANGUAGE_ISO2', '') or ''

    # Get the user's current settings from ET, if any
    user_data = get_user_data(token=token)
    # If we don't find the user, get_user_data returns None. Create
    # a minimal dictionary to use going forward. This will happen
    # often due to new people signing up.
    if user_data is None:
        user_data = {
            'email': email,
            'token': token,
            'master': False,
            'pending': False,
            'confirmed': False,
            'lang': lang,
            'status': 'ok',
        }

    if lang:
        # User asked for a language change. Use the new language from
        # here on.
        user_data['lang'] = lang
    else:
        # Use `lang` as a shorter reference to user_data['lang']
        lang = user_data['lang']

    # We need an HTML/Text format choice for sending welcome messages, and
    # optionally to update their ET record
    if 'format' in data:  # Submitted in call
        fmt = 'T' if data.get('format', 'H').upper().startswith('T') else 'H'
        # We only set the format in ET if the call asked us to
        record['EMAIL_FORMAT_'] = fmt
    elif 'format' in user_data:  # Existing user preference
        fmt = user_data['format']
    else:  # Default to 'H'
        fmt = 'H'
    # From here on, fmt is either 'H' or 'T', preferring 'H'

    newsletters = [x.strip() for x in data.get('newsletters', '').split(',')]

    cur_newsletters = user_data.get('newsletters', None)
    if cur_newsletters is not None:
        cur_newsletters = set(cur_newsletters)

    # Set the newsletter flags in the record by comparing to their
    # current subscriptions.
    to_subscribe, to_unsubscribe = parse_newsletters(record, api_call_type,
                                                     newsletters,
                                                     cur_newsletters)

    # Are they subscribing to any newsletters that don't require confirmation?
    # When including any newsletter that does not
    # require confirmation, user gets a pass on confirming and goes straight
    # to confirmed.
    exempt_from_confirmation = optin or Newsletter.objects\
        .filter(slug__in=to_subscribe, requires_double_optin=False)\
        .exists()

    # Send welcomes when api_call_type is SUBSCRIBE and trigger_welcome
    # arg is absent or 'Y'.
    should_send_welcomes = data.get('trigger_welcome', 'Y') == 'Y' and api_call_type == SUBSCRIBE

    MASTER = settings.EXACTTARGET_DATA
    OPT_IN = settings.EXACTTARGET_OPTIN_STAGE

    if user_data['confirmed']:
        # The user is already confirmed.
        # Just add any new subs to whichever of master or optin list is
        # appropriate, and send welcomes.
        target_et = MASTER if user_data['master'] else OPT_IN
        apply_updates(target_et, record)
        if should_send_welcomes:
            send_welcomes(user_data, to_subscribe, fmt)
        return_code = UU_ALREADY_CONFIRMED
    elif exempt_from_confirmation:
        # This user is not confirmed, but they
        # qualify to be excepted from confirmation.
        if user_data['pending']:
            # We were waiting for them to confirm.  Update the data in
            # their record (currently in the Opt-in table), then go
            # ahead and confirm them. This will also send welcomes.
            apply_updates(OPT_IN, record)
            confirm_user(user_data['token'], user_data)
            return_code = UU_EXEMPT_PENDING
        else:
            # Brand new user: Add them directly to master subscriber DB
            # and send welcomes.
            record['CREATED_DATE_'] = gmttime()
            apply_updates(MASTER, record)
            if should_send_welcomes:
                send_welcomes(user_data, to_subscribe, fmt)
            return_code = UU_EXEMPT_NEW
    else:
        # This user must confirm
        if user_data['pending']:
            return_code = UU_MUST_CONFIRM_PENDING
        else:
            # Creating a new record, need a couple more fields
            record['CREATED_DATE_'] = gmttime()
            record['SubscriberKey'] = record['TOKEN']
            record['EmailAddress'] = record['EMAIL_ADDRESS_']
            return_code = UU_MUST_CONFIRM_NEW
        # Create or update OPT_IN record and send email telling them (or
        # reminding them) to confirm.
        apply_updates(OPT_IN, record)
        send_confirm_notice(email, token, lang, fmt, to_subscribe)
    return return_code
Example #19
0
def lookup_user(request):
    """Lookup a user in Exact Target given email or token (not both).

    To look up by email, a valid API key are required.

    If email and token are both provided, an error is returned rather
    than trying to define all the possible behaviors.

    SSL is always required when using this call. If no SSL, it'll fail
    with 401 and an appropriate message in the response body.

    Response content is always JSON.

    If user is not found, returns a 404 status and json is::

        {
            'status': 'error',
            'desc': 'No such user'
        }

    (If you need to distinguish user not found from an error calling
    the API, check the response content.)

    If a required, valid API key is not provided, status is 401 Unauthorized.
    The API key can be provided either as a GET query parameter ``api-key``
    or a request header ``X-api-key``. If it's provided as a query parameter,
    any request header is ignored.

    For other errors, similarly
    response status is 4xx and the json 'desc' says what's wrong.

    Otherwise, status is 200 and json is the return value from
    `get_user_data`. See that method for details.

    Note that because this method always calls Exact Target one or
    more times, it can be slower than some other Basket APIs, and will
    fail if ET is down.
    """
    if settings.MAINTENANCE_MODE and not settings.MAINTENANCE_READ_ONLY:
        # can't return user data during maintenance
        return HttpResponseJSON({
            'status': 'error',
            'desc': 'user data is not available in maintenance mode',
            'code': errors.BASKET_NETWORK_FAILURE,
        }, 400)

    if not request.is_secure():
        return HttpResponseJSON({
            'status': 'error',
            'desc': 'lookup_user always requires SSL',
            'code': errors.BASKET_SSL_REQUIRED,
        }, 401)

    token = request.GET.get('token', None)
    email = request.GET.get('email', None)

    if (not email and not token) or (email and token):
        return HttpResponseJSON({
            'status': 'error',
            'desc': MSG_EMAIL_OR_TOKEN_REQUIRED,
            'code': errors.BASKET_USAGE_ERROR,
        }, 400)

    if email and not has_valid_api_key(request):
        return HttpResponseJSON({
            'status': 'error',
            'desc': 'Using lookup_user with `email`, you need to pass a '
                    'valid `api-key` GET parameter or X-api-key header',
            'code': errors.BASKET_AUTH_ERROR,
        }, 401)

    if email:
        email = process_email(email)
        if not email:
            return invalid_email_response()

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

    status_code = 200
    if not user_data:
        code = errors.BASKET_UNKNOWN_TOKEN if token else errors.BASKET_UNKNOWN_EMAIL
        user_data = {
            'status': 'error',
            'desc': MSG_USER_NOT_FOUND,
            'code': code,
        }
        status_code = 404

    return HttpResponseJSON(user_data, status_code)
Example #20
0
def lookup_user(request):
    """Lookup a user in Exact Target given email or token (not both).

    To look up by email, a valid API key are required.

    If email and token are both provided, an error is returned rather
    than trying to define all the possible behaviors.

    SSL is always required when using this call. If no SSL, it'll fail
    with 401 and an appropriate message in the response body.

    Response content is always JSON.

    If user is not found, returns a 404 status and json is::

        {
            'status': 'error',
            'desc': 'No such user'
        }

    (If you need to distinguish user not found from an error calling
    the API, check the response content.)

    If a required, valid API key is not provided, status is 401 Unauthorized.
    The API key can be provided either as a GET query parameter ``api-key``
    or a request header ``X-api-key``. If it's provided as a query parameter,
    any request header is ignored.

    For other errors, similarly
    response status is 4xx and the json 'desc' says what's wrong.

    Otherwise, status is 200 and json is the return value from
    `get_user_data`. See that method for details.

    Note that because this method always calls Exact Target one or
    more times, it can be slower than some other Basket APIs, and will
    fail if ET is down.
    """

    if not request.is_secure():
        return HttpResponseJSON({
            'status': 'error',
            'desc': 'lookup_user always requires SSL',
            'code': errors.BASKET_SSL_REQUIRED,
        }, 401)

    token = request.GET.get('token', None)
    email = request.GET.get('email', None)

    if (not email and not token) or (email and token):
        return HttpResponseJSON({
            'status': 'error',
            'desc': MSG_EMAIL_OR_TOKEN_REQUIRED,
            'code': errors.BASKET_USAGE_ERROR,
        }, 400)

    if email and not has_valid_api_key(request):
        return HttpResponseJSON({
            'status': 'error',
            'desc': 'Using lookup_user with `email`, you need to pass a '
                    'valid `api-key` GET parameter or X-api-key header',
            'code': errors.BASKET_AUTH_ERROR,
        }, 401)

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

    status_code = 200
    if not user_data:
        code = errors.BASKET_UNKNOWN_TOKEN if token else errors.BASKET_UNKNOWN_EMAIL
        user_data = {
            'status': 'error',
            'desc': MSG_USER_NOT_FOUND,
            'code': code,
        }
        status_code = 404

    return HttpResponseJSON(user_data, status_code)