def test_create_pending_enterprise_users_http_error(
            self, mock_oauth_client):
        """
        Verify the ``create_pending_enterprise_users`` method does not raise an exception for successful requests.
        """
        # Mock out the response from the lms
        mock_oauth_client.return_value.post.return_value = MockResponse(
            {'detail': 'Bad Request'},
            400,
            content=b'error response',
        )

        user_emails = [
            '*****@*****.**',
            '*****@*****.**',
            '*****@*****.**',
        ]
        enterprise_client = EnterpriseApiClient()
        with self.assertRaises(requests.exceptions.HTTPError):
            response = enterprise_client.create_pending_enterprise_users(
                self.uuid, user_emails)
            mock_oauth_client.return_value.post.assert_called_once_with(
                enterprise_client.pending_enterprise_learner_endpoint,
                json=[{
                    'enterprise_customer': self.uuid,
                    'user_email': user_email
                } for user_email in user_emails],
            )
            assert response.status_code == 400
            assert response.content == 'error response'
Ejemplo n.º 2
0
def activation_email_task(custom_template_text, email_recipient_list, subscription_uuid):
    """
    Sends license activation email(s) asynchronously, and creates pending enterprise users to link the email recipients
    to the subscription's enterprise.

    Arguments:
        custom_template_text (dict): Dictionary containing `greeting` and `closing` keys to be used for customizing
            the email template.
        email_recipient_list (list of str): List of recipients to send the emails to.
        subscription_uuid (str): UUID (string representation) of the subscription that the recipients are associated
            with or will be associated with.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    pending_licenses = subscription_plan.licenses.filter(user_email__in=email_recipient_list).order_by('uuid')
    enterprise_api_client = EnterpriseApiClient()
    enterprise_customer = enterprise_api_client.get_enterprise_customer_data(subscription_plan.enterprise_customer_uuid)
    enterprise_slug = enterprise_customer.get('slug')
    enterprise_name = enterprise_customer.get('name')
    enterprise_sender_alias = enterprise_customer.get('sender_alias', 'edX Support Team')

    try:
        send_activation_emails(
            custom_template_text, pending_licenses, enterprise_slug, enterprise_name, enterprise_sender_alias
        )
    except SMTPException:
        msg = 'License manager activation email sending received an exception for enterprise: {}.'.format(
            enterprise_name
        )
        logger.error(msg, exc_info=True)
        return
Ejemplo n.º 3
0
def send_auto_applied_license_email_task(enterprise_customer_uuid, user_email):
    """
    Asynchronously sends onboarding email to learner. Intended for use following automatic license activation.

    Uses Braze client to send email via Braze campaign.
    """
    try:
        # Get some info about the enterprise customer
        enterprise_api_client = EnterpriseApiClient()
        enterprise_customer = enterprise_api_client.get_enterprise_customer_data(
            enterprise_customer_uuid)
        enterprise_slug = enterprise_customer.get('slug')
        enterprise_name = enterprise_customer.get('name')
        learner_portal_search_enabled = enterprise_customer.get(
            'enable_integrated_customer_learner_portal_search')
        identity_provider = enterprise_customer.get('identity_provider')
        enterprise_sender_alias = get_enterprise_sender_alias(
            enterprise_customer)
        enterprise_contact_email = enterprise_customer.get('contact_email')
    except Exception:  # pylint: disable=broad-except
        message = (
            f'Error getting data about the enterprise_customer {enterprise_customer_uuid}. '
            f'Onboarding email to {user_email} for auto applied license failed.'
        )
        logger.error(message, exc_info=True)
        return

    # Determine which email campaign to use
    if identity_provider and learner_portal_search_enabled is False:
        braze_campaign_id = settings.AUTOAPPLY_NO_LEARNER_PORTAL_CAMPAIGN
    else:
        braze_campaign_id = settings.AUTOAPPLY_WITH_LEARNER_PORTAL_CAMPAIGN

    # Form data we want to hand to the campaign's email template
    braze_trigger_properties = {
        'enterprise_customer_slug': enterprise_slug,
        'enterprise_customer_name': enterprise_name,
        'enterprise_sender_alias': enterprise_sender_alias,
        'enterprise_contact_email': enterprise_contact_email,
    }
    recipient = _aliased_recipient_object_from_email(user_email)

    try:
        # Hit the Braze api to send the email
        braze_client_instance = BrazeApiClient()
        braze_client_instance.create_braze_alias(
            [user_email],
            ENTERPRISE_BRAZE_ALIAS_LABEL,
        )
        braze_client_instance.send_campaign_message(
            braze_campaign_id,
            recipients=[recipient],
            trigger_properties=braze_trigger_properties,
        )
    except BrazeClientError:
        message = (
            'Error hitting Braze API. '
            f'Onboarding email to {user_email} for auto applied license failed.'
        )
        logger.error(message, exc_info=True)
Ejemplo n.º 4
0
def send_reminder_email_task(custom_template_text, email_recipient_list,
                             subscription_uuid):
    """
    Sends license activation reminder email(s) asynchronously.

    Arguments:
        custom_template_text (dict): Dictionary containing `greeting` and `closing` keys to be used for customizing
            the email template.
        email_recipient_list (list of str): List of recipients to send the emails to.
        subscription_uuid (str): UUID (string representation) of the subscription that the recipients are associated
            with or will be associated with.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    pending_licenses = subscription_plan.licenses.filter(
        user_email__in=email_recipient_list).order_by('uuid')
    enterprise_api_client = EnterpriseApiClient()
    enterprise_slug = enterprise_api_client.get_enterprise_slug(
        subscription_plan.enterprise_customer_uuid)
    enterprise_name = enterprise_api_client.get_enterprise_name(
        subscription_plan.enterprise_customer_uuid)

    try:
        send_activation_emails(custom_template_text,
                               pending_licenses,
                               enterprise_slug,
                               enterprise_name,
                               is_reminder=True)
    except Exception:  # pylint: disable=broad-except
        msg = 'License manager reminder email sending received an exception for enterprise: {}.'.format(
            enterprise_name)
        logger.error(msg, exc_info=True)
        # Return without updating the last_remind_date for licenses
        return

    License.set_date_fields_to_now(pending_licenses, ['last_remind_date'])
