Beispiel #1
0
    def send_sms(self, phone_numbers, message_id):
        if self.client is None:
            return

        if isinstance(phone_numbers, str):
            phone_numbers = [phone_numbers]

        phone_numbers = [pn.lstrip("+") for pn in phone_numbers]
        data = {
            "mobileNumbers": phone_numbers,
            "Subscribe": True,
            "Resubscribe": True,
            "keyword": "FFDROID",  # TODO: Set keyword in arguments.
        }
        url = self.sms_api_url.format(message_id)
        response = requests.post(url, json=data, headers=self.auth_header, timeout=10)
        if response.status_code >= 500:
            raise NewsletterException(
                "SFMC Server Error: {}".format(force_str(response.content)),
                status_code=response.status_code,
            )

        if response.status_code >= 400:
            raise NewsletterException(
                "SFMC Request Error: {}".format(force_str(response.content)),
                status_code=response.status_code,
            )
Beispiel #2
0
    def bulk_upsert_rows(self, de_name, values):
        url = self.rowset_api_url.format(de_name)
        response = requests.post(url, json=values, headers=self.auth_header, timeout=30)
        if response.status_code >= 500:
            raise NewsletterException('SFMC Server Error: {}'.format(response.content),
                                      status_code=response.status_code)

        if response.status_code >= 400:
            raise NewsletterException(response.content, status_code=response.status_code)
Beispiel #3
0
    def test_caching_bad_message_ids(self, mock_sfmc):
        """Bad message IDs are cached so we don't try to send to them again"""
        exc = NewsletterException()
        exc.message = 'Invalid Customer Key'
        mock_sfmc.send_mail.side_effect = exc

        message_id = "MESSAGE_ID"
        for i in range(10):
            send_message(message_id, 'email', 'sub-key', 'token')

        mock_sfmc.send_mail.assert_called_once_with(message_id, 'email', 'sub-key', 'token')
Beispiel #4
0
def get_user_data(token=None, email=None, extra_fields=None):
    """Return a dictionary of the user's data from Exact Target.
    Look them up by their email if given, otherwise by the token.

    If the user was not found, return None instead of a dictionary.

    Some data fields are not returned by default. Those fields are listed
    in the IGNORE_USER_FIELDS list. If you need one of those fields then
    call this function with said field name in a list passed in
    the `extra_fields` argument.

    Review of results:

    None = user completely unknown, no errors talking to ET.

    otherwise, return value is::

    {
        'status':  'ok',      # no errors talking to ET
        'status':  'error',   # errors talking to ET, see next field
        'desc':  'error message'   # details if status is error
        'email': 'email@address',
        'format': 'T'|'H',
        'country': country code,
        'lang': language code,
        'token': UUID,
        'created-date': date created,
        'newsletters': list of slugs of newsletters subscribed to,
        'confirmed': True if user has confirmed subscription (or was excepted),
        'pending': True if we're waiting for user to confirm subscription
        'master': True if we found them in the master subscribers table
    }
    """
    try:
        user = sfdc.get(token, email)
    except sfapi.SalesforceResourceNotFound:
        return None
    except requests.exceptions.RequestException as e:
        raise NewsletterException(str(e),
                                  error_code=errors.BASKET_NETWORK_FAILURE,
                                  status_code=400)
    except sfapi.SalesforceAuthenticationFailed:
        raise NewsletterException(
            'Email service provider auth failure',
            error_code=errors.BASKET_EMAIL_PROVIDER_AUTH_FAILURE,
            status_code=500)

    # don't send some of the returned data
    for fn in IGNORE_USER_FIELDS:
        if extra_fields and fn not in extra_fields:
            user.pop(fn, None)

    user['status'] = 'ok'
    return user
Beispiel #5
0
    def test_caching_bad_message_ids(self, mock_sfmc):
        """Bad message IDs are cached so we don't try to send to them again"""
        exc = NewsletterException()
        exc.message = 'Invalid Customer Key'
        mock_sfmc.send_mail.side_effect = exc

        message_id = "MESSAGE_ID"
        for i in range(10):
            send_message(message_id, 'email', 'token', 'format')

        mock_sfmc.send_mail.assert_called_once_with(message_id, 'email',
                                                    'token', None)
Beispiel #6
0
    def bulk_upsert_rows(self, de_name, values):
        if self.client is None:
            return

        url = self.rowset_api_url.format(de_name)
        response = requests.post(url, json=values, headers=self.auth_header, timeout=30)
        if response.status_code >= 500:
            raise NewsletterException(
                "SFMC Server Error: {}".format(force_str(response.content)),
                status_code=response.status_code,
            )

        if response.status_code >= 400:
            raise NewsletterException(
                force_str(response.content), status_code=response.status_code,
            )
Beispiel #7
0
 def test_error(self, get_user_data, sfdc_mock):
     """
     If user_data shows an error talking to ET, the task raises
     an exception so our task logic will retry
     """
     get_user_data.side_effect = NewsletterException('Stuffs broke yo.')
     with self.assertRaises(Retry):
         confirm_user('token')
     self.assertFalse(sfdc_mock.update.called)
Beispiel #8
0
    def request_token(self, payload):
        r = requests.post(self.auth_url, json=payload)
        try:
            token_response = r.json()
        except ValueError:
            raise NewsletterException('SFMC Error During Auth: ' + r.content,
                                      status_code=r.status_code)

        if 'accessToken' in token_response:
            return token_response

        # try again without refreshToken
        if 'refreshToken' in payload:
            # not strictly required, makes testing easier
            payload = payload.copy()
            del payload['refreshToken']
            return self.request_token(payload)

        raise NewsletterException('SFMC Error During Auth: ' + r.content,
                                  status_code=r.status_code)
Beispiel #9
0
    def send_sms(self, phone_numbers, message_id):
        if isinstance(phone_numbers, str):
            phone_numbers = [phone_numbers]

        phone_numbers = [pn.lstrip('+') for pn in phone_numbers]
        data = {
            'mobileNumbers': phone_numbers,
            'Subscribe': True,
            'Resubscribe': True,
            'keyword': 'FFDROID',  # TODO: Set keyword in arguments.
        }
        url = self.sms_api_url.format(message_id)
        response = requests.post(url, json=data, headers=self.auth_header, timeout=10)
        if response.status_code >= 500:
            raise NewsletterException('SFMC Server Error: {}'.format(response.content),
                                      status_code=response.status_code)

        if response.status_code >= 400:
            raise NewsletterException('SFMC Request Error: {}'.format(response.content),
                                      status_code=response.status_code)
    def test_caching_bad_message_ids(self, mock_sfmc):
        """Bad message IDs are cached so we don't try to send to them again"""
        exc = NewsletterException("Invalid Customer Key")
        mock_sfmc.send_mail.side_effect = exc

        message_id = "MESSAGE_ID"
        for _ in range(10):
            send_message(message_id, "email", "sub-key", "token")

        mock_sfmc.send_mail.assert_called_once_with(
            message_id, "email", "sub-key", "token",
        )
Beispiel #11
0
 def test_user_not_in_sf(self, sfdc_mock):
     """A user not found in SFDC should produce an error response."""
     sfdc_mock.get.side_effect = NewsletterException('DANGER!')
     token = generate_token()
     resp = self.client.get('/news/user/{}/'.format(token))
     self.assertEqual(resp.status_code, 400)
     resp_data = json.loads(resp.content)
     self.assertDictEqual(
         resp_data, {
             'status': 'error',
             'desc': 'DANGER!',
             'code': errors.BASKET_UNKNOWN_ERROR,
         })
Beispiel #12
0
 def test_user_not_in_sf(self, sfdc_mock):
     """A user not found in SFDC should produce an error response."""
     sfdc_mock.get.side_effect = NewsletterException("DANGER!")
     token = generate_token()
     resp = self.client.get("/news/user/{}/".format(token))
     self.assertEqual(resp.status_code, 400)
     resp_data = json.loads(resp.content)
     self.assertDictEqual(
         resp_data,
         {
             "status": "error",
             "desc": "DANGER!",
             "code": errors.BASKET_UNKNOWN_ERROR
         },
     )
Beispiel #13
0
def assert_response(resp):
    if not resp.status:
        raise NewsletterException(str(resp.results))