Ejemplo n.º 5
0
def revoke_course_enrollments_for_user_task(user_id, enterprise_id):
    """
    Sends revoking the user's enterprise licensed course enrollments asynchronously

    Arguments:
        user_id (str): The ID of the user who had an enterprise license revoked
        enterprise_id (str): The ID of the enterprise to revoke course enrollments for
    """
    try:
        enterprise_api_client = EnterpriseApiClient()
        enterprise_api_client.revoke_course_enrollments_for_user(
            user_id=user_id, enterprise_id=enterprise_id)
        logger.info(
            'Revocation of course enrollments SUCCEEDED for user [{user_id}], enterprise [{enterprise_id}]'
            .format(
                user_id=user_id,
                enterprise_id=enterprise_id,
            ))
    except Exception:  # pylint: disable=broad-except
        logger.error(
            'Revocation of course enrollments FAILED for user [{user_id}], enterprise [{enterprise_id}]'
            .format(
                user_id=user_id,
                enterprise_id=enterprise_id,
            ),
            exc_info=True,
        )
Ejemplo n.º 6
0
    def _validate_enterprise_customer_uuid(self):
        """
        Verifies that a customer with the given enterprise_customer_uuid exists
        """
        enterprise_customer_uuid = self.instance.enterprise_customer_uuid
        try:
            customer_data = EnterpriseApiClient().get_enterprise_customer_data(
                enterprise_customer_uuid)
            self.instance.enterprise_customer_slug = customer_data.get('slug')
            self.instance.enterprise_customer_name = customer_data.get('name')
            return True
        except HTTPError as ex:
            logger.exception(
                f'Could not validate enterprise_customer_uuid {enterprise_customer_uuid}.'
            )
            if ex.response.status_code == status.HTTP_404_NOT_FOUND:
                self.add_error(
                    'enterprise_customer_uuid',
                    f'An enterprise customer with uuid: {enterprise_customer_uuid} does not exist.',
                )
            else:
                self.add_error(
                    'enterprise_customer_uuid',
                    f'Could not verify the given UUID: {ex}. Please try again.',
                )

            return False
Ejemplo n.º 7
0
def send_onboarding_email(enterprise_customer_uuid, user_email):
    """
    Sends onboarding email to learner. Intended for use following license activation.

    Arguments:
        enterprise_customer_uuid (UUID): unique identifier of the EnterpriseCustomer
        that is linked to the SubscriptionPlan associated with the activated license
        user_email (str): email of the learner whose license has just been activated
    """
    enterprise_customer = EnterpriseApiClient().get_enterprise_customer_data(
        enterprise_customer_uuid)
    enterprise_name = enterprise_customer.get('name')
    enterprise_slug = enterprise_customer.get('slug')
    enterprise_sender_alias = get_enterprise_sender_alias(enterprise_customer)

    context = {
        'subject': ONBOARDING_EMAIL_SUBJECT,
        'template_name': ONBOARDING_EMAIL_TEMPLATE,
        'ENTERPRISE_NAME': enterprise_name,
        'ENTERPRISE_SLUG': enterprise_slug,
        'HELP_CENTER_URL': settings.SUPPORT_SITE_URL,
        'LEARNER_PORTAL_LINK': get_learner_portal_url(enterprise_slug),
        'MOBILE_STORE_URLS': settings.MOBILE_STORE_URLS,
        'RECIPIENT_EMAIL': user_email,
        'SOCIAL_MEDIA_FOOTER_URLS': settings.SOCIAL_MEDIA_FOOTER_URLS,
    }
    email = _message_from_context_and_template(context,
                                               enterprise_sender_alias)
    email.send()