Beispiel #14
0
def get_user_data(
    token=None,
    email=None,
    payee_id=None,
    amo_id=None,
    fxa_id=None,
    extra_fields=None,
    get_fxa=False,
):
    """
    Return a dictionary of the user's data.

    If SFDC_ENABLED, use Salesforce.com (SFDC) as the primary source. Lookups
    are by the first given of token, email, payee ID (e.g. Stripe), AMO ID, or
    FxA ID. CTMS is queried to add a CTMS email_id if unknown by SFDC.

    If not SFDC_ENABLED, use CTMS (Mozilla's Contact Management System) as the
    primary source. Lookups are by token, email, SFDC ID, AMO ID, and FxA ID.

    If the user was not found, return None instead of a dictionary.

    Some data fields are not returned by default. Those fields are listed
    in the IGNORE_USER_FIELDS list. If you need one of those fields then
    call this function with said field name in a list passed in
    the `extra_fields` argument.

    If `get_fxa` is True then a boolean field will be including indicating whether they are
    an account holder or not.

    Review of results:

    None = user completely unknown, no errors talking to SFDC / CTMS.

    otherwise, return value is::

    {
        'status':  'ok',      # no errors talking to ET
        'status':  'error',   # errors talking to ET, see next field
        'desc':  'error message'   # details if status is error
        'email': 'email@address',
        'email_id': CTMS UUID,
        'format': 'T'|'H',
        'country': country code,
        'lang': language code,
        'token': UUID,
        'created-date': date created,
        'newsletters': list of slugs of newsletters subscribed to,
        'confirmed': True if user has confirmed subscription (or was excepted),
        'pending': True if we're waiting for user to confirm subscription
        'master': True if we found them in the master subscribers table
    }
    """
    sfdc_enabled = True
    try:
        user = sfdc.get(
            token=token,
            email=email,
            payee_id=payee_id,
            amo_id=amo_id,
            fxa_id=fxa_id,
        )
    except sfapi.SalesforceResourceNotFound:
        return None
    except requests.exceptions.RequestException as e:
        raise NewsletterException(
            str(e),
            error_code=errors.BASKET_NETWORK_FAILURE,
            status_code=400,
        )
    except sfapi.SalesforceAuthenticationFailed:
        raise NewsletterException(
            "Email service provider auth failure",
            error_code=errors.BASKET_EMAIL_PROVIDER_AUTH_FAILURE,
            status_code=500,
        )
    except SFDCDisabled:
        sfdc_enabled = False
        user = {}

    if not user.get("email_id"):
        # Ask CTMS, if SFDC is disabled or SFDC doesn't have email_id
        ctms_user = None
        try:
            ctms_user = ctms.get(
                token=token,
                email=email,
                sfdc_id=user.get("id"),
                amo_id=amo_id,
                fxa_id=fxa_id,
            )
        except requests.exceptions.HTTPError as error:
            if error.response.status_code != 404:
                if sfdc_enabled:
                    sentry_sdk.capture_exception()
                elif error.response.status_code == 401:
                    raise NewsletterException(
                        "Email service provider auth failure",
                        error_code=errors.BASKET_EMAIL_PROVIDER_AUTH_FAILURE,
                        status_code=500,
                    )
                else:
                    raise NewsletterException(
                        str(error),
                        error_code=errors.BASKET_NETWORK_FAILURE,
                        status_code=400,
                    )
        except CTMSNotConfigured:
            raise NewsletterException(
                "Email service provider auth failure",
                error_code=errors.BASKET_EMAIL_PROVIDER_AUTH_FAILURE,
                status_code=500,
            )
        except CTMSError as e:
            if sfdc_enabled:
                sentry_sdk.capture_exception()
            else:
                raise NewsletterException(
                    str(e),
                    error_code=errors.BASKET_NETWORK_FAILURE,
                    status_code=400,
                )
        if sfdc_enabled:
            if ctms_user and "email_id" in ctms_user:
                user["email_id"] = ctms_user["email_id"]
        else:
            if ctms_user:
                user = ctms_user
            else:
                return None

    # don't send some of the returned data
    for fn in IGNORE_USER_FIELDS:
        if extra_fields and fn not in extra_fields:
            user.pop(fn, None)

    if get_fxa:
        user["has_fxa"] = bool(user.get("fxa_id"))

    user["status"] = "ok"
    return user