Ejemplo n.º 8
0
def activation_task(custom_template_text, email_recipient_list,
                    subscription_uuid):
    """
    Sends license activation email(s) asynchronously, and creates pending enterprise users to link the email recipients
    to the subscription's enterprise.

    Arguments:
        custom_template_text (dict): Dictionary containing `greeting` and `closing` keys to be used for customizing
            the email template.
        email_recipient_list (list of str): List of recipients to send the emails to.
        subscription_uuid (str): UUID (string representation) of the subscription that the recipients are associated
            with or will be associated with.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    pending_licenses = subscription_plan.licenses.filter(
        user_email__in=email_recipient_list).order_by('uuid')
    enterprise_api_client = EnterpriseApiClient()
    enterprise_slug = enterprise_api_client.get_enterprise_slug(
        subscription_plan.enterprise_customer_uuid)
    send_activation_emails(custom_template_text, pending_licenses,
                           enterprise_slug)
    License.set_date_fields_to_now(pending_licenses,
                                   ['last_remind_date', 'assigned_date'])

    for email_recipient in email_recipient_list:
        enterprise_api_client.create_pending_enterprise_user(
            subscription_plan.enterprise_customer_uuid,
            email_recipient,
        )
Ejemplo n.º 9
0
def send_reminder_email_task(custom_template_text, email_recipient_list,
                             subscription_uuid):
    """
    Sends license activation reminder email(s) asynchronously.

    Arguments:
        custom_template_text (dict): Dictionary containing `greeting` and `closing` keys to be used for customizing
            the email template.
        email_recipient_list (list of str): List of recipients to send the emails to.
        subscription_uuid (str): UUID (string representation) of the subscription that the recipients are associated
            with or will be associated with.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    pending_licenses = subscription_plan.licenses.filter(
        user_email__in=email_recipient_list).order_by('uuid')
    enterprise_api_client = EnterpriseApiClient()
    enterprise_customer = enterprise_api_client.get_enterprise_customer_data(
        subscription_plan.enterprise_customer_uuid)
    enterprise_slug = enterprise_customer.get('slug')
    enterprise_name = enterprise_customer.get('name')
    enterprise_sender_alias = get_enterprise_sender_alias(enterprise_customer)
    enterprise_contact_email = enterprise_customer.get('contact_email')

    # We need to send these emails individually, because each email's text must be
    # generated for every single user/activation_key
    for pending_license in pending_licenses:
        user_email = pending_license.user_email
        license_activation_key = str(pending_license.activation_key)
        braze_campaign_id = settings.BRAZE_REMIND_EMAIL_CAMPAIGN
        braze_trigger_properties = {
            'TEMPLATE_GREETING': custom_template_text['greeting'],
            'TEMPLATE_CLOSING': custom_template_text['closing'],
            'license_activation_key': license_activation_key,
            'enterprise_customer_slug': enterprise_slug,
            'enterprise_customer_name': enterprise_name,
            'enterprise_sender_alias': enterprise_sender_alias,
            'enterprise_contact_email': enterprise_contact_email,
        }
        recipient = _aliased_recipient_object_from_email(user_email)

        try:
            braze_client_instance = BrazeApiClient()
            braze_client_instance.create_braze_alias(
                [user_email],
                ENTERPRISE_BRAZE_ALIAS_LABEL,
            )
            braze_client_instance.send_campaign_message(
                braze_campaign_id,
                recipients=[recipient],
                trigger_properties=braze_trigger_properties,
            )

        except BrazeClientError as exc:
            message = ('Error hitting Braze API. '
                       f'reminder email to {user_email} for license failed.')
            logger.exception(message)
            raise exc

    License.set_date_fields_to_now(pending_licenses, ['last_remind_date'])
Ejemplo n.º 10
0
def _send_bulk_enrollment_results_email(
    bulk_enrollment_job,
    campaign_id,
):
    """
    Sends email with properties required to detail the results of a bulk enrollment job.

    Arguments:
        bulk_enrollment_job (BulkEnrollmentJob): the completed bulk enrollment job
        campaign_id: (str): The Braze campaign identifier

    """
    try:
        enterprise_api_client = EnterpriseApiClient()
        enterprise_customer = enterprise_api_client.get_enterprise_customer_data(
            bulk_enrollment_job.enterprise_customer_uuid, )

        admin_users = enterprise_api_client.get_enterprise_admin_users(
            bulk_enrollment_job.enterprise_customer_uuid, )

        # https://web.archive.org/web/20211122135949/https://www.braze.com/docs/api/objects_filters/recipient_object/
        recipients = []
        for user in admin_users:
            if int(user['id']) != bulk_enrollment_job.lms_user_id:
                continue
            # must use a mix of send_to_existing_only: false + enternal_id w/ attributes to send to new braze profiles
            recipient = {
                'send_to_existing_only': False,
                'external_user_id': str(user['id']),
                'attributes': {
                    'email': user['email'],
                }
            }
            recipients.append(recipient)
            break

        braze_client = BrazeApiClient()
        braze_client.send_campaign_message(campaign_id,
                                           recipients=recipients,
                                           trigger_properties={
                                               'enterprise_customer_slug':
                                               enterprise_customer.get('slug'),
                                               'enterprise_customer_name':
                                               enterprise_customer.get('name'),
                                               'bulk_enrollment_job_uuid':
                                               str(bulk_enrollment_job.uuid),
                                           })
        msg = (
            f'success _send_bulk_enrollment_results_email for bulk_enrollment_job_uuid={bulk_enrollment_job.uuid} '
            'braze_campaign_id={campaign_id} lms_user_id={bulk_enrollment_job.lms_user_id}'
        )
        logger.info(msg)
    except Exception as ex:
        msg = (
            f'failed _send_bulk_enrollment_results_email for bulk_enrollment_job_uuid={bulk_enrollment_job.uuid} '
            'braze_campaign_id={campaign_id} lms_user_id={bulk_enrollment_job.lms_user_id}'
        )
        logger.error(msg, exc_info=True)
        raise ex
    def test_create_pending_enterprise_user_logs(self, mock_oauth_client, mock_logger):
        """
        Verify the ``create_pending_enterprise_user`` method logs an error for a status code of >=400.
        """
        # Mock out the response from the lms
        mock_oauth_client().post.return_value = MockResponse({'detail': 'Bad Request'}, 400)

        EnterpriseApiClient().create_pending_enterprise_user(self.uuid, self.user_email)
        mock_logger.error.assert_called_once()
Ejemplo n.º 12
0
def _get_admin_users_for_enterprise(enterprise_customer_uuid):
    api_client = EnterpriseApiClient()
    admin_users = api_client.get_enterprise_admin_users(
        enterprise_customer_uuid)
    return [{
        'lms_user_id': admin_user['id'],
        'ecu_id': admin_user['ecu_id'],
        'email': admin_user['email']
    } for admin_user in admin_users]
Ejemplo n.º 13
0
    def test_create_pending_enterprise_user_successful(self, mock_oauth_client,
                                                       mock_logger):
        """
        Verify the ``create_pending_enterprise_user`` method does not retry or log an error on success
        """
        # Mock out the response from the lms
        mock_oauth_client().post.return_value = MockResponse(
            {'detail': 'Good Request'}, 201)

        EnterpriseApiClient().create_pending_enterprise_user(
            self.uuid, self.user_email)
        assert mock_oauth_client().post.call_count == 1
        mock_logger.error.assert_not_called()
    def test_revoke_course_enrollments_for_user_with_error(self, mock_oauth_client, mock_logger):
        """
        Verify the ``update_course_enrollment_mode_for_user`` method logs an error for a status code of >=400.
        """
        # Mock out the response from the lms
        mock_oauth_client().post.return_value = MockResponse({'detail': 'Bad Request'}, 400)
        mock_oauth_client().post.return_value.content = 'error response'

        EnterpriseApiClient().revoke_course_enrollments_for_user(
            user_id=self.user_id,
            enterprise_id=self.uuid,
        )
        mock_logger.error.assert_called_once()
Ejemplo n.º 15
0
def link_learners_to_enterprise_task(learner_emails, enterprise_customer_uuid):
    """
    Links learners to an enterprise asynchronously.

    Arguments:
        learner_emails (list): list containing the list of learner emails to link to the enterprise
        enterprise_customer_uuid (str): UUID (string representation) of the enterprise to link learns to
    """
    enterprise_api_client = EnterpriseApiClient()
    for learner_email in learner_emails:
        enterprise_api_client.create_pending_enterprise_user(
            enterprise_customer_uuid,
            learner_email,
        )
    def test_create_pending_enterprise_user_rate_limited(self, mock_oauth_client):
        """
        Verify the ``create_pending_enterprise_user`` method retries on a 429 response code.
        """
        rate_limited_response = MockResponse({'detail': 'Rate limited'}, 429)
        # Mock out a few rate-limited response and one good from the lms
        mock_oauth_client().post.side_effect = [
            rate_limited_response,
            rate_limited_response,
            rate_limited_response,
            MockResponse({'detail': 'Good Request'}, 201),
        ]

        EnterpriseApiClient().create_pending_enterprise_user(self.uuid, self.user_email)
        assert mock_oauth_client().post.call_count == 4
Ejemplo n.º 17
0
def link_learners_to_enterprise_task(learner_emails, enterprise_customer_uuid):
    """
    Links learners to an enterprise asynchronously.

    Arguments:
        learner_emails (list): list email addresses to link to the given enterprise.
        enterprise_customer_uuid (str): UUID (string representation) of the enterprise to link learns to.
    """
    enterprise_api_client = EnterpriseApiClient()

    for user_email_batch in chunks(learner_emails, PENDING_ACCOUNT_CREATION_BATCH_SIZE):
        enterprise_api_client.create_pending_enterprise_users(
            enterprise_customer_uuid,
            user_email_batch,
        )