Beispiel #15
0
def get_user_data(
    token=None,
    email=None,
    payee_id=None,
    amo_id=None,
    fxa_id=None,
    extra_fields=None,
    get_fxa=False,
):
    """Return a dictionary of the user's data from Salesforce.com (SFDC).
    Look them up by their email or payment processor id (e.g. Stripe) if given,
    otherwise by the token.

    If the user was not found, return None instead of a dictionary.

    Some data fields are not returned by default. Those fields are listed
    in the IGNORE_USER_FIELDS list. If you need one of those fields then
    call this function with said field name in a list passed in
    the `extra_fields` argument.

    If `get_fxa` is True then a boolean field will be including indicating whether they are
    an account holder or not.

    If SFDC does not have the CTMS email_id, try to get it from CTMS.

    Review of results:

    None = user completely unknown, no errors talking to SFDC.

    otherwise, return value is::

    {
        'status':  'ok',      # no errors talking to ET
        'status':  'error',   # errors talking to ET, see next field
        'desc':  'error message'   # details if status is error
        'email': 'email@address',
        'email_id': CTMS UUID,
        'format': 'T'|'H',
        'country': country code,
        'lang': language code,
        'token': UUID,
        'created-date': date created,
        'newsletters': list of slugs of newsletters subscribed to,
        'confirmed': True if user has confirmed subscription (or was excepted),
        'pending': True if we're waiting for user to confirm subscription
        'master': True if we found them in the master subscribers table
    }
    """
    try:
        user = sfdc.get(
            token=token,
            email=email,
            payee_id=payee_id,
            amo_id=amo_id,
            fxa_id=fxa_id,
        )
    except sfapi.SalesforceResourceNotFound:
        return None
    except requests.exceptions.RequestException as e:
        raise NewsletterException(
            str(e),
            error_code=errors.BASKET_NETWORK_FAILURE,
            status_code=400,
        )
    except sfapi.SalesforceAuthenticationFailed:
        raise NewsletterException(
            "Email service provider auth failure",
            error_code=errors.BASKET_EMAIL_PROVIDER_AUTH_FAILURE,
            status_code=500,
        )

    # don't send some of the returned data
    for fn in IGNORE_USER_FIELDS:
        if extra_fields and fn not in extra_fields:
            user.pop(fn, None)

    if get_fxa:
        user["has_fxa"] = bool(user.get("fxa_id"))

    if not user.get("email_id"):
        # Add email_id from CTMS, if CTMS knows about this contact
        ctms_user = None
        try:
            ctms_user = ctms.get(
                token=token,
                email=email,
                sfdc_id=user.get("id"),
                amo_id=amo_id,
                fxa_id=fxa_id,
            )
        except requests.exceptions.HTTPError as error:
            if error.response.status_code != 404:
                sentry_sdk.capture_exception()
        except RuntimeError:
            sentry_sdk.capture_exception()
        if ctms_user and "email_id" in ctms_user:
            user["email_id"] = ctms_user["email_id"]

    user["status"] = "ok"
    return user
Beispiel #16
0
def get_user_data(token=None, email=None, payee_id=None, amo_id=None, fxa_id=None,
                  extra_fields=None, get_fxa=False):
    """Return a dictionary of the user's data from Exact Target.
    Look them up by their email or payment processor id (e.g. Stripe) if given,
    otherwise by the token.

    If the user was not found, return None instead of a dictionary.

    Some data fields are not returned by default. Those fields are listed
    in the IGNORE_USER_FIELDS list. If you need one of those fields then
    call this function with said field name in a list passed in
    the `extra_fields` argument.

    If `get_fxa` is True then the email will be looked up in the FxA Data Extension
    in SFMC and a boolean field will be including indicating whether they are
    an account holder or not.

    Review of results:

    None = user completely unknown, no errors talking to ET.

    otherwise, return value is::

    {
        'status':  'ok',      # no errors talking to ET
        'status':  'error',   # errors talking to ET, see next field
        'desc':  'error message'   # details if status is error
        'email': 'email@address',
        'format': 'T'|'H',
        'country': country code,
        'lang': language code,
        'token': UUID,
        'created-date': date created,
        'newsletters': list of slugs of newsletters subscribed to,
        'confirmed': True if user has confirmed subscription (or was excepted),
        'pending': True if we're waiting for user to confirm subscription
        'master': True if we found them in the master subscribers table
    }
    """
    try:
        user = sfdc.get(token, email, payee_id, amo_id, fxa_id)
    except sfapi.SalesforceResourceNotFound:
        return None
    except requests.exceptions.RequestException as e:
        raise NewsletterException(str(e),
                                  error_code=errors.BASKET_NETWORK_FAILURE,
                                  status_code=400)
    except sfapi.SalesforceAuthenticationFailed:
        raise NewsletterException('Email service provider auth failure',
                                  error_code=errors.BASKET_EMAIL_PROVIDER_AUTH_FAILURE,
                                  status_code=500)

    # don't send some of the returned data
    for fn in IGNORE_USER_FIELDS:
        if extra_fields and fn not in extra_fields:
            user.pop(fn, None)

    if get_fxa:
        try:
            sfmc.get_row('Firefox_Account_ID', ['FXA_ID'], email=user['email'])
        except NewsletterNoResultsException:
            fxa_user = False
        except Exception:
            # some error happened, but we should return the user data anyway
            sentry_sdk.capture_exception()
            fxa_user = None
        else:
            fxa_user = True

        if fxa_user is not None:
            user['has_fxa'] = fxa_user

    user['status'] = 'ok'
    return user