Ejemplo n.º 18
0
    def test_create_pending_enterprise_user_rate_with_error_retries(
            self, mock_oauth_client):
        """
        Verify the ``create_pending_enterprise_user`` method retries on a non 429 error code
        """
        error_response = MockResponse({'detail': '500 Internal Server'}, 500)
        # Mock out a few rate-limited response and one good from the lms
        mock_oauth_client().post.side_effect = [
            error_response,
            error_response,
            MockResponse({'detail': 'Good Request'}, 201),
        ]

        EnterpriseApiClient().create_pending_enterprise_user(
            self.uuid, self.user_email)
        assert mock_oauth_client().post.call_count == 3
Ejemplo n.º 19
0
def sync_agreement_with_enterprise_customer(customer_agreement):
    """
    Syncs any updates made to the enterprise customer slug or name as returned by the
    ``EnterpriseApiClient`` with the specified ``CustomerAgreement``.
    """
    try:
        customer_data = EnterpriseApiClient().get_enterprise_customer_data(
            customer_agreement.enterprise_customer_uuid,
        )
        customer_agreement.enterprise_customer_slug = customer_data.get('slug')
        customer_agreement.enterprise_customer_name = customer_data.get('name')
        customer_agreement.save()
    except HTTPError as exc:
        error_message = (
            'Could not fetch customer fields from the enterprise API: {}'.format(exc)
        )
        raise CustomerAgreementError(error_message) from exc
Ejemplo n.º 20
0
def license_expiration_task(license_uuids):
    """
    Sends terminating the licensed course enrollments for the submitted license_uuids asynchronously

    Arguments:
        license_uuids (list of str): The UUIDs of the expired licenses
    """
    try:
        enterprise_api_client = EnterpriseApiClient()
        enterprise_api_client.bulk_licensed_enrollments_expiration(expired_license_uuids=license_uuids)
    except Exception:  # pylint: disable=broad-except
        logger.error(
            "Expiration of course enrollments FAILED for licenses [{license_uuids}]".format(
                license_uuids=license_uuids,
            ),
            exc_info=True,
        )
    def get_enterprise_customer(self, enterprise_customer_slug):
        """ Returns an enterprise customer """
        logger.info('\nFetching an enterprise customer {} name ...'.format(enterprise_customer_slug))
        try:
            enterprise_api_client = EnterpriseApiClient()

            # Query endpoint by slug for easy dev CLI experience
            endpoint = '{}?slug={}'.format(enterprise_api_client.enterprise_customer_endpoint,
                                           str(enterprise_customer_slug))
            response = enterprise_api_client.client.get(endpoint).json()
            if response.get('count'):
                return response.get('results')[0]

            return None

        except IndexError:
            logger.error('No enterprise customer found.')
            return None
def create_relationships(apps, schema_editor):
    """
    Create new CustomerAgreements from all existing SubscriptionPlans with enterprise customers.
    """
    CustomerAgreement = apps.get_model('subscriptions', 'CustomerAgreement')
    SubscriptionPlan = apps.get_model('subscriptions', 'SubscriptionPlan')

    subscriptions_with_customers = SubscriptionPlan.objects.exclude(enterprise_customer_uuid=None)
    for plan in subscriptions_with_customers:
        customer_uuid = plan.enterprise_customer_uuid
        enterprise_slug = EnterpriseApiClient().get_enterprise_customer_data(customer_uuid).get('slug')
        customer_agreement, _ = CustomerAgreement.objects.get_or_create(
            enterprise_customer_uuid=customer_uuid,
            defaults={
                'enterprise_customer_slug': enterprise_slug,
            }
        )
        plan.customer_agreement = customer_agreement
        plan.save()
Ejemplo n.º 23
0
def send_revocation_cap_notification_email_task(subscription_uuid):
    """
    Sends revocation cap email notification to ECS asynchronously.

    Arguments:
        subscription_uuid (str): UUID (string representation) of the subscription that has reached its recovation cap.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    enterprise_api_client = EnterpriseApiClient()
    enterprise_customer = enterprise_api_client.get_enterprise_customer_data(
        subscription_plan.enterprise_customer_uuid)
    enterprise_name = enterprise_customer.get('name')

    now = localized_utcnow()
    revocation_date = datetime.strftime(now, "%B %d, %Y, %I:%M%p %Z")

    braze_campaign_id = settings.BRAZE_REVOKE_CAP_EMAIL_CAMPAIGN
    braze_trigger_properties = {
        'SUBSCRIPTION_TITLE': subscription_plan.title,
        'NUM_REVOCATIONS_APPLIED': subscription_plan.num_revocations_applied,
        'ENTERPRISE_NAME': enterprise_name,
        'REVOKED_LIMIT_REACHED_DATE': revocation_date,
    }
    recipient = _aliased_recipient_object_from_email(
        settings.CUSTOMER_SUCCESS_EMAIL_ADDRESS)

    try:
        braze_client_instance = BrazeApiClient()
        braze_client_instance.create_braze_alias(
            [settings.CUSTOMER_SUCCESS_EMAIL_ADDRESS],
            ENTERPRISE_BRAZE_ALIAS_LABEL,
        )
        braze_client_instance.send_campaign_message(
            braze_campaign_id,
            recipients=[recipient],
            trigger_properties=braze_trigger_properties,
        )

    except BrazeClientError as exc:
        message = 'Revocation cap notification email sending received an exception.'
        logger.exception(message)
        raise exc
Ejemplo n.º 24
0
def send_revocation_cap_notification_email_task(subscription_uuid):
    """
    Sends revocation cap email notification to ECS asynchronously.

    Arguments:
        subscription_uuid (str): UUID (string representation) of the subscription that has reached its recovation cap.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    enterprise_api_client = EnterpriseApiClient()
    enterprise_name = enterprise_api_client.get_enterprise_name(
        subscription_plan.enterprise_customer_uuid)

    try:
        send_revocation_cap_notification_email(
            subscription_plan,
            enterprise_name,
        )
    except Exception:  # pylint: disable=broad-except
        logger.error(
            'Revocation cap notification email sending received an exception.',
            exc_info=True)
Ejemplo n.º 25
0
def send_revocation_cap_notification_email_task(subscription_uuid):
    """
    Sends revocation cap email notification to ECS asynchronously.

    Arguments:
        subscription_uuid (str): UUID (string representation) of the subscription that has reached its recovation cap.
    """
    subscription_plan = SubscriptionPlan.objects.get(uuid=subscription_uuid)
    enterprise_api_client = EnterpriseApiClient()
    enterprise_customer = enterprise_api_client.get_enterprise_customer_data(subscription_plan.enterprise_customer_uuid)
    enterprise_name = enterprise_customer.get('name')
    enterprise_sender_alias = enterprise_customer.get('sender_alias', 'edX Support Team')

    try:
        send_revocation_cap_notification_email(
            subscription_plan,
            enterprise_name,
            enterprise_sender_alias,
        )
    except SMTPException:
        logger.error('Revocation cap notification email sending received an exception.', exc_info=True)
Ejemplo n.º 26
0
    def test_create_pending_enterprise_user_rate_with_error_retries_and_rate_limiting(
            self, mock_oauth_client):
        """
        Verify the ``create_pending_enterprise_user`` method has 3 error retries if a rate limited error occurs
        """
        error_response = MockResponse({'detail': '500 Internal Server'}, 500)
        rate_limited_response = MockResponse({'detail': 'Rate limited'}, 429)
        # Mock out a few rate-limited response and one good from the lms
        mock_oauth_client().post.side_effect = [
            error_response,
            error_response,
            rate_limited_response,
            error_response,
            error_response,
            error_response,
            error_response,
            MockResponse({'detail': 'Good Request'}, 201),
        ]

        EnterpriseApiClient().create_pending_enterprise_user(
            self.uuid, self.user_email)
        assert mock_oauth_client().post.call_count == 6
Ejemplo n.º 27
0
def license_expiration_task(license_uuids,
                            ignore_enrollments_modified_after=None):
    """
    Sends terminating the licensed course enrollments for the submitted license_uuids asynchronously

    Arguments:
        license_uuids (list of str): The UUIDs of the expired licenses
    """
    try:
        enterprise_api_client = EnterpriseApiClient()
        enterprise_api_client.bulk_licensed_enrollments_expiration(
            expired_license_uuids=license_uuids,
            ignore_enrollments_modified_after=ignore_enrollments_modified_after
        )
        logger.info(
            "Expiration of course enrollments SUCCEEDED for licenses [{license_uuids}]"
            .format(license_uuids=license_uuids, ))
    except Exception as exc:
        logger.error(
            "Expiration of course enrollments FAILED for licenses [{license_uuids}]"
            .format(license_uuids=license_uuids, ),
            exc_info=True,
        )
        raise exc
Ejemplo n.º 28
0
def send_post_activation_email_task(enterprise_customer_uuid, user_email):
    """
    Asynchronously sends post license activation email to learner.
    """
    enterprise_customer = EnterpriseApiClient().get_enterprise_customer_data(
        enterprise_customer_uuid)
    enterprise_name = enterprise_customer.get('name')
    enterprise_slug = enterprise_customer.get('slug')
    enterprise_sender_alias = get_enterprise_sender_alias(enterprise_customer)
    enterprise_contact_email = enterprise_customer.get('contact_email')

    braze_campaign_id = settings.BRAZE_ACTIVATION_EMAIL_CAMPAIGN
    braze_trigger_properties = {
        'enterprise_customer_slug': enterprise_slug,
        'enterprise_customer_name': enterprise_name,
        'enterprise_sender_alias': enterprise_sender_alias,
        'enterprise_contact_email': enterprise_contact_email,
    }
    recipient = _aliased_recipient_object_from_email(user_email)

    try:
        braze_client_instance = BrazeApiClient()
        braze_client_instance.create_braze_alias(
            [user_email],
            ENTERPRISE_BRAZE_ALIAS_LABEL,
        )
        braze_client_instance.send_campaign_message(
            braze_campaign_id,
            recipients=[recipient],
            trigger_properties=braze_trigger_properties,
        )
    except BrazeClientError as exc:
        message = ('Error hitting Braze API. '
                   f'Onboarding email to {user_email} for license failed.')
        logger.exception(message)
        raise exc
Ejemplo n.º 29
0
def enterprise_enrollment_license_subsidy_task(
    bulk_enrollment_job_uuid,
    enterprise_customer_uuid,
    learner_emails,
    course_run_keys,
    notify_learners,
    subscription_uuid,
):
    """
    Enroll a list of enterprise learners into a list of course runs with or without notifying them.
    Optionally, filter license check by a specific subscription.

    Arguments:
        bulk_enrollment_job_uuid (str): UUID (string representation) for a BulkEnrollmentJob created
            by the enqueuing process for logging and progress tracking table updates.
        enterprise_customer_uuid (str): UUID (string representation) the enterprise customer id
        learner_emails (list(str)): email addresses of the learners to enroll
        course_run_keys (list(str)): course keys of the courses to enroll the learners into
        notify_learners (bool): whether or not to send notifications of their enrollment to the learners
        subscription_uuid (str): UUID (string representation) of the specific enterprise subscription to use when
            validating learner licenses
    """
    # AED 2022-01-24 - I don't have enough context to unwind this sanely.
    # Declaring bankruptcy for now.
    # pylint: disable=too-many-nested-blocks
    try:
        logger.info('starting enterprise_enrollment_license_subsidy_task for '
                    f'bulk_enrollment_job_uuid={bulk_enrollment_job_uuid} '
                    f'enterprise_customer_uuid={enterprise_customer_uuid}')

        # collect/return results (rather than just write to the CSV) to help testability
        results = []

        bulk_enrollment_job = BulkEnrollmentJob.objects.get(
            uuid=bulk_enrollment_job_uuid)
        customer_agreement = CustomerAgreement.objects.get(
            enterprise_customer_uuid=enterprise_customer_uuid)

        # this is to avoid hitting timeouts on the enterprise enroll api
        # take course keys 25 at a time, for each course key chunk, take learners 25 at a time
        for course_run_key_batch in chunks(course_run_keys, 25):
            logger.debug(
                "enterprise_customer_uuid={} course_run_key_batch size: {}".
                format(enterprise_customer_uuid, len(course_run_key_batch)))
            for learner_enrollment_batch in chunks(learner_emails, 25):
                logger.debug(
                    "enterprise_customer_uuid={} learner_enrollment_batch size: {}"
                    .format(enterprise_customer_uuid,
                            len(learner_enrollment_batch)))

                missing_subscriptions, licensed_enrollment_info = utils.check_missing_licenses(
                    customer_agreement,
                    learner_enrollment_batch,
                    course_run_key_batch,
                    subscription_uuid=subscription_uuid,
                )

                if missing_subscriptions:
                    for failed_email, course_keys in missing_subscriptions.items(
                    ):
                        for course_key in course_keys:
                            results.append([
                                failed_email, course_key, 'failed',
                                'missing subscription'
                            ])

                if licensed_enrollment_info:
                    options = {
                        'licenses_info': licensed_enrollment_info,
                        'notify': notify_learners
                    }
                    enrollment_result = EnterpriseApiClient(
                    ).bulk_enroll_enterprise_learners(
                        str(enterprise_customer_uuid), options).json()

                    for success in enrollment_result['successes']:
                        results.append([
                            success.get('email'),
                            success.get('course_run_key'), 'success', ''
                        ])

                    for pending in enrollment_result['pending']:
                        results.append([
                            pending.get('email'),
                            pending.get('course_run_key'), 'pending',
                            'pending license activation'
                        ])

                    for failure in enrollment_result['failures']:
                        results.append([
                            failure.get('email'),
                            failure.get('course_run_key'), 'failed', ''
                        ])

                    if enrollment_result.get('invalid_email_addresses'):
                        for result_email in enrollment_result[
                                'invalid_email_addresses']:
                            for course_key in course_run_key_batch:
                                results.append([
                                    result_email, course_key, 'failed',
                                    'invalid email address'
                                ])

        with NamedTemporaryFile(mode='w', delete=False) as result_file:
            result_writer = csv.writer(result_file)
            result_writer.writerow(
                ['email address', 'course key', 'enrollment status', 'notes'])
            for result in results:
                result_writer.writerow(result)

            result_file.close()

            if hasattr(settings, "BULK_ENROLL_JOB_AWS_BUCKET"
                       ) and settings.BULK_ENROLL_JOB_AWS_BUCKET:
                bulk_enrollment_job.upload_results(result_file.name)

            if hasattr(settings, "BULK_ENROLL_RESULT_CAMPAIGN"
                       ) and settings.BULK_ENROLL_RESULT_CAMPAIGN:
                _send_bulk_enrollment_results_email(
                    bulk_enrollment_job=bulk_enrollment_job,
                    campaign_id=settings.BULK_ENROLL_RESULT_CAMPAIGN,
                )

        return results
    except Exception as ex:
        msg = ('failed enterprise_enrollment_license_subsidy_task for '
               f'bulk_enrollment_job_uuid={bulk_enrollment_job_uuid} '
               f'enterprise_customer_uuid={enterprise_customer_uuid}')
        logger.error(msg, exc_info=True)
        raise ex
Ejemplo n.º 30
0
    def post(self, request):
        """
        Returns the enterprise bulk enrollment API response after validating that each user requesting to be enrolled
        has a valid subscription for each of the requested courses.

        Expected params:
            - notify (bool): Whether or not learners should be notified of their enrollments.

            - course_run_keys (list of strings): An array of course run keys in which all provided learners will be
            enrolled in
            Example:
                course_run_keys: ['course-v1:edX+DemoX+Demo_Course', 'course-v2:edX+The+Second+DemoX+Demo_Course', ... ]

            - emails (string): A single string of multiple learner emails separated with a `\n` (new line) character
            Example:
                emails: '[email protected]\[email protected]\[email protected]'

            - enterprise_customer_uuid (string): the uuid of the associated enterprise customer provided as a query
            params.

        Expected Return Values:
            Success cases:
                - All learners have licenses and are enrolled - {}, 201

            Partial failure cases:
                License verification and bulk enterprise enrollment happen non-transactionally, meaning that a subset of
                learners failing one step will not stop others from continuing the enrollment flow. As such, partial
                failures will be reported in the following ways:

                Fails license verification:
                    response includes: {'failed_license_checks': [<users who do not have valid licenses>]}

                Fails Enrollment:
                    response includes {'failed_enrollments': [<users who were not able to be enrolled>]

                Fails Validation (something goes wrong with requesting enrollments):
                    response includes:
                     {'bulk_enrollment_errors': [<errors returned by the bulk enrollment endpoint>]}
        """
        param_validation = self._validate_request_params()
        if param_validation:
            return Response(param_validation,
                            status=status.HTTP_400_BAD_REQUEST)

        results = {}
        customer_agreement = utils.get_customer_agreement_from_request_enterprise_uuid(
            request)
        missing_subscriptions, licensed_enrollment_info = self._check_missing_licenses(
            customer_agreement)

        if missing_subscriptions:
            msg = 'One or more of the learners entered do not have a valid subscription for your requested courses. ' \
                  'Learners: {}'.format(missing_subscriptions)
            results['failed_license_checks'] = missing_subscriptions
            logger.error(msg)

        if licensed_enrollment_info:
            options = {
                'licenses_info': licensed_enrollment_info,
                'notify': self.requested_notify_learners
            }
            enrollment_response = EnterpriseApiClient(
            ).bulk_enroll_enterprise_learners(self.requested_enterprise_id,
                                              options)

            # Check for bulk enrollment errors
            if enrollment_response.status_code >= 400 and enrollment_response.status_code != 409:
                status_code = status.HTTP_400_BAD_REQUEST
                results['bulk_enrollment_errors'] = []
                try:
                    response_json = enrollment_response.json()
                except JSONDecodeError:
                    # Catch uncaught exceptions from enterprise
                    results['bulk_enrollment_errors'].append(
                        enrollment_response.reason)
                else:
                    msg = 'Encountered a validation error when requesting bulk enrollment. Endpoint returned with ' \
                          'error: {}'.format(response_json)
                    logger.error(msg)

                    # check for non field specific errors
                    if response_json.get('non_field_errors'):
                        results['bulk_enrollment_errors'].append(
                            response_json['non_field_errors'])

                    # check for param field specific validation errors
                    for param in options:
                        if response_json.get(param):
                            results['bulk_enrollment_errors'].append(
                                response_json.get(param))

            else:
                enrollment_result = enrollment_response.json()
                if enrollment_result.get('failures'):
                    results['failed_enrollments'] = enrollment_result[
                        'failures']

                if enrollment_result.get('failures') or missing_subscriptions:
                    status_code = status.HTTP_409_CONFLICT
                else:
                    status_code = status.HTTP_201_CREATED
        else:
            status_code = status.HTTP_404_NOT_FOUND

        return Response(results, status=status_code)